
本文详解 Three.js 中阴影无法渲染的三大典型问题:全局阴影开关未启用、receiveShadow 拼写错误、以及语法错误导致脚本中断,并提供可直接运行的修复代码与关键配置要点。
本文详解 three.js 中阴影无法渲染的三大典型问题:全局阴影开关未启用、`receiveshadow` 拼写错误、以及语法错误导致脚本中断,并提供可直接运行的修复代码与关键配置要点。
在 Three.js 中实现真实感阴影看似简单,但常因细微疏漏导致 castShadow 和 receiveShadow 完全失效——表面配置看似完整,实际阴影却完全不可见。这并非引擎缺陷,而是由几个关键前提未满足所致。以下将逐项剖析并给出可落地的解决方案。
✅ 必须满足的三大前提条件
Three.js 的阴影系统是显式启用、逐对象控制、全局依赖的,缺一不可:
-
全局启用阴影映射(Shadow map)
即使所有对象都设置了 castShadow = true 和 receiveShadow = true,若未开启渲染器的阴影功能,一切配置均无效:renderer.shadowMap.enabled = true; // ✅ 必须放在 renderer 初始化之后、渲染循环之前 -
正确拼写 receiveShadow(非 recieveShadow)
这是最常见的低级错误。JavaScript 不会报错(仅静默忽略),但 ground.recieveShadow = true 实际创建了一个无意义的自定义属性,而真正的 receiveShadow 仍为 false:// ❌ 错误拼写(无效) ground.recieveShadow = true; // ✅ 正确拼写(关键!) ground.receiveShadow = true; -
确保脚本无语法/运行时错误
原代码末尾存在致命笔误:scene.add(ground)j —— 多余的 j 会导致整个 <script type="module"> 执行中断,后续 renderer.shadowMap.enabled = true 根本不会执行。务必检查控制台报错(如 Uncaught SyntaxError),并清理非法字符。</script>
? 完整修复后代码(已验证)
<style> body { margin: 0; overflow: hidden; } </style> <script async src="https://unpkg.com/es-module-shims@1.10.0/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.162.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.162.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 场景、相机、渲染器 const scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 8); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; // ✅ 全局启用阴影(关键位置!) renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 可选:更柔和的阴影 document.body.appendChild(renderer.domElement); // 控制器 const controls = new OrbitControls(camera, renderer.domElement); // 立方体(投射阴影) const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.castShadow = true; // ✅ 允许投射 cube.receiveShadow = false; // 立方体自身不接收阴影(通常如此) scene.add(cube); // 地面(接收阴影) const groundGeometry = new THREE.PlaneGeometry(10, 10); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff, side: THREE.DoubleSide }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; // 旋转至水平 ground.position.y = -3; ground.receiveShadow = true; // ✅ 正确拼写! scene.add(ground); // 平行光(投射阴影) const light = new THREE.DirectionalLight(0xffffff, 1); light.castShadow = true; light.position.set(5, 10, 7); // ? 关键:配置阴影贴图参数(否则默认分辨率极低,阴影模糊或消失) light.shadow.mapSize.width = 1024; light.shadow.mapSize.height = 1024; light.shadow.camera.near = 0.5; light.shadow.camera.far = 50; light.shadow.camera.left = -10; light.shadow.camera.right = 10; light.shadow.camera.top = 10; light.shadow.camera.bottom = -10; scene.add(light); // 环境光(保证基础可见性) scene.add(new THREE.AmbientLight(0x404040, 2)); // 动画循环 function animate() { requestAnimationFrame(animate); cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate(); // 响应窗口大小变化 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script>
⚠ 注意事项与进阶提示
- 阴影贴图分辨率:light.shadow.mapSize 默认为 512×512,复杂场景建议设为 1024×1024 或更高,否则阴影边缘严重锯齿或丢失细节。
- 阴影相机范围:light.shadow.camera.* 参数必须包围所有需要投射/接收阴影的对象。范围过小会导致阴影被裁剪;过大则降低分辨率精度。可临时添加 light.shadow.cameraHelper = new THREE.CameraHelper(light.shadow.camera) 并 scene.add(light.shadow.cameraHelper) 可视化调试。
- 材质兼容性:MeshBasicMaterial 和 MeshDepthMaterial 不支持阴影。确保使用 MeshStandardMaterial、MeshPhongMaterial 等支持光照计算的材质。
- 性能权衡:阴影计算开销较大。生产环境可对静态物体预烘焙阴影,或动态物体启用 shadowMap.autoUpdate = false 并手动调用 renderer.shadowMap.needsUpdate = true。
遵循以上三原则并检查拼写与语法,Three.js 阴影即可稳定呈现。记住:shadowMap.enabled 是总开关,castShadow/receiveShadow 是个体权限,而正确的单词拼写与无错误脚本是这一切生效的基石。