About web3d-runtime
A skill for developers building three.js Web 3D scenes, covering WebGPU and WebGL2 rendering, shaders, performance checks, and fallback behavior. Its guidance is verified against three.js r186 and emphasizes compatibility failures that can leave a scene rendering incorrectly.
Key features
- 01Check versions, renderer, and frame loop
- 02Guide WebGPU and WebGL2 fallbacks
- 03Cover TSL and compute shaders
- 04Address prewarming, measurement, and disposal
- 05Flag checks that were not run
Use cases
- 01Debug rendering issues in WebGPU scenes
- 02Port ShaderMaterial code to TSL
- 03Measure and tune frame time on weaker devices
- 04Plan for device loss or missing WebGPU
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ ssheleg/ web3d-dev --skill web3d-runtimeAfter installing, type/web3d-runtimein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/performance-and-r3f.mdrenderer-and-loop.mdtsl-and-compute.mdDetails
From the project READMEweb3d-runtime — the scene runs, on every GPU it meets, and you can prove how fast
three.js on WebGPU moves quickly: r186 throws where r180 warned, renamed the post-processing
entry point, deprecated Clock, and still ships a WebGL2 backend underneath the same API.
Most bugs here are silent — the scene renders, just wrongly. So this skill leads with the
failures, then the patterns.
First action — inspect, then report
- Versions.
three,@react-three/fiber,@react-three/dreifrompackage.json. Facts here are pinned to r186; on another release, checkreferences/renderer-and-loop.md→ Version notes before trusting a name. - Renderer.
WebGPURendererfromthree/webgpu, or the classicWebGLRenderer? CustomShaderMaterial,RawShaderMaterialoronBeforeCompileanywhere? Those do not run on WebGPU (gotchas). - Loop. Who owns the frame:
setAnimationLoop, R3F'suseFrame, or severalrequestAnimationFrames (a bug — one clock per scene). - Evidence of speed. Is there any measurement (Inspector, timestamps, fps under load on the weakest target), or only a feeling?
Report a table — area · now · problem · fix — and exactly ONE next action.
The capability ladder
Choose the rung per scene, top down; every rung must still be a working page.
- WebGPU —
new WebGPURenderer()thenawait renderer.init(). - WebGL2 backend of the same renderer — automatic when WebGPU is unavailable (it logs
running under WebGL2 backend); forced withforceWebGL: true. Compute-heavy features need a lighter path here: detect withrenderer.backend.isWebGPUBackendafterinit(). - Calm — no WebGL at all, reduced motion, or a device loss that could not recover: a
static image or CSS/SVG still, fully legible. This is
sheleg-design's degrade-to-calm rule applied to a 3D scene.
Gotchas — r186, each one silent or surprising
render()throws beforeawait renderer.init()(so doclear(),hasFeature(),initTexture());setAnimationLoopawaits init itself.renderAsyncand friends are deprecated since r181.positionNodereplaces the position after skinning, morphs and instancing.positionLocal.add(offset)keeps them;positionGeometry.add(...)orbuffer.element(instanceIndex)alone collapses every instance to a point. For particles use the official pattern:positionNode = buffer.toAttribute()on a sprite or point material.- JS reassignment is not a shader store. Inside
Fn,a = a.add(1)rebinds a JS name and emits nothing; use.toVar()then.assign()/.addAssign(). A JSifor ternary on a node is the same bug — useIf/select. - Atomics need
storage(buf, 'uint', n).toAtomic()beforeatomicAdd. computeAsync()does not wait for the GPU. Onlyrenderer.getArrayBufferAsync()reads results back to the CPU (with a reusableTHREE.ReadbackBufferin r186).ShaderMaterial,RawShaderMaterialandonBeforeCompilehave no WebGPU mapping — the object renders as a plain node material with a "not compatible" error. Port to TSL. About twenty drei components are built on them and fail the same way.- The adapter is requested in compatibility mode; on a device that only offers it, MSAA is silently disabled.
alphadefaults totrueon WebGPURenderer (false on WebGLRenderer): a port that relied on an opaque canvas shows the page through it.PostProcessingis nowRenderPipeline;Clockis deprecated — useTimer(core);PCFSoftShadowMapis removed on WebGPU (PCFShadowMapis soft since r183);renderer.dispose()is async.- Device loss: use
renderer.onDeviceLost(andrenderer.onErrorfor validation and out-of-memory errors). It exists on both backends; hooking the WebGPU device directly breaks under the WebGL2 fallback. - R3F 9.8.1 defaults differ from three's: ACES tone mapping on,
shadows={true}selects the removed soft shadow map (useshadows="percentage"), anduseFramedelta is unclamped.
The procedure
- Renderer and loop — one renderer,
await init(), onesetAnimationLoop, oneTimer(references/renderer-and-loop.md). - Materials and shaders in TSL; compute for anything per-particle or per-instance
(
references/tsl-and-compute.md). - Prewarm:
await renderer.compileAsync(scene, camera)(andcompileComputeAsyncfor compute) before the first visible frame, so the first interaction does not hitch. - Measure on the weakest target with the Inspector or timestamp queries, then spend the
budget (
references/performance-and-r3f.md). - Degrade: reduced motion, no WebGPU, device loss and hidden tab each have a written branch — and each branch was seen working.
- Tear down: dispose geometries, materials, textures and render targets;
await renderer.dispose().
When something is missing
- No tools at all in this host → write the commands and the code, never their results. A
size, a pass, a frame time or "done" that nothing measured is fabricated evidence; mark each
such check
NOT_RUNand say what a person must run. - No browser or GPU in this host → build, type-check and review; report visual and
frame-time gates as
NOT_RUNwith the page and the device a person must use. Never report "smooth" from reading code. - The project is on an older three.js → name the release gap; do not apply r186 names blindly, and do not upgrade the dependency without the operator.
- R3F project, WebGPU wanted → the async
glfactory pattern inreferences/performance-and-r3f.md; list the drei components the scene uses that will not run on WebGPU before switching.
References
| Read | When |
|---|---|
references/renderer-and-loop.md | creating the renderer, the fallback, the loop and clock, colour and tone mapping, post-processing, device loss, disposal |
references/tsl-and-compute.md | writing a TSL material or shader, a compute pass, particles, atomics, readback, or porting GLSL |
references/performance-and-r3f.md | measuring or fixing frame time, draw calls and memory; running React Three Fiber on WebGPU; drei on WebGPU |
Asset files and budgets are web3d-assets; making things move is web3d-animation. The
public webgpu-threejs-tsl skill (dgreenheck) informed the gotcha-first shape of this one;
the errata found when re-verifying its examples against r186 are among the gotchas above.
Triggers
More phrases that route here: "WGSL", "compute shader" / "компьют-шейдер", "particles collapsed" / "частицы схлопнулись", "device lost" / "потеря устройства", "React Three Fiber", "post-processing" / "постобработка", "WebGL fallback".
Moved here from the description in 0.2.4: "three.js WebGPU", "the 3D scene is slow".