Three.js中仅接收光照的透明材质实现及Mapbox自定义图层聚光灯模拟技术咨询
嘿,这个需求我刚好在类似的Mapbox+Three.js项目里折腾过,给你捋捋具体的实现思路:
透明平面接收聚光灯光照的解决方案
一、原生Three.js有没有现成的材质?
原生Three.js里没有直接满足「仅接收光照、其余状态完全透明」的内置材质——ShadowMaterial其实只负责处理阴影接收,不会响应聚光灯的直接漫反射/高光。所以咱们得自己动手写个自定义材质。
二、用自定义ShaderMaterial实现核心效果
核心逻辑很简单:在片元着色器里只计算聚光灯对当前片段的光照贡献,当光照强度大于阈值时才显示颜色,否则直接丢弃像素实现透明。下面是适配Mapbox场景的简化示例:
1. 自定义材质代码
// 顶点着色器:复用Three.js默认的顶点变换与法线传递逻辑 const vertexShader = ` varying vec3 vNormal; varying vec3 vPosition; void main() { vNormal = normalize(normalMatrix * normal); vPosition = vec3(modelViewMatrix * vec4(position, 1.0)); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `; // 片元着色器:只响应聚光灯光照,无光照时透明 const fragmentShader = ` uniform vec3 lightColor; uniform vec3 lightPosition; uniform float lightAngle; uniform float lightPenumbra; uniform float lightIntensity; varying vec3 vNormal; varying vec3 vPosition; void main() { // 计算聚光灯方向与顶点法线的夹角 vec3 lightDir = normalize(lightPosition - vPosition); float angle = acos(dot(-lightDir, normalize(vNormal))); // 计算聚光灯的衰减(包含角度范围和半影过渡) float spotAttenuation = smoothstep(lightAngle + lightPenumbra, lightAngle, angle); // 计算漫反射光照强度 float diffuse = max(dot(vNormal, lightDir), 0.0) * spotAttenuation * lightIntensity; // 光照强度过低时直接丢弃像素,实现透明 if (diffuse < 0.01) { discard; } // 输出光照颜色,透明度随光照强度变化 gl_FragColor = vec4(lightColor * diffuse, diffuse); } `; // 创建自定义材质 const spotlightReceiverMaterial = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms: { lightColor: { value: new THREE.Color(0xffffbb) }, // 聚光灯颜色 lightPosition: { value: new THREE.Vector3(0, 0, 10) }, // 聚光灯位置 lightAngle: { value: THREE.MathUtils.degToRad(30) }, // 聚光灯照射角度 lightPenumbra: { value: THREE.MathUtils.degToRad(10) }, // 半影过渡角度 lightIntensity: { value: 1.5 } // 光照强度 }, transparent: true, depthWrite: false, // 避免遮挡地图图层 side: THREE.DoubleSide // 平面双面都能接收光照,按需调整 });
2. Mapbox自定义图层的适配要点
在Mapbox GL的Three.js自定义图层里使用时,还要注意这些细节:
- 确保透明平面(比如
THREE.PlaneGeometry)的坐标和Mapbox的地理投影坐标系对齐,需要把Three.js的局部坐标转换为Mapbox的经纬度投影坐标。 - 如果聚光灯需要跟随地图视角/交互移动,要在图层的
render回调里实时更新lightPosition这个uniform的值。 - 调整图层的
zIndex,确保聚光效果显示在地图之上但不遮挡其他交互元素。
三、备选方案:静态光照贴图
如果你的聚光灯位置固定,也可以提前渲染一张光照贴图,用MeshBasicMaterial结合透明贴图实现——不过这种方法灵活性差,只适合静态场景,不如自定义ShaderMaterial适配动态聚光灯需求。
内容的提问来源于stack exchange,提问作者daumann
相关产品推荐
相关产品推荐

