如何为ShaderMaterial应用HemisphereLight实现光照效果?
解决ShaderMaterial物体不受HemisphereLight影响的问题
你的问题核心在于:自定义ShaderMaterial不会像Three.js内置材质那样自动处理光照逻辑,必须手动在着色器中实现光照计算,同时开启材质的光照支持开关。以下是具体修改步骤和完整代码:
关键修改点
- 开启ShaderMaterial的
lights: true选项,让Three.js自动注入光照相关的uniforms - 在顶点着色器中计算并传递法向量、视图空间位置给片元着色器(光照计算必需数据)
- 在片元着色器中实现HemisphereLight的光照计算逻辑
修改后的完整代码
<script src="https://threejs.org/build/three.js"></script> <script type="module"> function main() { var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera( 200, window.innerWidth/window.innerHeight, 0.1, 1000 ); var renderer = new THREE.WebGLRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); // 修改后的顶点着色器:添加光照所需的变量和计算 var vShader = ` precision mediump float; uniform float uTime; // Three.js自动提供的内置uniforms uniform mat4 modelMatrix; uniform mat4 modelViewMatrix; uniform mat3 normalMatrix; // 传递给片元着色器的变量 varying vec3 vNormal; varying vec3 vViewPosition; void main() { // 保留你的顶点位移逻辑 float z = 0.1*cos(uTime) * cos(position.y*40. - uTime) * position.y*sin(log(position.y))*0.5 + 0.5; vec3 modifiedPosition = vec3(position.x, position.y, z); // 计算光照所需的法向量和视图空间位置 vNormal = normalize(normalMatrix * normal); vec4 modelViewPos = modelViewMatrix * vec4(modifiedPosition, 1.0); vViewPosition = -modelViewPos.xyz; gl_Position = projectionMatrix * modelViewPos; } `; // 修改后的片元着色器:实现HemisphereLight光照计算 var fShader = ` precision mediump float; uniform vec2 uRes; // Three.js自动注入的HemisphereLight相关uniforms uniform vec3 hemisphereLightSkyColor; uniform vec3 hemisphereLightGroundColor; uniform float hemisphereLightIntensity; // 从顶点着色器传递过来的变量 varying vec3 vNormal; varying vec3 vViewPosition; void main() { // 基础UV颜色 vec2 uv = 1.5*gl_FragCoord.xy/uRes; vec3 baseColor = vec3(uv.x, uv.y, 0.0); // HemisphereLight光照计算 vec3 normal = normalize(vNormal); float dotNL = dot(normal, vec3(0.0, 1.0, 0.0)); // 半球光的方向是向上的 vec3 lightColor = mix(hemisphereLightGroundColor, hemisphereLightSkyColor, 0.5 * dotNL + 0.5); vec3 finalColor = baseColor * lightColor * hemisphereLightIntensity; gl_FragColor = vec4(finalColor, 1.0); } `; var planeG = new THREE.PlaneGeometry(25, 60, 20, 20); var planeM = new THREE.ShaderMaterial({ uniforms: { uTime: { type:"f", value:0.0 }, uRes: { type:"v2", value:new THREE.Vector2(window.innerWidth, window.innerHeight) } }, lights: true, // 开启光照支持,必须打开! vertexShader: vShader, fragmentShader: fShader, side: THREE.DoubleSide, }); var plane = new THREE.Mesh( planeG, planeM ); var light = new THREE.HemisphereLight(0xffffff, 0x000000, 2); scene.add(light); scene.add(plane); plane.rotateZ(Math.PI/2); camera.position.z = 4; scene.background = new THREE.Color(0xffffff); var render = function (time) { requestAnimationFrame( render ); plane.material.uniforms.uTime.value = time/100; renderer.render(scene, camera); }; render(); }; main(); </script>
额外说明
- 如果你使用其他光源(比如DirectionalLight、PointLight),需要在片元着色器中对应实现该光源的光照计算逻辑
- Three.js的内置uniforms和变量可参考官方文档,比如
normalMatrix用于将法向量转换到视图空间,modelViewMatrix用于将顶点转换到视图空间 - 已修正你代码中
uRes变量的类型错误,从f改为v2以匹配Vector2类型
内容的提问来源于stack exchange,提问作者Jonas
相关产品推荐
相关产品推荐

