A scene can be rendered several times for one displayed frame: the main view, a reflection, and an ambient-occlusion pass may all need the same object poses. In my browser game Vesper, I keep animation and transform preparation ahead of those render passes.
This article was prepared with generative AI and is labeled Fully Autonomous. I used GPT-6 Astra in Codex during the game's development too; that is separate from the authorship of this article.
One set of poses, several cameras
A child mesh inherits its parent's transform. An animated skeleton adds another dependency: bones must reach their world-space poses before a pass reads them. If different passes update that hierarchy independently, the code does repeated work and makes the ordering harder to reason about.
The current implementation uses this small wrapper, from src/scene-frame.ts:
import type {Scene} from 'three';
/** Animation is finished before submission. Main, reflection and AO cameras
* share those transforms; their render callbacks change visibility, not poses.
* Keep camera updates automatic, and restore the scene policy even on failure. */
export function renderSceneFrame(scene:Scene,render:()=>void){
const automatic=scene.matrixWorldAutoUpdate;
scene.updateMatrixWorld();
scene.matrixWorldAutoUpdate=false;
try{render();}finally{scene.matrixWorldAutoUpdate=automatic;}
}
The ordering is the contract:
- Finish animation and object movement.
- Update the scene's world matrices once.
- Submit all the views that share those poses.
- Restore the original automatic-update policy.
The wrapper changes the scene's policy, leaving camera updates automatic. The finally matters: a failed render should not leave the scene stuck in a different update mode on the next frame. It also restores an originally false setting correctly.
The constraint that makes this useful
Render callbacks may change visibility, but they must not move objects or change bone poses after the shared matrix update. A reflection that temporarily moves geometry would violate this contract. Such a pass needs a different update boundary or an explicit additional transform update.
This is a scheduling choice for this renderer. It is not a general instruction to disable automatic updates in every Three.js project. The code here targets the project's installed Three.js 0.180 setup.
What the checks establish
The existing focused tests exercise a parent/child hierarchy and animated bone poses across four simulated render passes. They assert that the transforms are current and that scene preparation happens once. A second test throws inside the render callback and checks restoration for both original policy values.
On September 15, 2026, npm run test:focus -- tests/scene-frame.test.ts completed with 2 passing tests and no failures. These checks cover the transform contract and error cleanup. They do not measure a frame-rate gain, GPU cost, or performance on a phone.
The live implementation is part of Vesper - The Last Light, a Three.js browser adventure with reflective flooded areas. The takeaway is small: define when poses become stable, share that state only across compatible passes, and restore temporary rendering policy even when a pass fails.
Top comments (0)