Three.js阴影渲染性能问题排查求助
Hey there! Let's break down why your Minecraft-style Three.js build is seeing FPS drop below 30 (and keep falling over time) when shadows are enabled—even though you'd expect this scale to be manageable for a GPU. Here are the most likely culprits and fixes to try:
1. Overly High Shadow Map Resolution & Unnecessary Shadow-Casting Lights
By default, Three.js uses a 1024x1024 shadow map, but if you've got multiple lights casting shadows, each one adds a full render pass. That adds up fast for a blocky world with lots of geometry.
- Fix: Stick to only your main directional light casting shadows. Disable
castShadowon all other lights withlight.castShadow = false. - Tweak resolution: Lower the shadow map size to 512x512 (or even 256x256 if you can tolerate slightly softer shadows) with:
light.shadow.mapSize.width = 512; light.shadow.mapSize.height = 512;
2. Thousands of Individual Meshes Killing Shadow Rendering
Minecraft-style worlds are made of hundreds/thousands of small blocks. If each block is a separate Mesh, the GPU has to process shadow casting for every single one individually—this is a massive waste of resources.
- Fix: Use
THREE.InstancedMeshfor repeating block types. This lets you render hundreds of blocks with a single draw call, and shadow rendering will only process the base geometry once instead of thousands of times. Example setup:const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardMaterial({ color: 0x888888 }); const instancedMesh = new THREE.InstancedMesh(geometry, material, totalBlockCount); // Set positions for each instance with instancedMesh.setMatrixAt() instancedMesh.castShadow = true; instancedMesh.receiveShadow = true; scene.add(instancedMesh);
3. Memory Leaks Causing Progressive FPS Drops
The fact that FPS gets worse over time points to a memory leak—you're probably leaving old geometry/materials/shadow resources hanging around instead of disposing them properly.
- Fix: Whenever you remove blocks from the scene, make sure to clean up their resources:
// When deleting a block mesh: mesh.removeFromParent(); mesh.geometry.dispose(); mesh.material.dispose(); - Also, check if you're creating new materials/geometries every time you spawn blocks instead of reusing existing ones—reuse is key for keeping memory usage stable.
4. Unoptimized Shadow Camera Frustum
If your shadow camera's frustum is way larger than the area that actually needs shadows, the GPU is wasting cycles rendering empty space into the shadow map, which reduces precision and increases load.
- Fix: Tighten up the shadow camera's bounds to only cover your playable area. For a directional light:
light.shadow.camera.near = 1; light.shadow.camera.far = 50; // Adjust based on your world size light.shadow.camera.left = -25; light.shadow.camera.right = 25; light.shadow.camera.top = 25; light.shadow.camera.bottom = -25; // Optional: Enable camera helper to visualize bounds // const helper = new THREE.CameraHelper(light.shadow.camera); // scene.add(helper);
5. Overly Expensive Shadow Filtering
If you're using THREE.PCFSoftShadowMap for soft shadows, it looks nicer but is more GPU-intensive than simpler options.
- Fix: Test switching to
THREE.PCFShadowMap(a balance between quality and performance) or evenTHREE.BasicShadowMap(fastest, harshest shadows) to see if FPS improves:renderer.shadowMap.type = THREE.PCFShadowMap;
6. Unnecessary Shadow Casting/Receiving
Not every block needs to cast or receive shadows. For example:
- Blocks underground or behind other blocks can have
castShadow = false—their shadows will never be visible. - The ground plane only needs to
receiveShadow, not cast it (unless you have holes in it). - Faraway blocks can have shadow properties disabled entirely, since their shadows are too small to be noticeable.
Try implementing these fixes one at a time to isolate which one is causing the biggest hit. Start with InstancedMesh—that's almost always the biggest win for blocky worlds with lots of repeating geometry.
内容的提问来源于stack exchange,提问作者johnhckuo




