Three.js中如何将方向光的作用范围限制在玩家周边的有限区域?
Three.js 限制方向光作用范围的实现方案
方案1:自定义着色器修改方向光衰减(效果最优)
直接修改材质的着色器逻辑,保留方向光的平行光照特性和阴影效果,同时精准控制作用范围,适配所有几何体类型,不会出现面数不足导致的光斑异常问题:
- 给所有需要接收该方向光的材质添加
onBeforeCompile钩子,在片元着色器的光照计算环节插入距离判断逻辑 - 椭圆形范围只需将点到玩家的欧式距离替换为椭球距离公式即可,示例代码如下:
// 先在uniform里传入玩家位置和范围参数 material.onBeforeCompile = (shader) => { shader.uniforms.playerPos = { value: playerMesh.position }; shader.uniforms.lightRange = { value: 20 }; // 光照范围半径 shader.uniforms.ellipseRatio = { value: 1.5 }; // 椭圆X轴和Z轴的长度比例 shader.fragmentShader = ` uniform vec3 playerPos; uniform float lightRange; uniform float ellipseRatio; ` + shader.fragmentShader; // 插入范围衰减逻辑到光照计算前 shader.fragmentShader = shader.fragmentShader.replace( '#include <lights_fragment_begin>', ` #include <lights_fragment_begin> // 计算当前片元到玩家的平面椭球距离(俯视视角无需计算Y轴) float dx = abs(vWorldPosition.x - playerPos.x) * ellipseRatio; float dz = abs(vWorldPosition.z - playerPos.z); float dist = sqrt(dx * dx + dz * dz); // 超出范围的方向光贡献置0,加渐变过渡避免边缘割裂 float rangeAtten = 1.0 - smoothstep(lightRange * 0.8, lightRange, dist); directionalLightColor *= rangeAtten; ` ); };
方案2:阴影视锥体裁剪+范围遮罩(改造成本最低)
如果不想修改着色器,可以用原生Three.js接口实现,性能开销更低:
- 保持方向光的配置不变,开启阴影后每帧同步更新方向光的阴影相机视锥体,让视锥体刚好框住玩家周围需要光照的范围,超出视锥体的物体不会接收该方向光的照射和阴影
- 要实现椭圆形光照范围,只需要在地面材质上叠加一张随玩家移动的椭圆形渐变alpha纹理,或者用全屏后期处理遮罩,把范围外的光照效果抹去即可
方案3:聚光灯优化适配(性能最优)
针对你之前使用聚光灯遇到的问题,做少量优化即可达到需求:
- 给聚光灯的
map参数赋值一张椭圆形的渐变透明度纹理,这样不管接收面的几何体面数多少,都能输出均匀平滑的椭圆光斑,不会出现面数不足导致的光斑异常 - 调高聚光灯的阴影分辨率、调整阴影偏置参数,就能拿到接近方向光的高质量阴影效果,整体性能开销比前两个方案更低
注意事项
- 俯视视角下无需计算Y轴的距离,仅判断X、Z轴的平面距离就能满足需求,进一步降低性能开销
- 边缘使用
smoothstep做渐变过渡,可以避免光照范围边缘出现明显的锯齿和割裂感 - 如果项目使用了后期处理管线,可以直接在光照合成阶段统一做范围裁剪,不需要逐个修改材质的着色器,适配性更高
内容的提问来源于stack exchange,提问作者uIM7AI9S
相关产品推荐
相关产品推荐

