@linear_non/stellar-gl
v1.6.3
Published
WebGL setup & helpers for Non-Linear Studio projects.
Readme
stellar-gl
@linear_non/stellar-gl— WebGL / Three.js layer for Non-Linear Studio projects.
A lightweight and modular WebGL layer built on top of Three.js, designed to integrate seamlessly with stellar-kit. Supports full-page single-canvas setups and per-DOM-element modular GL components.
Installation
npm install @linear_non/stellar-glRequires three and @linear_non/stellar-kit:
npm install three @linear_non/stellar-kit
@theatre/coreis bundled as a direct dependency — no additional install needed.
For the editor UI, install in devDependencies only:npm install -D @theatre/studio
Package exports
@linear_non/stellar-gl/classes → classes/index.js
@linear_non/stellar-gl/utils → utils/index.js
@linear_non/stellar-gl/three → libraries/three/index.js
@linear_non/stellar-gl/loaders → loaders/index.js
@linear_non/stellar-gl/shaders/* → shaders/*
@linear_non/stellar-gl/decoders/* → decoders/*Classes
| Class | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| App | Main entry point. Wires stellar-kit events → world + canvas lifecycle. Supports ManagerClass / managerOptions for swappable managers |
| Canvas | Owns scene, camera instance, and renderer |
| Camera | PerspectiveCamera / OrthographicCamera wrapper with getViewSize() |
| Renderer | THREE.WebGLRenderer wrapper (alpha, high-performance, DPR capped at 2, AA off) |
| RenderTarget | THREE.WebGLRenderTarget wrapper with size-change guard |
| Materials | Caching material + HDR env map manager (auto-unwraps renderer wrapper) |
| PostPass | Full-screen post-processing: scene → RT → custom ShaderMaterial quad |
| ShaderPlane | PlaneGeometry(1,1) + ShaderMaterial — fullscreen or DOM-tracked, with built-in uTime / uResolution uniforms |
| TextGL | Troika-powered 3D text — standalone or DOM-tracked with CSS-driven responsive sizing via getTextStyles |
| GLSpritePlayer | KTX2 compressed sprite sequence player on a ShaderPlane — two-phase loading, scroll-driven playback, cover-fit rendering |
| WorldManager | Per-DOM-element GL component manager ([data-{name}] selectors) — always-on, all components tick every frame |
| ScrollableWM | Extends WorldManager — adds isActive gating, scrollProgress (0→1), and onEnter/onLeave lifecycle hooks |
| SceneManager | Multi-scene manager with RT-based transitions — pluggable transition shaders, manual/scroll-driven progress, auto Theatre sheets per scene |
Loaders
| Loader | Description |
| ------------- | ---------------------------------------------------------------------------- |
| ModelLoader | GLTFLoader + DRACOLoader with batch API (loadAll) |
| AssetLoader | Preferred. Unified manifest loader — texture, cubeTexture, gltf, hdr, ktx2. Promise + event API. |
| Resources | Deprecated. Texture + cube texture only, event API. Kept for backward compat — use AssetLoader instead. |
Utils
| Util | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| Tracker | Tracks a DOM element's bounds → position + size in 3D world space |
| DOMSync | DOM → mesh sync for both Lenis smooth-scroll (desktop) and native scroll (mobile) — same API for both |
| boundsToView | DOMRect → world { position, size } at camera plane |
| boundsToViewAtZ | DOMRect → world { position, size } at arbitrary Z depth |
| OrbitControls | THREE.OrbitControls wrapper that integrates with App services pattern |
| Theatre | Theatre.js (@theatre/core) service wrapper |
| getTextStyles | Reads computed CSS from a DOM element and converts px → 3D world units (fontSize, lineHeight, etc.) |
| MeshSampler | sampleSurface, sampleSurfaceWithNormals, sampleSurfaceForInstancing, normalsToQuaternions, sampleVertices, sampleToTexture — mesh sampling for particles, instancing + GPGPU seeds |
| dracoPath | Draco CDN URL constant used by ModelLoader |
Usage
Full-page single world
import { setupKit } from "@linear_non/stellar-kit"
import { App } from "@linear_non/stellar-gl/classes"
setupKit()
class MyWorld {
constructor({ scene, camera, gl, services }) { ... }
update({ time, current }) { ... }
resize() { ... }
destroy() { ... }
}
const app = new App({ canvas: document.querySelector("canvas"), WorldClass: MyWorld })Modular DOM-synced GL components
const app = new App({
canvas: document.querySelector("canvas"),
components: [
{ name: "hero", Class: GLHero },
{ name: "slider", Class: GLSlider },
],
})
// Each class receives: { el, index, scene, camera, gl, services }
// Mapped via [data-hero], [data-slider] attributesPost-processing
import { PostPass } from "@linear_non/stellar-gl/classes"
const app = new App({
canvas,
WorldClass: MyWorld,
services: {
post: new PostPass({
fragmentShader: myFrag,
uniforms: { uIntensity: { value: 0.5 } },
}),
},
})Multi-scene transitions
import { App, SceneManager } from "@linear_non/stellar-gl/classes"
const app = new App({
canvas,
WorldClass: class extends SceneManager {
constructor(opts) {
super({
...opts,
scenes: [
{ name: 'hero', Class: HeroScene },
{ name: 'about', Class: AboutScene },
],
transitionShader: gridRevealFrag,
transitionUniforms: { uGridSize: { value: 50 } },
})
}
},
services: { theatre: new Theatre({ projectName: 'MyProject' }) },
})
// Animated transition
app.world.go('about', { duration: 1.8 })
// Scroll-driven
app.world.setManualMode(true)
app.world.prepareTransition('hero', 'about')
app.world.progress = scrollProgress // 0→1Model loading
import { ModelLoader } from "@linear_non/stellar-gl/loaders"
const app = new App({
canvas,
WorldClass: MyWorld,
services: { models: new ModelLoader() },
})
// In MyWorld.constructor:
const { hero } = await this.services.models.loadAll({ hero: "/models/hero.glb" })
this.scene.add(hero)Local Development
npm install
npm run devDev pages:
| Route | Demo |
| ------------------- | ---------------------------------------------------------------------- |
| / | Index |
| /world/ | Spinning cube + ModelLoader |
| /particles/ | GPU tube particles |
| /manager/ | DOM-synced planes + backgroundSize shader |
| /loaders/ | GLTF + DRACO + OrbitControls |
| /asset-loader/ | AssetLoader — GLTF + HDR env map + progress |
| /gl-sprite-player/| GLSpritePlayer — KTX2 sprite sequence, scroll-driven playback |
| /fbo/ | FBOSystem — 16 384 curl-noise particles advanced via GPGPU ping-pong |
| /post/ | PostPass — chromatic aberration + vignette |
| /shader-plane/ | ShaderPlane — fullscreen + DOM-tracked |
| /scroll-manager/ | ScrollableWM — scroll progress, isActive gating, lifecycle hooks |
| /text/ | TextGL — standalone + CSS-driven 3D text |
| /rt/ | RenderTarget — off-screen scene on a cube |
| /theatre/ | Theatre.js Studio + sequence keyframes |
Note: Dev pages require local binary assets (sprites, models, HDR, fonts) that are gitignored to keep the repo lightweight. See
claude.md"Local development" section for details.
Roadmap
High priority
- [x]
GlobalUniformssingleton (classes/GlobalUniforms.js) —{ uTime, uResolution, uDpr }as plain{ value }refs. Spread into anyShaderMaterial—Appkeeps them in sync every tick and resize. No UBO/WebGL2 needed. - [x]
time.scaleon App clock —app.timeScale(default1). Scales delta anduTime. Existing code unaffected. - [x]
AssetLoader(loaders/AssetLoader.js) — unified manifest loader. Supportstexture,cubeTexture,gltf,hdr. Promise API (await loader.load(sources)) + event API. Key-based deduplication.Resourceskept untouched. - [x]
TrackerasObject3Dsubclass —Trackernow extendsTHREE.Object3D.scene.add(tracker),tracker.add(mesh). Fully backward-compatible. - [x] GLSL shader chunks library (
shaders/chunks/) —cnoise,simplex,valueNoise,mapRange,rotate2d,rotate3d,applyQuat,backgroundCoveras JS template-literal GLSL. Import from@linear_non/stellar-gl/shaders/chunks. - [x]
onBeforeCompileshader injector (utils/shaderInjector.js) —injectShader(mat, { uniforms, vertex, fragment })replaces#includetokens in Three.js built-in materials. SetscustomProgramCacheKey, warns on unknown tokens. - [x]
MeshSamplerutility (utils/MeshSampler.js) —sampleSurface,sampleSurfaceWithNormals,sampleSurfaceForInstancing,normalsToQuaternions,sampleVertices,sampleToTexture. WrapsMeshSurfaceSamplerfor particleBufferAttributes, instanced geometry, and GPGPUDataTextureseeds. - [x] GPU performance tier (
utils/gpuTier.js) —getTier()→1|2|3,tierScale([0,64,96,128]),setTierOverride(). Tier 1 = mobile, 2 = Safari desktop, 3 = all other desktop. - [x]
assignMaterialsmodel traversal util (utils/assignMaterials.js) —assignMaterials(gltfRoot, { 'Wheel*': mat, '*Glass*': glassMat })with glob wildcards. ReturnsMap<Mesh, Material>of all updated meshes. - [x]
TextGL(classes/TextGL.js) +getTextStyles(utils/getTextStyles.js) — Troika-powered 3D text with standalone and CSS-driven DOM-tracked modes.getTextStylesconverts computed CSS (font-size, line-height, letter-spacing, color) to world units.preloadFontre-exported from@linear_non/stellar-gl/three.
Medium priority
- [ ]
ScrollableWMcross-fade hook — optionalonTransition(from, to, progress)callback called each frame asscrollProgresschanges between two active components. - [x] Three.js
Layershelper (utils/layers.js) —createLayerMap()+setLayer(object3D, layer)for per-section render isolation. Shipped v1.4.0. - [x]
ktx2type inAssetLoader—{ name, type: 'ktx2', path }support viaKTX2Loader(Basis Universal). Shipped v1.5.0.
Lower priority
- [x]
FBOSystemGPGPU wrapper (classes/FBOSystem.js) — ping-pongWebGLRenderTargethelper (no external dependencies). Shipped v1.4.0. - [x]
SceneManager(classes/SceneManager.js) — multi-scene manager with RT-based transitions, pluggable transition shaders, manual/scroll-driven progress, auto Theatre sheets per scene. Shipped v1.6.0. - [ ]
Appasync init path —async init()gates first render behindawait resources.load(manifest)to prevent blank frames. - [ ]
LazyLoader— visibility-gated asset loading withIntersectionObserver+ Web Worker image decoding viacreateImageBitmap. - [ ] Theatre.js per-scene wrapper —
createSheet(project, sheet, state)helper reducing boilerplate for per-scene authoring.
Nice to have
- [ ] Debug stats via URL param — auto-mount
three-perfoverlay when?debugis active. Zero overhead in production. - [ ]
DestroyManagermixin — tracks allIntersectionObservers, event listeners, and timers for automatic flush ondestroy().
Completed
- [x] Post-processing pipeline — shipped as
PostPass - [x] Theatre.js service —
Theatreclass bundled with@theatre/coreas a direct dependency (@theatre/studiodev-only) - [x]
ModelLoader— shipped inloaders/ModelLoader.js - [x]
RenderTargetsupport — shipped inclasses/RenderTarget.js - [x]
Renderer.dispose()fix (B1) - [x]
WorldManagerdropsservicesfix (B2) - [x]
Resourceshangs on unknown types fix (B3) - [x]
boundsToViewAtZraw camera expectation documented (B4) - [x] README antialias correction (B5)
- [x]
boundsToView+Trackercontainer-aware (B6) - [x]
Renderer.resize()updates pixel ratio (B7) - [x]
Materialsauto-unwraps renderer wrapper (B8) - [x]
Resourcesempty-sources emits withitems(B9)
Made with ❤️ by Non-Linear Studio
