Skip to content

Project-owned shaders

When the authored material types aren’t enough, import Three.js and create a THREE.ShaderMaterial directly in project source. Shader code, uniforms, update rules, and cleanup are game-specific behavior; keeping them together makes their owner and lifecycle explicit.

Project source builds a Three.js ShaderMaterial, attaches it to a mesh, updates uniforms in a phase, and disposes both
The project-owned shader lifecycle.

The setup function can create the mesh and register its update in a normal engine phase:

import type { GameCleanup, GameContext } from '@engine/runtime/types';
import * as THREE from 'three';
export async function setup(ctx: GameContext): Promise<GameCleanup> {
const uniforms = { uTime: { value: 0 } };
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: `
uniform float uTime;
void main() {
vec3 p = position;
p.z += sin(p.x * 0.8 + uTime) * 0.25;
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
}
`,
fragmentShader: `
void main() {
gl_FragColor = vec4(0.04, 0.37, 0.65, 1.0);
}
`,
});
const geometry = new THREE.PlaneGeometry(12, 12, 48, 48);
const water = new THREE.Mesh(geometry, material);
water.rotation.x = -Math.PI / 2;
ctx.scene.add(water);
const animate = (dt: number) => {
uniforms.uTime.value += dt;
};
ctx.systems.add('gameLogic', animate);
return {
dispose() {
ctx.systems.remove('gameLogic', animate);
ctx.scene.remove(water);
geometry.dispose();
material.dispose();
},
};
}
  • The material is a normal Three.js object—there is no vgai shader wrapper or registry.
  • Put animation in a phase-ordered system or a GameComponent attached to the owning mesh.
  • Remove the update and dispose GPU resources in the cleanup returned by setup().

For an editor-authored mesh, a project component is often the better ownership boundary: find or replace its material in init(), update uniforms in update(dt), then restore/dispose in dispose().

Feature-scenes deliberately demonstrates the same ownership rule without a shader-specific engine abstraction. Its scene uses an ordinary standard material, while the project-owned WaterSurface component animates native BufferGeometry in Play mode. A shader component would own a ShaderMaterial through the same lifecycle.