A-Frame中如何为受光照物体的不同区域设置特殊纹理?
当然可以实现这个效果!核心思路是通过自定义GLSL shader,根据物体表面的光照强度来混合日间和夜间两张纹理,而且完全可以做到适配不同光源类型和非完美几何体。我给你详细拆解一下实现方案:
核心思路
我们需要在fragment shader里完成两个关键步骤:
- 计算物体每个表面点的光照强度(0到1之间,0代表完全没被照亮,1代表完全照亮)
- 用这个光照强度作为混合因子,在日间纹理和夜间纹理之间做平滑过渡
这个方法不依赖物体是完美球体,只要模型的法线数据正确,就能适配任何几何体;同时通过传递光源的参数(方向、位置、类型),可以支持平行光、点光源等多种光源类型。
自定义Shader实现步骤
在A-Frame中,我们可以通过shader: custom来使用自定义GLSL shader,需要分别编写vertex shader和fragment shader:
1. Vertex Shader
顶点着色器的作用是把模型空间的法线、顶点位置转换到世界空间,传递给片元着色器,这样才能正确计算光照:
varying vec3 vNormal; varying vec2 vUv; void main() { vNormal = normalize(mat3(modelMatrix) * normal); vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
2. Fragment Shader(平行光版本)
片元着色器里计算光照强度,然后混合纹理。这里先以模拟太阳光的平行光为例:
uniform sampler2D dayMap; uniform sampler2D nightMap; uniform vec3 lightDirection; // 平行光方向(世界空间) uniform float ambientIntensity; // 环境光强度,避免完全变黑 varying vec3 vNormal; varying vec2 vUv; void main() { // 计算漫反射强度 float diffuse = max(dot(normalize(vNormal), normalize(-lightDirection)), 0.0); // 结合环境光,得到最终光照强度 float lightIntensity = ambientIntensity + (1.0 - ambientIntensity) * diffuse; // 采样两张纹理 vec4 dayColor = texture2D(dayMap, vUv); vec4 nightColor = texture2D(nightMap, vUv); // 混合纹理:光照强的地方显示日间纹理,弱的地方显示夜间纹理 gl_FragColor = mix(nightColor, dayColor, lightIntensity); }
3. 适配点光源
如果要支持点光源,只需要修改光照强度的计算逻辑,加入距离衰减:
// 新增uniform参数 uniform vec3 lightPosition; // 点光源位置(世界空间) uniform float lightRadius; // 点光源影响半径 // 替换光照强度计算部分 vec3 lightVec = lightPosition - vWorldPosition; float distance = length(lightVec); float attenuation = 1.0 - clamp(distance / lightRadius, 0.0, 1.0); float diffuse = max(dot(normalize(vNormal), normalize(lightVec)), 0.0); float lightIntensity = ambientIntensity + (1.0 - ambientIntensity) * diffuse * attenuation;
你甚至可以加入一个uniform变量来指定光源类型,在shader里做分支判断,实现同时支持多种光源的灵活效果。
完整A-Frame示例代码
下面是一个可直接运行的地球昼夜效果示例:
<!DOCTYPE html> <html> <head> <script src="https://aframe.io/releases/1.4.2/aframe.min.js"></script> <script> AFRAME.registerShader('dayNight', { schema: { dayMap: { type: 'map' }, nightMap: { type: 'map' }, lightDirection: { type: 'vec3', default: [1, 0, 0] }, ambientIntensity: { type: 'float', default: 0.1 } }, vertexShader: ` varying vec3 vNormal; varying vec2 vUv; void main() { vNormal = normalize(mat3(modelMatrix) * normal); vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D dayMap; uniform sampler2D nightMap; uniform vec3 lightDirection; uniform float ambientIntensity; varying vec3 vNormal; varying vec2 vUv; void main() { float diffuse = max(dot(normalize(vNormal), normalize(-lightDirection)), 0.0); float lightIntensity = ambientIntensity + (1.0 - ambientIntensity) * diffuse; vec4 dayColor = texture2D(dayMap, vUv); vec4 nightColor = texture2D(nightMap, vUv); gl_FragColor = mix(nightColor, dayColor, lightIntensity); } ` }); </script> </head> <body> <a-scene> <!-- 平行光模拟太阳光 --> <a-light type="directional" position="5 0 0" intensity="1"></a-light> <!-- 地球模型,使用自定义昼夜shader --> <a-entity geometry="primitive: sphere; radius: 2" material="shader: dayNight; dayMap: url(https://example.com/earth-day.jpg); nightMap: url(https://example.com/earth-night.jpg); lightDirection: 5 0 0;" animation="property: rotation; to: 0 360 0; dur: 100000; loop: true"> </a-entity> <a-camera position="0 0 5"></a-camera> </a-scene> </body> </html>
灵活适配的扩展技巧
- 非完美几何体适配:只要你的模型导出时包含正确的法线数据,这个shader就能正常工作——因为我们用的是模型空间法线转换到世界空间后的结果,和几何体形状无关。
- 多光源支持:如果场景里有多个光源,可以在shader里计算每个光源的光照强度,然后取最大值或者累加(注意不要超过1.0)。
- 平滑过渡调整:可以给
lightIntensity加入smoothstep函数,让昼夜过渡更自然:lightIntensity = smoothstep(0.2, 0.8, lightIntensity); - 动态光源:如果光源位置/方向会变化,只需要在JavaScript里实时更新material的
lightDirection或lightPosition属性即可。
内容的提问来源于stack exchange,提问作者Niko Lang
相关产品推荐
相关产品推荐

