orbix-engine
v1.0.4
Published
Orbix — GPU-accelerated WebGL engine with fluid simulations, particle systems, and post-processing. Works on desktop & mobile.
Maintainers
Readme
Orbix Engine
GPU-accelerated WebGL engine — fluid simulations, particle systems, post-processing, physics, audio, VR/AR, and more. One script tag, zero dependencies, works on desktop and mobile.
<script src="https://unpkg.com/[email protected]/dist/orbix.min.js"></script><script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/orbix.min.js"></script>Hello World
<!DOCTYPE html>
<html>
<head>
<style>* { margin: 0 } body { background: #000; overflow: hidden }</style>
</head>
<body>
<script src="https://unpkg.com/[email protected]/dist/orbix.min.js"></script>
<script>
Orbix.ready().then(function() {
var cube = new Mesh(
new BoxGeometry(2, 2, 2),
new Shader('ColorMaterial', {
unique: true,
color: { value: new Color(0.2, 0.5, 1.0) },
alpha: { value: 1.0 }
})
);
World.SCENE.add(cube);
World.CAMERA.position.z = 6;
Render.start(function() {
cube.rotation.x += 0.01;
cube.rotation.y += 0.02;
});
});
</script>
</body>
</html>Install
| Method | URL |
|--------|-----|
| unpkg CDN | https://unpkg.com/[email protected]/dist/orbix.min.js |
| jsDelivr CDN | https://cdn.jsdelivr.net/npm/[email protected]/dist/orbix.min.js |
| npm | npm install orbix-engine |
npm / bundler
import Orbix, { Mesh, BoxGeometry, Shader, Color, Render, World } from 'orbix-engine';
Orbix.ready().then(() => { /* ... */ });Core Concepts
Orbix.ready()
All engine use must happen inside the .ready() promise. The engine boots asynchronously (WebGL context, shader compilation, internal asset loading). Calling it multiple times is safe — each call gets its own Promise.
Orbix.ready().then(function() {
// safe to use any Orbix class here
});
// or async/await
async function main() {
await Orbix.ready();
// ...
}Orbix.check()
Returns support information before loading the full bundle:
var info = Orbix.check();
// { supported: true, webgl: true, mobile: false, gpu: 'Apple M2', tier: 'high' }API Reference
Globals available after ready()
All classes and constants listed below are also available directly on window (or the Orbix namespace). You can use new Mesh(...) or new Orbix.Mesh(...) interchangeably.
Boot
| Symbol | Description |
|--------|-------------|
| Orbix.ready() | Returns Promise — resolves when engine is fully booted |
| Orbix.check() | Returns { supported, webgl, mobile, gpu, tier } — safe to call before load |
| Orbix.VERSION | Semver string |
| Orbix.supported | Boolean — false if WebGL unavailable |
World & Rendering
| Symbol | Description |
|--------|-------------|
| World.SCENE | The root Scene — add your objects here |
| World.CAMERA | Default PerspectiveCamera |
| World.RENDERER | The Renderer instance |
| World.NUKE | Post-processing pipeline |
| World.ELEMENT | The <canvas> DOM element |
| Render.start(fn) | Register a per-frame callback (time, dt) => void |
| Render.stop(fn) | Remove a callback |
| Render.fps | Current frames per second |
Render.start(function(time, dt) {
mesh.rotation.y += dt * 2;
});3D Objects
| Class | Constructor | Description |
|-------|-------------|-------------|
| Mesh | (geometry, shader) | A visible 3D object |
| Group | () | Container to transform multiple objects together |
| Scene | () | Root container (use World.SCENE by default) |
| Points | (geometry, shader) | Point cloud |
| Line3D | (geometry, shader) | Line strip |
| Object3D | () | Base class — position, rotation, scale, matrix |
Every 3D object has:
mesh.position.set(x, y, z)
mesh.rotation.x = Math.PI / 4
mesh.scale.setScalar(2)
mesh.visible = false
mesh.add(child)
mesh.remove(child)
mesh.traverse(fn)
mesh.dispose()Geometry
| Class | Constructor |
|-------|-------------|
| PlaneGeometry | (width, height, widthSegs, heightSegs) |
| BoxGeometry | (width, height, depth, wSegs, hSegs, dSegs) |
| SphereGeometry | (radius, widthSegs, heightSegs) |
| CylinderGeometry | (radiusTop, radiusBottom, height, radialSegs) |
| IcosahedronGeometry | (radius, detail) |
| Geometry | Base — set attributes and index manually |
// Custom geometry
var geo = new Geometry();
geo.addAttribute('position', new GeometryAttribute(new Float32Array([...]), 3));
geo.setIndex([0, 1, 2]);
geo.computeVertexNormals();Loaders:
// Load from web worker thread (non-blocking)
GeomThread.loadGeometry('/model.bin').then(function(geo) { ... });
// Draco-compressed
DracoThread.decode(buffer).then(function(geo) { ... });
// GLTF
var loader = new GLTFLoader();
loader.load('/scene.glb', function(gltf) {
World.SCENE.add(gltf.scene);
});
// Spline paths
var spline = new SplineGen(points, closed);
var pt = spline.getPoint(0.5); // Vector3 at t=0.5Shaders & Materials
Built-in: ColorMaterial
The simplest shader — a flat color with optional transparency.
new Shader('ColorMaterial', {
unique: true, // each mesh gets its own uniform copy
color: { value: new Color(1, 0, 0) }, // RGB 0–1
alpha: { value: 1.0 }
})Custom GLSL via Shaders.parse()
Register a custom shader at runtime from a JavaScript string and use it like any built-in:
Shaders.parse(`
{@}MyShader.glsl{@}
#!ATTRIBUTES
#!UNIFORMS
uniform float uTime;
uniform vec3 uColor;
#!VARYINGS
varying vec2 vUv;
#!SHADER: MyShader.Vertex
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
#!SHADER: MyShader.Fragment
void main() {
gl_FragColor = vec4(uColor, 1.0);
}
`, 'MyShader.glsl');
var mesh = new Mesh(
new SphereGeometry(1, 32, 32),
new Shader('MyShader', {
unique: true,
uTime: { value: 0 },
uColor: { value: new Color(0.2, 0.8, 1.0) }
})
);
World.SCENE.add(mesh);
Render.start(function(time) {
mesh.shader.uniforms.uTime.value = time;
});PBRShader — physically based
new PBRShader({
roughness: 0.4,
metalness: 0.8,
envMapIntensity: 1.0
})ShaderVariants — switch between presets
var variants = new ShaderVariants('ColorMaterial', {
hot: { color: { value: new Color(1, 0.2, 0) } },
cool: { color: { value: new Color(0, 0.5, 1) } }
});
mesh.shader = variants.use('cool');Math
Vectors
var v = new Vector3(1, 2, 3);
v.add(new Vector3(0, 1, 0)); // chainable
v.normalize();
v.length();
v.dot(other);
v.cross(other);
v.lerp(target, 0.5);
v.applyMatrix4(matrix);
v.clone();Vector2, Vector3, Vector4 all follow the same pattern.
Color
var c = new Color(1, 0.5, 0); // RGB 0–1
c.setHex(0xff8800);
c.setHSL(0.05, 1.0, 0.5);
c.lerp(other, t);Quaternion / Euler / Matrix
var q = new Quaternion();
q.setFromEuler(new Euler(0, Math.PI, 0));
q.slerp(target, 0.1);
var m = new Matrix4();
m.makeRotationFromQuaternion(q);
m.makeTranslation(x, y, z);MathUtils
MathUtils.radians(90) // 1.5707...
MathUtils.lerp(0, 10, 0.5) // 5
MathUtils.clamp(val, 0, 1)
MathUtils.range(val, 0, 1, -100, 100)
MathUtils.smoothStep(0, 1, val)
MathUtils.randFloat(0, 1)
MathUtils.randInt(0, 10)Textures
// From URL (cached)
var tex = Utils3D.getTexture('/path/to/image.jpg');
// Repeat-wrap texture
var rep = Utils3D.getRepeatTexture('/tile.jpg');
// Cubemap
var env = Utils3D.loadCubemap(['+x.jpg', '-x.jpg', '+y.jpg', '-y.jpg', '+z.jpg', '-z.jpg']);
// Data texture (Float32 data → GPU)
var data = Utils3D.createDataTexture(float32Array, width, height);
// RenderTarget (render-to-texture)
var rt = new RenderTarget(512, 512, { type: 'float' });
// Pooled RenderTargets (saves GC pressure)
var rt = RTPool.get(512, 512);
RTPool.release(rt);Camera
// Default perspective camera
World.CAMERA.fov = 60;
World.CAMERA.position.z = 10;
World.CAMERA.lookAt(new Vector3(0, 0, 0));
World.CAMERA.updateProjectionMatrix();
// Orthographic
var ortho = new OrthographicCamera(-1, 1, 1, -1, 0.1, 100);
// VR / XR
var vrCam = new VRCamera();
vrCam.setFromXRFrame(frame, refSpace);
// Screen ↔ World projection
var screen = ScreenProjection.toScreen(worldPos, camera); // Vector2
var world = ScreenProjection.toWorld(screenPos, z, camera); // Vector3Lighting
Lighting.setAmbient(new Color(0.1, 0.1, 0.1), 1.0);
var sun = new ShadowLight({
color: new Color(1, 0.95, 0.8),
intensity: 2.0,
shadow: { mapSize: new Vector2(2048, 2048) }
});
sun.castShadow = true;
World.SCENE.add(sun);Post-Processing (Nuke)
// Bloom
var bloom = new HydraBloom({ strength: 1.5, radius: 0.4, threshold: 0.7 });
World.NUKE.add(bloom);
// Unreal-style bloom
var unrealBloom = new UnrealBloom({ strength: 2.0, radius: 0.3 });
World.NUKE.add(unrealBloom);
// Anti-aliasing
var fxaa = new FXAA();
World.NUKE.add(fxaa);
// Lens streaks
var streak = new HydraLensStreak();
World.NUKE.add(streak);
// Volumetric light shafts
var vol = new VolumetricLight({ density: 0.96, decay: 0.94, weight: 0.4, samples: 100 });
World.NUKE.add(vol);
// Custom fullscreen shader pass
var pass = new NukePass('MyShader', {
uTexture: { value: someTexture },
uIntensity: { value: 0.5 }
});
World.NUKE.add(pass);
// Mirror reflection
var mirror = new Mirror({ clipBias: 0.003 });
World.SCENE.add(mirror);Fluid Simulation
// Standalone fluid (manage yourself)
var fluid = new Fluid(128, 512); // simSize, dyeSize
Render.start(function(t, dt) {
fluid.splat(Mouse.nX * 0.5 + 0.5, 0.5 - Mouse.nY * 0.5,
Mouse.vX * 20, -Mouse.vY * 20,
new Color(1, 0.5, 0));
fluid.update(dt);
mesh.shader.uniforms.uFluid.value = fluid.texture;
});
// Mouse-driven fluid (automatic)
var mouseFluid = new MouseFluid();
mouseFluid.applyTo(mesh); // links fluid texture to mesh's shader
Render.start(function() { mouseFluid.update(); });
// Full fluid scene (mesh + sim pre-wired)
var scene = new FluidScene({ simSize: 128, dyeSize: 512 });
World.SCENE.add(scene.mesh);
Render.start(function(t, dt) { scene.update(dt); });Particles — Antimatter
GPU-based particle system simulated on the graphics card.
var particles = new Antimatter({
count: 100000,
size: 2.0,
blending: 'additive',
spawner: new AntimatterSpawn({ rate: 1000, burst: false }),
behavior: 'default'
});
World.SCENE.add(particles.mesh);
// Manual spawn
particles.spawn(
new Vector3(0, 0, 0), // position
new Vector3(0, 1, 0) // velocity
);
Render.start(function() { /* Antimatter auto-updates via Render loop */ });Spline particles — particles that travel along a path:
var path = new SplineGen([p0, p1, p2, p3], false);
var ribbon = new SplineParticles(path, { count: 500, size: 1.5 });
World.SCENE.add(ribbon.mesh);
Render.start(function(t, dt) { ribbon.update(dt); });Instancing & Batching
Draw thousands of meshes in a single draw call:
// InstanceMesh — each instance can have its own matrix + color
var inst = new InstanceMesh(
new BoxGeometry(0.5, 0.5, 0.5),
new Shader('ColorMaterial', { color: { value: new Color(1, 1, 1) } }),
10000
);
var m = new Matrix4();
for (var i = 0; i < 10000; i++) {
m.makeTranslation(
MathUtils.randFloat(-10, 10),
MathUtils.randFloat(-10, 10),
MathUtils.randFloat(-10, 10)
);
inst.setMatrixAt(i, m);
inst.setColorAt(i, new Color(Math.random(), Math.random(), Math.random()));
}
inst.update();
World.SCENE.add(inst.mesh);
// MeshBatch — add/remove at runtime
var batch = new MeshBatch(geo, shader, 500);
var id = batch.add(new Matrix4(), new Color(1, 0, 0));
batch.remove(id);
batch.update();Mouse & Interaction
// Normalized mouse position (-1 to +1)
Mouse.nX // horizontal
Mouse.nY // vertical
// Raw pixel position
Mouse.x
Mouse.y
// Velocity (per frame delta)
Mouse.vX
Mouse.vY
// Is mouse/finger down
Mouse.down
// Events
Mouse.on('down', function() { ... });
Mouse.on('up', function() { ... });
Mouse.on('move', function() { ... });
// Pointer-events-style (touch + mouse unified)
Interaction.on('down', handler);
Interaction.on('up', handler);
Interaction.on('move', handler);3D raycasting:
var i3d = new Interaction3D(World.CAMERA, World.SCENE);
i3d.add(mesh);
i3d.on('click', function(obj) { console.log('clicked', obj); });
i3d.on('over', function(obj) { ... });
i3d.on('out', function(obj) { ... });
// Manual raycasting
var raycaster = new Raycaster();
raycaster.setFromCamera(new Vector2(Mouse.nX, -Mouse.nY), World.CAMERA);
var hits = raycaster.intersectObjects(World.SCENE.children, true);Scroll:
Scroll.y // current scroll Y
Scroll.delta // delta this frame
Scroll.on('scroll', function() { ... });Keyboard:
Keyboard.on('down', function(key, code) { ... });
Keyboard.isDown('Space'); // true/false
Keyboard.isDown(32); // by key codeAnimation & Tweening
// TweenManager
TweenManager.to(mesh.position, { y: 5 }, 1.0, 'easeOutCubic');
TweenManager.to(shader.uniforms.uColor, { value: new Color(1, 0, 0) }, 0.5);
TweenManager.kill(mesh.position); // cancel
// One-liner shortcut
tween(mesh.rotation, { y: Math.PI * 2 }, 2.0, 'easeInOutQuad');
// TweenTimeline — sequence / overlap animations
var tl = new TweenTimeline({ loop: false });
tl.add(TweenManager.to(mesh.position, { y: 3 }, 0.5), 0.0);
tl.add(TweenManager.to(mesh.scale, { x: 2 }, 0.5), 0.3);
tl.play();
// CSS transitions
var css = new CSSTransition(element, { duration: 0.4, ease: 'easeOutQuad' });
css.to({ opacity: 0, transform: 'translateY(-20px)' });Ease names: linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeInQuart, easeOutQuart, easeInOutQuart, easeOutBack, easeInOutElastic, easeOutExpo, easeInOutBounce
Audio
// Spatial audio (WebAudio API)
var sfx = new Audio3D('/click.mp3', { volume: 0.8, loop: false });
sfx.position.copy(mesh.position);
sfx.play();
// Sound effects controller (named sound bank)
var sfxCtrl = new SFXController({
click: '/sounds/click.mp3',
whoosh: '/sounds/whoosh.mp3'
});
sfxCtrl.play('click', { volume: 0.5, pitch: 1.2 });
// Master volume
GlobalAudio3D.setVolume(0.5);
GlobalAudio3D.mute();
GlobalAudio3D.unmute();
// Raw Web Audio access
var ctx = WebAudio.context;
var analyser = WebAudio.createAnalyser();
var freqData = WebAudio.getFrequencyData(analyser); // Uint8ArrayVideo
var video = new Video('/clip.mp4', { autoplay: true, loop: true, muted: true, playsInline: true });
video.play();
var vtex = new VideoTexture(video);
// Use as shader texture
var mesh = new Mesh(
new PlaneGeometry(16, 9),
new Shader('MyVideoShader', {
unique: true,
uVideo: { value: vtex }
})
);
Render.start(function() {
vtex.update(); // push new frame to GPU
});
// Webcam
var cam = new Webcam({ facingMode: 'user', width: 1280, height: 720 });
cam.start().then(function() {
var tex = cam.texture;
});Text (WebGL)
var label = new Text3D('Hello World', {
font: '/fonts/Inter.json',
size: 0.5,
color: new Color(1, 1, 1),
align: 'center'
});
World.SCENE.add(label);
label.setText('New Text');
var { width, height } = label.measure();Post — Scene Fragments (Frag3D)
Frag3D is a composable scene unit — encapsulate a sub-scene with its own lifecycle:
class MyFragment extends Frag3D {
init() {
this.mesh = new Mesh(new BoxGeometry(1, 1, 1), new Shader('ColorMaterial', {
unique: true, color: { value: new Color(1, 0, 0) }, alpha: { value: 1 }
}));
this.add(this.mesh);
}
update(dt) {
this.mesh.rotation.y += dt;
}
}
var frag = new MyFragment();
SceneLayout.add(frag);
World.SCENE.add(frag);
Render.start(function(t, dt) { SceneLayout.update(dt); });
frag.show(0.5); // fade in over 0.5s
frag.hide(0.5); // fade outLoading Assets
// Non-blocking asset preload
AssetLoader.on('progress', function(pct) { console.log(pct * 100 + '%'); });
AssetLoader.on('complete', function() { /* all done */ });
AssetLoader.loadAssets({
textures: ['/diffuse.jpg', '/normal.jpg'],
audio: ['/music.mp3'],
json: ['/data.json']
});
// Individual loads
Assets.loadJSON('/config.json').then(function(data) { ... });
Assets.loadText('/shader.glsl').then(function(src) { ... });
// KTX2 compressed textures (GPU-native, fastest load)
var ktx = new Ktx2Transcoder();
ktx.transcode(buffer).then(function(texture) { ... });Multiplayer / Networking
var conn = Multiplayer.connect('wss://yourserver.com');
conn.on('connect', function() { conn.send('hello', { id: 'player1' }); });
conn.on('message', function(data) { ... });
// Synchronized object positions
var sync = new SynchronizedObjects();
sync.add(mesh, 'object-id');
Render.start(function(t, dt) { sync.update(dt); });VR / AR
// Check support
XRDeviceManager.isSupported('immersive-vr').then(function(ok) {
if (ok) {
var btn = document.createElement('button');
btn.textContent = 'Enter VR';
btn.onclick = function() {
XRDeviceManager.requestSession('immersive-vr').then(function(session) {
var vrCam = new VRCamera();
var vrRenderer = new VRRenderer(World.RENDERER, session);
var vrInput = new VRInput(session);
session.requestAnimationFrame(function frame(t, xrFrame) {
vrCam.setFromXRFrame(xrFrame, refSpace);
vrInput.update(xrFrame);
vrRenderer.render(World.SCENE, vrCam, xrFrame);
session.requestAnimationFrame(frame);
});
});
};
document.body.appendChild(btn);
}
});
// AR: place object at tap position
XRDeviceManager.isSupported('immersive-ar').then(function(ok) {
ARUtils.createSession().then(function(session) { ... });
});GPU & Performance
// GPU info (before anything renders)
console.log(GPU.vendor); // 'Apple'
console.log(GPU.tier); // 0 = low, 1 = mid, 2 = high
console.log(GPU.webgl2); // true/false
console.log(GPU.isMobile); // true/false
// Adaptive quality
if (GPU.tier < 1) {
World.RENDERER.setPixelRatio(1);
bloom.enabled = false;
}
// Frame timing
Performance.on('low-fps', function(fps) {
console.warn('FPS dropped to', fps);
});Device Detection
Device.mobile // false | { phone, tablet, pwa, native }
Device.system.os // 'ios' | 'android' | 'windows' | 'macos' | 'linux'
Device.system.browser // 'chrome' | 'safari' | 'firefox' | ...
Device.system.retina // true/false
Device.pixelRatio // 1 | 2 | 3
Device.touchCapable // true/falseThreading
Off-main-thread work via Web Workers:
var worker = Thread.create(function() {
self.onmessage = function(e) {
// do heavy computation
var result = e.data.map(function(n) { return n * n; });
self.postMessage(result);
};
});
Thread.post(worker, bigArray, [bigArray.buffer]);
worker.onmessage = function(e) {
console.log('result:', e.data);
};Debug
Dev.enabled = true;
// Helpers
World.SCENE.add(Dev.grid(World.SCENE, 10, 10)); // grid floor
World.SCENE.add(Dev.axes(2)); // XYZ axes
document.body.appendChild(Dev.stats()); // FPS/MS/MB panel
Dev.log('mesh', mesh);
Dev.warn('shader missing uniform');
Dev.error('fatal:', err);State Management (App)
AppState.set('score', 0);
AppState.get('score'); // 0
// Reactive — callback fires whenever value changes
AppState.on('score', function(val) {
console.log('score changed to', val);
});
// Scoped local state (component-level)
var state = AppState.createLocal({ count: 0, active: true });
state.count++;
// Storage (localStorage wrapper)
Storage.set('prefs', { volume: 0.8 });
Storage.get('prefs'); // { volume: 0.8 }Helper Factories
Convenience shortcuts on the Orbix namespace:
// Full scene setup in one call
var { scene, camera, nuke, renderer, element } = Orbix.createScene();
// Fluid simulation
var fluid = Orbix.createFluid({ simSize: 128, dyeSize: 512 });
// MouseFluid
var mf = Orbix.createMouseFluid();
// Particle system
var particles = Orbix.createAntimatter({ count: 50000 });
// Post-processing pass
var pass = Orbix.createNukePass('ChromaticAberration', { uAmount: { value: 0.005 } });
// Texture
var tex = Orbix.getTexture('/img.jpg');TypeScript
Full type definitions are included (dist/orbix.d.ts):
import Orbix, {
Mesh, BoxGeometry, Shader, Color,
Render, World, Mouse,
Fluid, Antimatter, HydraBloom
} from 'orbix-engine';
Orbix.ready().then(() => {
const mesh = new Mesh(
new BoxGeometry(1, 1, 1),
new Shader('ColorMaterial', {
unique: true,
color: { value: new Color(0.2, 0.5, 1) },
alpha: { value: 1 }
})
);
World.SCENE.add(mesh);
});Files
| File | Size | Description |
|------|------|-------------|
| dist/orbix.min.js | ~1.9 MB | Production CDN build — use this |
| dist/orbix.js | ~3.0 MB | Development build (unminified) |
| dist/orbix.esm.js | 11 KB | ESM loader / named exports |
| dist/orbix.d.ts | 60 KB | Full TypeScript definitions |
License
MIT
