Add performance, asset pipeline, WebGPU/TSL and R3F skills - #13
Draft
AgentEnder wants to merge 14 commits into
Draft
AgentEnder wants to merge 14 commits into
AgentEnder wants to merge 14 commits into
Conversation
Covers the "make it fast, don't leak" axis that the existing skills document only in passing. - Draw call budget (<100/frame) as the governing constraint, with a strategy table for InstancedMesh vs BatchedMesh vs mergeGeometries - Full GPU disposal sweep, including the ImageBitmap close() that GLTF textures need on top of dispose() - Object pooling and texture caching to avoid GC pauses - Profiling: renderer.info, stats-gl, lil-gui, Spector.js, DevTools - three-mesh-bvh accelerated raycasting - Context loss handling and setAnimationLoop - A triage checklist ordered cheapest-fix-first APIs verified against three r185.
Asset compression and loading strategy, previously uncovered. - gltf-transform CLI as the primary tool, with per-operation recipes - Draco vs Meshopt trade-off (compression ratio vs decode speed and decoder size) - KTX2/Basis: why PNG/JPEG cost ~10x the VRAM, and when to pick UASTC (normal maps, hero textures) over ETC1S (diffuse, secondary) - Decoder path setup checklist, the most common prod-only breakage - Loading strategy for Core Web Vitals: code splitting, IntersectionObserver lazy loading, preload hints, progressive loading, streaming, workers - Rough production budgets for GLB size, texture size and triangle count
No existing skill mentioned WebGPURenderer or TSL. - WebGPURenderer setup, the mandatory await init(), automatic WebGL 2 fallback, forceWebGL, browser support matrix and feature detection - A "should you migrate?" table, since WebGPU is not universally faster - TSL node materials, the Fn pattern for reusable shader logic, and the built-in MaterialX noise library - Compute shaders: instancedArray GPU-persistent buffers, GPU particles, physics, storage textures, workgroup shared memory, indirect draws - Node-based post-processing All exports verified against the three r185 export maps. Note that the view direction node is positionViewDirection; there is no viewDirection export, and effect nodes live in three/addons/tsl/display/ rather than three/tsl.
R3F had no coverage at all despite being how many projects consume three.js. - The core rule: React's render cycle and the animation loop are different clocks; mutate through refs rather than setState in useFrame - Never allocate inside useFrame, and drive motion with delta - On-demand rendering via frameloop="demand" plus invalidate(), including the gotcha where scene changes silently do not appear - Re-render avoidance: useThree selectors, React.memo, visibility toggling instead of remounting - Suspense, useGLTF.preload, drei Detailed for LOD - Disposal on unmount, since R3F only auto-disposes what it created - Adaptive quality and the dpr clamp, the highest-leverage mobile setting Closes with a common-mistakes list.
The skill documented the light API thoroughly but gave little guidance on what each option costs. - The light budget: 3 or fewer active lights, since each is compiled into every lit material's shader - Shadow map renders per light type, calling out that PointLight costs 6 (one per cube face) so two shadowed PointLights over 10 casters is 120 extra draw calls - Shadow map sizing table; memory is quadratic in resolution - renderer.shadowMap.autoUpdate = false for static casters, a large and cheap win for product viewers and walkthroughs - Cascaded Shadow Maps, including the camera/parent options and the setupMaterial + update calls the addon requires - Baked lightmaps and cheap fake contact shadows Also fixes a broken import: ContactShadows was imported from three/examples/jsm/objects/ContactShadows.js, which does not exist in three.js. It is a drei component. Replaced with a vanilla gradient-plane recipe and a pointer to drei for R3F users.
- Recommends pmndrs/postprocessing for WebGL, which merges compatible effects into a single fullscreen pass where EffectComposer runs one pass per effect - Renderer configuration once a composer is present: antialias/stencil/ depth off, tone mapping deferred to the end of the pipeline rather than applied early to already-uncompressed values - Selective bloom via SelectiveBloomEffect instead of layer swapping - Expanded performance section: pass merging, multisampling: 0, half-res effect rendering, AA last, bloom parameter ranges, render target disposal Also fixes the WebGPU section, which imported a lowercase `postProcessing` and effect nodes from three/addons/nodes/Nodes.js. Neither resolves. PostProcessing comes from three/webgpu, pass() from three/tsl, and effect nodes from three/addons/tsl/display/. The pipeline composes via getTextureNode(), not a .pipe() chain.
Replaces a five-line performance list with guidance on what actually costs time in a fragment shader. - mediump on mobile, roughly 2x faster than highp, with a note on which values still need precision - Branchless mix()/step(), and when a branch is still worth keeping - Varying budget: under 3 on mobile, with a packing example - RGBA channel packing to cut texture fetches, plus the NoColorSpace requirement for data textures - Constant loop bounds with early break instead of dynamic bounds - Shader program reuse and watching renderer.info.programs.length - A pointer to TSL for anyone targeting WebGPU, since raw GLSL/WGSL means maintaining two shader codebases
- Adds a BatchedMesh section: many distinct geometries sharing one material in a single draw call, the variation InstancedMesh cannot give - Documents the two-step id model (addGeometry returns a geometry id, addInstance returns an instance id, and setMatrixAt takes the instance id), which changed after the r156 API commonly cited in blog posts - Adds the <100 draw calls per frame budget and a table for choosing between InstancedMesh, BatchedMesh, mergeGeometries and LOD, including what merging costs you in culling and raycast granularity - Notes that frustum culling is only correct if bounding volumes are
Material sharing was one line in a list; it is the highest-leverage material-level optimization there is. - Shows the bad/good pair: a unique material per mesh defeats batching and multiplies compiled shader programs, each compile being a main thread stall - Explains when three.js reuses a program, and why gratuitous variation (differing defines, a map present on one material and not another) proliferates them - Points at per-instance data as the right way to get variation - Adds a warning against creating materials inside a render callback
- Expands the compressed texture section to explain why it matters: PNG and JPEG are file compression and the GPU stores them decompressed, so a 200KB PNG occupies 20MB+ of VRAM while KTX2/Basis stays compressed on the GPU for roughly 10x less - Documents the UASTC vs ETC1S choice and makes detectSupport(renderer) explicitly mandatory - Adds a DataArrayTexture section: indexed layers give visual variety with a single texture bind, pairing naturally with BatchedMesh - Adds the ImageBitmap close() step, since GLTFLoader decodes textures into ImageBitmaps that survive texture.dispose() and leak
- Adds Meshopt as the Draco alternative: comparable compression once gzipped, faster decode, ~30KB decoder against Draco's ~200KB WASM - Adds a decoder setup checklist, since misconfigured paths are the most common works-locally-breaks-in-production failure. Covers copying the decoder directories as a build step, version-pinning any CDN URL, the mandatory detectSupport call, preload() to keep WASM off the critical path, and loader disposal - Replaces the unpinned CDN transcoder path with a local path - Notes that removing a model from the scene does not free VRAM
Default raycasting tests every triangle, which is unusable at interactive rates on detailed models. - Adds the three-mesh-bvh prototype patch, per-geometry computeBoundsTree, the firstHitOnly flag that lets the BVH prune aggressively, and disposeBoundsTree for cleanup - Notes when it is not worth it (a handful of simple primitives, where the BVH build cost exceeds the saving) - Points R3F users at drei's <Bvh> wrapper
- Switches the Quick Start and Clock examples from requestAnimationFrame to renderer.setAnimationLoop, which handles WebXR sessions (where rAF does not work) and gives clean start/stop - Adds context loss handling; unhandled, a lost context leaves the canvas permanently black, and preventDefault() is required for restoration - Leads the performance section with measurement via renderer.info and the <100 draw call target, and adds the setPixelRatio clamp - Notes that three.js never garbage-collects GPU resources, with a pointer to the full disposal sweep
- Adds a Performance & Platform table covering threejs-performance, threejs-asset-pipeline, threejs-webgpu-tsl and threejs-react-three-fiber - Extends the auto-loading examples with the request shapes that trigger them - Notes that the new skills were verified against the three r185 source and export maps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds four skills and expands nine existing ones, covering the performance/platform axis the current set documents only in passing. The existing skills are a strong API reference (what exists, correct signatures); this adds the budget and trade-off layer (what to reach for, what it costs).
New skills
threejs-performancerenderer.info, stats-gl, Spector.js), three-mesh-bvh, context lossthreejs-asset-pipelinethreejs-webgpu-tslFn, compute shaders,instancedArray, storage textures, workgroup memory, indirect draws, node post-processingthreejs-react-three-fiberuseFramemutation,frameloop="demand"/invalidate, re-render avoidance, Suspense, drei, disposal on unmountUpdated skills
threejs-lighting(light budget, PointLight's 6x shadow cost, CSM,shadowMap.autoUpdate, lightmaps, fake shadows) ·threejs-postprocessing(pmndrs pass merging, renderer config, tone mapping placement) ·threejs-shaders(mediump, varying budget, branchless, RGBA packing, program reuse) ·threejs-geometry(BatchedMesh, batching strategy table) ·threejs-materials(material sharing) ·threejs-textures(KTX2 VRAM cost, array textures, ImageBitmap) ·threejs-loaders(Meshopt, decoder checklist) ·threejs-interaction(three-mesh-bvh) ·threejs-fundamentals(setAnimationLoop, context loss)Bug fixes included
Two existing snippets don't resolve against any three.js version:
threejs-lightingimportedContactShadowsfromthree/examples/jsm/objects/ContactShadows.js. That file does not exist —ContactShadowsis a drei component. Replaced with a vanilla gradient-plane recipe plus a pointer to drei.threejs-postprocessingimported a lowercasepostProcessingand effect nodes fromthree/addons/nodes/Nodes.js.PostProcessingcomes fromthree/webgpu,pass()fromthree/tsl, and effect nodes fromthree/addons/tsl/display/.Verification
New and changed APIs were checked against the
threer185 source and export maps rather than from memory. Three things that differ from what's commonly published:positionViewDirection; there is noviewDirectionexport.bloom/fxaa/smaa/doflive inthree/addons/tsl/display/*, notthree/tsl, and compose viagetTextureNode()rather than a.pipe()chain.BatchedMeshchanged after r156:addGeometry()returns a geometry id,addInstance()returns an instance id, andsetMatrixAttakes the instance id. Code written against the older shape indexes the wrong thing silently.Notes