在 Three.js 的三维世界里,物体就像一个个舞台上的演员,灯光是它们的聚光灯,而阴影则是为这场演出增添真实感的关键道具。没有阴影,再精美的 3D 场景都会像漂浮在虚空里的幽灵,缺少扎根现实的灵魂。今天,我们就来揭秘 Three.js 中阴影映射(Shadow Mapping)这位光影魔术师的神秘配方,看看它是如何用阴影贴图技术,为场景绘制出逼真阴影的。
阴影映射的底层魔法 ------ 一场 "偷看" 游戏
阴影映射的核心思想,就像是一场精心策划的 "偷看" 游戏。想象一下,有一盏灯站在场景中,它想要知道哪些地方被物体挡住了,哪些地方能直接被光照亮。于是,它拿出一个 "小本本"(也就是阴影贴图),站在自己的位置上,把能看到的所有物体的深度信息都记录下来。
这里的深度信息,就好比是物体到灯的距离。离灯近的物体,深度值小;离灯远的物体,深度值大。当这张 "小本本" 记录完所有信息后,真正的魔法时刻就来了。
场景中的每个物体都要接受一次 "灵魂拷问":从灯的视角看,我和记录在 "小本本" 上的深度值相比,谁更近?如果物体发现自己比 "小本本" 上记录的深度值大,那就意味着在灯的视线里,它被前面的物体挡住了,于是它就默默给自己打上 "阴影" 的标签。反之,如果它比 "小本本" 上的深度值小,说明它能被灯直接照到,那就开开心心地享受光照啦!
开启 Three.js 的阴影大门
在 Three.js 中,想要让阴影映射这个魔法生效,我们得先准备好几个关键道具。
1. 激活渲染器的阴影功能
首先,我们要告诉 Three.js 的渲染器:"嘿,我要开启阴影特效!" 就像给相机装上特殊滤镜一样,我们通过以下代码激活渲染器的阴影:
ini
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// 开启阴影映射
renderer.shadowMap.enabled = true;
// 选择阴影映射类型,这里我们选一种平衡质量和性能的类型
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
在这段代码里,renderer.shadowMap.enabled = true;就像是打开了阴影魔法的开关,而renderer.shadowMap.type = THREE.PCFSoftShadowMap;则是在挑选一种合适的阴影风格。不同的阴影映射类型,就像是不同的画笔,画出来的阴影效果各有千秋。
2. 给灯光赋予 "记录员" 的使命
灯光是这场 "偷看" 游戏的主角,我们得给它配备好记录深度信息的工具。以平行光为例,我们这样改造它:
ini
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 10, 10);
// 告诉灯光开启阴影投射
light.castShadow = true;
// 设置灯光的可视范围,就像给灯光带上一个"取景框"
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
light.shadow.camera.top = 10;
light.shadow.camera.bottom = -10;
light.shadow.camera.near = 0.1;
light.shadow.camera.far = 100;
// 精细调整阴影贴图的分辨率,数值越大越清晰,但也越耗性能
light.shadow.mapSize.width = 1024;
light.shadow.mapSize.height = 1024;
scene.add(light);
light.castShadow = true;这句话,就像是给灯光颁发了 "记录员" 的工作证,让它开始执行记录深度信息的任务。后面设置的可视范围和阴影贴图分辨率,则是在为它的工作划定范围、提供工具。
3. 让物体成为 "阴影候选人"
场景中的物体想要拥有阴影,也得做好准备。我们要告诉它们:"该你上场表演,争夺阴影席位啦!"
ini
const boxGeometry = new THREE.BoxGeometry(2, 2, 2);
const boxMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const box = new THREE.Mesh(boxGeometry, boxMaterial);
box.position.set(0, 1, 0);
// 让物体可以投射阴影
box.castShadow = true;
// 让物体可以接收阴影
box.receiveShadow = true;
scene.add(box);
box.castShadow = true;和box.receiveShadow = true;这两行代码,就像是给物体发放了 "阴影投射许可证" 和 "阴影接收许可证",让它们能够参与到这场光影大戏中。
优化阴影效果:让魔法更完美
虽然我们已经让阴影生效了,但有时候效果可能并不理想,比如阴影边缘锯齿明显,或者阴影看起来很生硬。这时候,我们就需要对阴影效果进行优化,让这场光影魔法更加完美。
调整阴影贴图分辨率
前面我们设置了阴影贴图的分辨率,分辨率越高,阴影细节越丰富,但同时也会消耗更多性能。如果你的场景中阴影边缘出现锯齿,不妨试着提高分辨率:
ini
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
不过要小心哦,过高的分辨率可能会让你的程序跑得像蜗牛一样慢,就像给电脑背上了一座大山。
选择合适的阴影映射类型
不同的阴影映射类型对效果和性能的影响也很大。除了前面用到的THREE.PCFSoftShadowMap,还有其他类型可供选择:
- THREE.BasicShadowMap:最基础的阴影映射类型,性能最好,但阴影效果比较生硬,边缘锯齿明显,就像是用蜡笔随便涂出来的阴影。
- THREE.PCFSoftShadowMap:通过一种叫做百分比渐近过滤(PCF)的技术,让阴影边缘变得柔和,效果更自然,就像是用细腻的水彩笔精心绘制的阴影。
- THREE.VSMShadowMap:基于方差阴影贴图(VSM)技术,在处理透明物体的阴影时表现出色,不过计算复杂度较高,对性能要求也更苛刻。
你可以根据场景的具体需求,选择最适合的阴影映射类型,就像是为不同的演出挑选最合适的舞台布景。
结语:让你的 3D 世界鲜活起来
掌握了 Three.js 的阴影映射技术,就像是学会了光影魔术师的神秘咒语,能够为你的 3D 场景注入灵魂,让它从单调的模型变成鲜活的世界。无论是打造梦幻的游戏场景,还是制作精美的产品展示,阴影都能让你的作品更具真实感和吸引力。快去发挥你的创意,用阴影映射创造出令人惊叹的 3D 奇迹吧!