Skip to content
cccskills
FreePublished on GitHub

web-3d-threejs-babylon

Use when building a 3D web game with three.js, Babylon.js or PlayCanvas - render loop and delta per library, fixed-step physics with Rapier or Havok, explicit dispose of GPU resources, instancing and draw-call budgets, glTF with Draco/meshopt/KTX2, pixel ratio and resize, WebGPU opt-in, React Three Fiber notes, and headless testing.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md6.4 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Web 3D: three.js, Babylon.js, PlayCanvas

Overview

three.js is a rendering library with a huge ecosystem; Babylon.js is a full engine (physics plugin, GUI, inspector, a NullEngine for headless tests); PlayCanvas is an engine built around its hosted editor, also usable engine-only. All three hand you the GPU directly — and with it the job of freeing what you allocate, keeping draw calls low, and staying inside a mobile browser's memory.

Core principle: Simulate on a fixed step, render every frame, dispose every GPU resource you create, and count draw calls.

The loop and delta

// three.js — Timer replaces the deprecated Clock (r183+); setAnimationLoop also drives WebXR
const timer = new THREE.Timer();
timer.connect(document);                         // ignores the huge delta after a hidden tab
renderer.setAnimationLoop((t) => {
  timer.update(t);
  const dt = Math.min(timer.getDelta(), 0.25);   // seconds
  stepFixed(dt);                                 // accumulator: physics + rules at 1/60 (game-loop-and-time)
  renderer.render(scene, camera);
});
// Babylon.js — getDeltaTime() is milliseconds
scene.onBeforeRenderObservable.add(() => stepFixed(Math.min(engine.getDeltaTime() / 1000, 0.25)));
engine.runRenderLoop(() => scene.render());
window.addEventListener("resize", () => engine.resize());
// PlayCanvas (engine API / ESM scripts) — dt in seconds
export class Spin extends Script {
  static scriptName = "spin";
  speed = 90;
  update(dt: number) { this.entity.rotate(0, this.speed * dt, 0); }
}

Physics on a fixed step

  • Rapier (WASM, three.js or Babylon): await RAPIER.init(), world.timestep = 1 / 60, call world.step() from the accumulator, copy body transforms to meshes with interpolation. The deterministic build exists for lockstep/rollback.
  • Babylon: Havok via the physics V2 plugin (new HavokPlugin(true, await HavokPhysics())); keep its fixed timestep consistent across devices.
  • PlayCanvas: its rigidbody components (ammo.js) step with the app.
  • Never move a dynamic body by writing mesh.position; set velocities/impulses or use kinematic bodies.

Dispose what you create

// ❌ removing from the scene does not free GPU memory
scene.remove(level);

// ✅ free geometry, materials and textures explicitly (unless shared with something still alive)
level.traverse((o) => {
  if (o instanceof THREE.Mesh) {
    o.geometry.dispose();
    for (const m of Array.isArray(o.material) ? o.material : [o.material]) {
      for (const v of Object.values(m)) if (v instanceof THREE.Texture) v.dispose();
      m.dispose();
    }
  }
});
scene.remove(level);
console.assert(renderer.info.memory.geometries <= baseline.geometries);
  • three.js: also dispose render targets, PMREMGenerator outputs, and the renderer itself on teardown. renderer.info.memory before and after a level change is the leak test.
  • Babylon: mesh.dispose(false, true) frees materials and textures too; load levels into an AssetContainer and dispose() it on exit; scene.dispose() / engine.dispose() on teardown.
  • PlayCanvas: entity.destroy(), and asset.unload() for assets no longer used.

Draw calls and instancing

  • Many copies of one mesh: THREE.InstancedMesh (setMatrixAt + instanceMatrix.needsUpdate = true, count for the live number) or Babylon thin instances (mesh.thinInstanceAdd(matrix)).
  • Merge static scenery (BufferGeometryUtils.mergeGeometries, Babylon Mesh.MergeMeshes) and share materials.
  • Budget draw calls per frame — a few hundred for mobile browsers, more on desktop — and read the actual number (renderer.info.render.calls, Babylon Inspector's statistics) before and after.
  • Shadows are expensive: one shadow-casting light, tight shadow camera bounds, castShadow only where it shows; bake lighting for static scenes.
  • Cap the pixel ratio: renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); resize from the canvas size, not the window, with a ResizeObserver.

Assets

  • glTF/GLB with Draco or meshopt geometry compression and KTX2 textures: three.js GLTFLoader + DRACOLoader / MeshoptDecoder + KTX2Loader (decoder files served from the app's own path); Babylon's glTF loader with its Draco/KTX2 support; LoadAssetContainerAsync to keep a level's assets disposable together.
  • Optimise models offline (npx @gltf-transform/cli optimize) and keep originals out of the shipped bundle (game-asset-pipeline).

WebGPU

three.js WebGPURenderer (from three/webgpu, falling back to WebGL2) and Babylon WebGPUEngine are usable; adopt them only when the repository or task opts in — shader code, post-processing and some materials differ between the paths.

React Three Fiber (if the repo uses it)

useFrame((state, delta) => ...) mutates refs; never setState per frame. Dispose is automatic for objects R3F created declaratively, not for ones you created imperatively. <Instances> / <instancedMesh> for repetition.

Testing

  • Rules and simulation: plain TS with Vitest, no renderer import.
  • Babylon: new NullEngine() + new Scene(engine) runs scene logic headless in Vitest (no pixels).
  • three.js: scene-graph logic can be tested without a renderer; rendering is checked with the seeded Playwright smoke run and screenshots (game-visual-self-review). Headless Chromium uses a software GL — correct images, meaningless frame rates.

Common Mistakes

  • scene.remove without dispose — memory grows every level.
  • Allocating new THREE.Vector3() in the frame loop instead of reusing module-level temporaries.
  • Full device pixel ratio on 3× phones.
  • One mesh per bullet or tree instead of instancing.
  • THREE.Clock in new code on a revision that deprecated it.

Red Flags

  • No dispose() call anywhere in a project that loads more than one level.
  • renderer.info.render.calls in the thousands on a mobile target.
  • Physics stepped with the raw render delta.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here

The English summary is being prepared. Showing the original description.

makifbaysal/tasktrooper117Updated Oct 20, 2026

See all skills by makifbaysal

Report a problem with this skill