3D shots
Three.js (live layer)
- Use
threeLayer(el, { update(t), background, fov })from/engines/three.js. Add an import map forthree(seeexamples/engines-demo/index.html). update(t)sets every transform fromt. Camera moves: keyframe with a GSAP tween on a plain object (tl.to(cam, { x: 4, z: 8, duration: 2 }, 3)) and copy it intocamera.positioninupdate.- Models:
GLTFLoaderfromthree/addons/loaders/GLTFLoader.js(add"three/addons/": "/core/node_modules/three/examples/jsm/"to the import map). Wrap the load inStudio.wait(...). Put model files in the brand'sassets/models/or the production'scomposition/. - The look (
looks/README.md) decides the rendering style; the same model can go any of these ways:- Cartoon (
looks/toon-3d):toon(color, { steps })+outline(mesh, { thickness }). Hard light: oneDirectionalLight+ lowAmbientLight. - Realistic (
looks/photoreal):await realistic(renderer, scene)(wrap inStudio.wait: ACES tone mapping, a studio environment for reflections, soft shadows) +pbr(color, { metalness, roughness, clearcoat }). A key light withcastShadow, a cool rim light from behind, aShadowMaterialfloor over a CSS gradient. Keep the camera above grazing angles (glossy faces turn white), move slowly, render the final withStudio.setup({ motionBlur: true }). - Neon (
looks/neon-noir):emissive(color, intensity)+threeLayer(el, { post: bloom({ strength, threshold }) }),scene.fog. Keepthreshold≥ 0.2 and emissive ≤ 2 or everything blooms white.
- Cartoon (
- Animated GLB clips: create an
AnimationMixer, then inupdatecallmixer.setTime(t - start). Nevermixer.update(delta). - HTML tags that follow 3D points: in
update(t), callcamera.updateMatrixWorld()and the parent'supdateMatrixWorld()beforevector.applyMatrix4(obj.matrixWorld).project(camera). Three.js refreshes these only insiderender(), so without it a tag follows the previous frame. Example:productions/bike-logger/REEL-002-engines. - CAD meshes as JSON (vertices + indices):
toNonIndexed()+computeVertexNormals()gives crisp flat facets. Render withthreeLayer(..., { pixelRatio: 2 })for supersampled edges. - Physics: bake it (simulate at a fixed 1/120 s step into an array before the first frame) and look up by
t. - Talking character: load
/engines/lipsync.jsand the line's.visemes.js(fromscripts/lipsync.sh) with classic<script>tags, then inupdate(t):LipSync.paramsAt(cues, t, "mouth", { offset })→{ open, wide, round }(0…1) onto morph targets (mesh.morphTargetInfluences) or a mouth mesh's scale. Seeengines/README.md, Lip sync.
Blender (plate)
- A scene script defines
build(ctx); copyengines/blender/example_scene.py.ctxgivestoon(rgb),key(obj, "location", t, value),size,fps,duration,frames,scene. - Render:
engines/blender/render.sh scene.py composition/plates/<name>.mp4 --size 1080x1920 --duration 4 [--engine EEVEE|CYCLES|WORKBENCH]. Draft at--size 540x960, final at full size. - Backdrop colour: show it to the camera only (Light Path "Is Camera Ray" mix, as in the example), or it lights the scene and flattens toon bands.
- Colour management is set to Standard so brand hex colours come out as typed. For a photoreal plate, switch to
ctx["scene"].view_settings.view_transform = "AgX"inbuild, render with--engine CYCLES, and use area lights and camera depth of field. - Physics and simulations: bake them in the scene script before rendering.
- Talking character:
import lipsync as Lin the scene script (run.pyputsengines/blender/and the script's own folder on the path),cues = L.load("productions/…/audio/vo.visemes.json"), thenL.key_shape_keys(head, cues, ctx, offset=VO_START)keys shape keysopen,wide,roundon every frame (names={"open": "Jaw"}for other names). Anything else:L.params_at(cues, t, offset=…)per frame withctx["key"]. - In the composition:
<video src="plates/<name>.mp4">+Studio.video(el, { start }). Plates have no alpha; put text and characters on top with HTML layers.
Check
Render the key beats as stills and look at them. For Three.js, render one frame twice and compare md5s. Note render times in the production log.