如何在Three.js(R3F)中让平行光照亮球体的完整半面?
问题描述
需要用两盏平行光照亮球体:一盏置于前方模拟太阳光照亮正面,另一盏置于后方模拟城市灯光照亮背面,计划通过图层控制光照对材质的作用,但目前两盏平行光无法分别照亮球体对应的完整半面。已尝试调整光照强度、缩放比例、光照距离等参数,问题仍未解决。
简化后的代码示例:
export default function Earth(): JSX.Element { const earthMapRef = useRef<any>(); const frontLightRef = useRef<any>(); const backLightRef = useRef<any>(); useHelper(frontLightRef, DirectionalLightHelper, 1, 'red'); useHelper(backLightRef, DirectionalLightHelper, 1, 'red'); return ( <Fragment> <PivotControls anchor={[0, 0, 0]} depthTest={false} scale={0.5} visible={true}> <mesh ref={earthMapRef} position={[0, 0, 0]}> <sphereGeometry args={[1, 64, 64]} /> <meshStandardMaterial /> </mesh> </PivotControls> <directionalLight ref={frontLightRef} position={[0, 0, 5]} scale={[3, 3, 3]} intensity={Math.PI} color="#FFFFFF" /> <directionalLight ref={backLightRef} position={[0, 0, -5]} scale={[3, 3, 3]} intensity={Math.PI} color="#FFFFFF" /> </Fragment> ); }
效果截图:
解决方案
问题根源
- 平行光
scale属性无效:DirectionalLight是无限远光源,不存在缩放属性,设置后不会改变光照范围。 - 未配置图层隔离:你提到用图层控制光照,但代码中未给灯光和球体分配对应图层,导致两盏灯默认作用于所有图层(图层0),同时照亮整个球体,无法实现半面独立光照。
- 单Mesh无法区分图层接收:单个球体的正反两面属于同一图层,无法让不同灯光仅照射其中一面。
解决方法
- 拆分球体为正反两个Mesh:分别创建仅显示正面和背面的球体,分配到不同图层:
- 正面Mesh设置
side: FrontSide,加入图层1 - 背面Mesh设置
side: BackSide,加入图层2
- 正面Mesh设置
- 给灯光分配对应图层:让前灯仅作用于图层1,后灯仅作用于图层2
- 移除无效参数:删除平行光的
scale属性
修改后的代码
import { Fragment, useRef } from 'react'; import { PivotControls, useHelper, mesh, sphereGeometry, meshStandardMaterial, directionalLight } from '@react-three/fiber'; import { DirectionalLightHelper } from 'three'; export default function Earth(): JSX.Element { const frontSphereRef = useRef<any>(); const backSphereRef = useRef<any>(); const frontLightRef = useRef<any>(); const backLightRef = useRef<any>(); useHelper(frontLightRef, DirectionalLightHelper, 1, 'red'); useHelper(backLightRef, DirectionalLightHelper, 1, 'blue'); // 换颜色方便区分灯光 return ( <Fragment> <PivotControls anchor={[0, 0, 0]} depthTest={false} scale={0.5} visible={true}> {/* 正面球体:仅显示正面,归属于图层1 */} <mesh ref={frontSphereRef} position={[0, 0, 0]} layers={1}> <sphereGeometry args={[1, 64, 64]} /> <meshStandardMaterial side={0} /> {/* FrontSide 对应数值0 */} </mesh> {/* 背面球体:仅显示背面,归属于图层2 */} <mesh ref={backSphereRef} position={[0, 0, 0]} layers={2}> <sphereGeometry args={[1, 64, 64]} /> <meshStandardMaterial side={1} /> {/* BackSide 对应数值1 */} </mesh> </PivotControls> {/* 前方平行光:仅作用于图层1 */} <directionalLight ref={frontLightRef} position={[0, 0, 5]} intensity={Math.PI} color="#FFFFFF" layers={1} /> {/* 后方平行光:仅作用于图层2 */} <directionalLight ref={backLightRef} position={[0, 0, -5]} intensity={Math.PI} color="#FFFFFF" layers={2} /> </Fragment> ); }
补充说明
- 若相机曾修改过图层设置,需确保开启图层1和2的可见性:
camera.layers.enable(1)、camera.layers.enable(2)(默认相机显示所有图层,一般无需额外设置) - 若想避免创建两个Mesh,可以通过自定义着色器修改材质,让正反两面分别接收不同图层的灯光,但上述方法更简单易维护
内容的提问来源于stack exchange,提问作者Guillaume
相关产品推荐
相关产品推荐

