ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【three.js教程】阴影:从“假阴影“到“阴影贴图“一次讲清

【three.js教程】阴影:从“假阴影“到“阴影贴图“一次讲清 Three.js 阴影从假阴影到阴影贴图一次讲清原文出处Three.js Manual – Shadows本文基于官方手册 Shadows 章节整理从最便宜的假阴影讲起再到真正的阴影贴图shadow map怎么调一篇看懂。写在前面前面两篇我们聊了灯光和相机场景有立体感、也能正确看了。但还差最后一部分——阴影。没有阴影物体像是飘在半空怎么看都不真实。three.js 里做阴影有一套完整的机制但坑也不少。这篇先介绍假阴影方案再介绍真正的阴影贴图并说清新手最常卡住的几个点。一、阴影很贵three.js 默认用shadow map阴影贴图做阴影。它的原理一定要记住这句话对于每一盏投影的灯场景里所有投射阴影的物体都要从灯的角度再画一遍。假设你有 20 个物体、5 盏投影灯整个场景就会被画 6 遍5 遍从灯的角度、1 遍正式渲染。更复杂的是如果是点光源投影一盏灯就要画 6 遍立方体六个面各一遍所以现实里常见做法有几种多盏灯但只让其中一盏平行光投影用 lightmap / 环境光遮蔽贴图离线烘焙静态、快但后面单讲直接用假阴影最简单。二、假阴影一个圆片搞定最省事的方案弄一块平面贴一张灰度阴影纹理放在物体正下方。像《动物森友会》《纪念碑谷》里角色脚下的圆影就是这么干的——便宜又有效。做法是给每个球建一个Object3D作为底座球和阴影都挂它下面这样移动底座两者一起动constbasenewTHREE.Object3D();scene.add(base);constshadowMatnewTHREE.MeshBasicMaterial({map:shadowTexture,transparent:true,// 能透出地面depthWrite:false,// 阴影之间互不打架不用排序});constshadowMeshnewTHREE.Mesh(shadowGeo,shadowMat);shadowMesh.position.y0.001;// 略微抬高避免和地面 z-fightingshadowMesh.rotation.xMath.PI*-.5;constshadowSizesphereRadius*4;shadowMesh.scale.set(shadowSize,shadowSize,shadowSize);base.add(shadowMesh);动画时让球上下弹跳同时把阴影的透明度跟着调淡——球越高影子越淡constyOffMath.abs(Math.sin(time*2ndx));sphereMesh.position.yyTHREE.MathUtils.lerp(-2,2,yOff);shadowMesh.material.opacityTHREE.MathUtils.lerp(1,.25,yOff);两个小坑depthWrite: false是让多个阴影互不遮挡position.y 0.001是防止和地面深度冲突闪烁z-fighting。三、真正的阴影贴图当你需要真实阴影时走这三步1. 渲染器开阴影renderer.shadowMap.enabledtrue;2. 灯光开启投影constlightnewTHREE.DirectionalLight(color,intensity);light.castShadowtrue;3. 每个物体声明投影接收地面只接收、不投射反正底下看不见立方体、球两者都要mesh.receiveShadowtrue;// 地面mesh.castShadowtrue;// 立方体/球mesh.receiveShadowtrue;跑起来——咦怎么阴影缺了一块四、为什么阴影缺一块调阴影相机原因阴影贴图是从灯的角度渲染的灯上挂着一台阴影相机它框定的范围之外就不产生阴影。上面缺一块是因为这个范围太小了。把灯的阴影相机可视化出来就一目了然constcameraHelpernewTHREE.CameraHelper(light.shadow.camera);scene.add(cameraHelper);平行光用的是正交相机做阴影相机回顾相机那篇靠left/right/top/bottom/near/far/zoom定框。手册用一个DimensionGUIHelper同时设left/right为width、bottom/top为height再配之前写过的MinMaxGUIHelper调near/farfunctionupdateCamera(){light.target.updateMatrixWorld();helper.update();light.shadow.camera.updateProjectionMatrix();// 改了阴影相机参数必须调这个cameraHelper.update();}updateCamera();把width、height调到约 30阴影就完整了。但别贪大有人想那我设成 100 全包住不香吗——结果阴影变得马赛克一样糊。因为阴影贴图本质是一张纹理框得越大这张纹理被拉伸得越厉害分辨率就越低。五、阴影贴图分辨率mapSize阴影贴图是张有尺寸的纹理默认512×512。越大越清晰但越吃内存越慢。用这两个属性调light.shadow.mapSize.width1024;light.shadow.mapSize.height1024;实战经验阴影清晰度的两个杠杆是——① 阴影相机框尽量小刚好罩住场景② mapSize 尽量小但够用。另外注意每台机器的纹理上限renderer.capabilities.maxTextureSize。六、聚光灯阴影框由灯自己定换成SpotLight它的阴影相机变成透视相机。而且大部分参数你改不了——它的fov直接绑死在聚光灯的angle上aspect由贴图尺寸自动算。所以调聚光灯阴影本质就是调angle和penumbra见灯光那篇。angle开太大阴影贴图被摊到很大一片区域分辨率照样崩。七、点光源阴影最慢的一种PointLight向四面八方发光它的阴影相当于6 个聚光灯阴影立方体六个面各一遍。所以只有near和far两个参数可调而且整场景要画 6 遍最慢。常见演示是给场景套个盒子side: THREE.BackSide渲染内侧让阴影能投到墙和天花板上constcubeGeonewTHREE.BoxGeometry(cubeSize,cubeSize,cubeSize);constcubeMatnewTHREE.MeshPhongMaterial({color:#CCC,side:THREE.BackSide,});constmeshnewTHREE.Mesh(cubeGeo,cubeMat);mesh.receiveShadowtrue;mesh.position.set(0,cubeSize/2-0.1,0);// 略低于地面避免 z-fightingscene.add(mesh);拖动点光源位置能看到阴影落在四周墙上。near以内不投影、far以外永远在阴影里。总结怎么选阴影方案方案真实度性能适用假阴影圆片低极快移动端、卡通风格、俯视游戏平行光阴影贴图高中室外主光源聚光灯阴影贴图高中舞台、手电点光源阴影贴图高慢6 遍室内灯泡谨慎用记住四句话① 投影前renderer.shadowMap.enabled true 灯castShadow 物体cast/receiveShadow② 阴影缺一块是阴影相机框太小③ 框别太大、mapSize 别太高否则糊或卡④ 能少用几盏投影灯就少用。延伸阅读Renderer.shadowMapLightShadowDirectionalLightShadowPointLightShadow回顾Lights灯光 · Cameras相机
返回列表