材质与光照
材质描述表面如何与光交互,灯光提供光。地形有没有立体感、数据分层的颜色准不准、 模型会不会发灰,基本都由这两组参数决定。
材质从简单到复杂
| 材质 | 受光 | 适用场景 | 代价 |
|---|---|---|---|
MeshBasicMaterial |
否,颜色固定 | 标注点、辅助线、背景面、不需要立体感的图层 | 最低 |
MeshLambertMaterial |
漫反射 | 大范围地形、粗略建筑体块 | 低 |
MeshPhongMaterial |
漫反射 + 高光 | 需要明确高光但不在意物理正确性的物体 | 中 |
MeshStandardMaterial |
PBR | 默认选择:建筑、地形、设备模型 | 中 |
MeshPhysicalMaterial |
PBR + 清漆、透射、折射 | 玻璃、水面、需要清漆层的展示模型 | 较高 |
PointsMaterial / LineBasicMaterial |
点与线专用 | 点云、轨迹、经纬网 | 低 |
一个常见误区是给地形用 MeshBasicMaterial:颜色是准的,但没有明暗,
起伏完全看不出来。地形应当用受光材质,再配合方向光。
PBR 的两个核心参数
MeshStandardMaterial 用 roughness 与 metalness
描述表面。前者控制反射的模糊程度,后者控制有多少反射被当作金属反射。
非金属物体的 metalness 通常接近 0;把建筑写成 1,会得到一块像金属板的立面。
const material = new THREE.MeshStandardMaterial({
color: 0x9aa7b4,
roughness: 0.85, // 粗糙,接近混凝土
metalness: 0.05, // 非金属
map: colorTexture, // 基础色贴图
normalMap: normalTexture,
side: THREE.FrontSide
});
金属材质需要环境贴图才有反射内容。没有环境贴图时,metalness 越高画面越暗,
因为金属没有漫反射,而可反射的环境是空的。展示类场景可以用
PMREMGenerator 从一张 HDR 或程序生成的天空生成环境贴图。
光源类型
| 光源 | 行为 | 典型用途 |
|---|---|---|
AmbientLight | 均匀照亮所有表面,无方向 | 补暗部,避免阴影死黑 |
HemisphereLight | 天空色与地面色从上到下渐变 | 室外场景的基础环境光 |
DirectionalLight | 平行光,只有方向没有位置衰减 | 太阳光、地形主光 |
PointLight | 点光源,随距离衰减 | 路灯、室内补光 |
SpotLight | 锥形光,有角度与衰减 | 聚光、夜间照明 |
RectAreaLight | 面光源,只影响 Standard/Physical | 柔和的展示照明 |
从 r155 起,点光源与聚光灯的 intensity 采用物理单位,默认
decay = 2,亮度随距离平方衰减。同样是「1」,平行光看起来正常,
点光源却几乎看不见,这是单位差异,不是代码错了。室内点光源常见取值在几十到几百之间。
一组能用的室外布光
// 天空与地面反射,提供基础亮度
scene.add(new THREE.HemisphereLight(0xcfe4ff, 0x1a222c, 1.0));
// 主光:太阳,负责明暗关系与阴影
const sun = new THREE.DirectionalLight(0xfff0d8, 2.4);
sun.position.set(8, 14, 6);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048);
sun.shadow.camera.left = -30;
sun.shadow.camera.right = 30;
sun.shadow.camera.top = 30;
sun.shadow.camera.bottom = -30;
sun.shadow.camera.near = 1;
sun.shadow.camera.far = 80;
sun.shadow.bias = -0.0005;
scene.add(sun);
// 补光:压住主光背后的死黑,强度要低
const fill = new THREE.DirectionalLight(0x8fb4ff, 0.6);
fill.position.set(-10, 6, -8);
scene.add(fill);
阴影的代价与边界
阴影会把场景从主光视角再渲染一遍到深度纹理。对象越多、贴图越大,开销越高。 平行光的阴影相机范围要贴着实际场景设置,范围开得越大,同样分辨率下每个像素覆盖的地面越宽, 阴影边缘越糊,还会出现锯齿。
静态场景可以关闭阴影自动更新:renderer.shadowMap.autoUpdate = false,
需要时再置为 true 并渲染一帧。城市级三维场景里,这能省下可观的每帧开销。
阴影不出现
依次检查:渲染器 shadowMap.enabled 是否为 true;投光物体 castShadow
是否为 true;受光物体 receiveShadow 是否为 true;光源的 castShadow
是否开启;阴影相机的范围是否覆盖了物体。任意一项没开,阴影都不会出现。
颜色空间
颜色贴图要标成 sRGB,数据贴图(法线、粗糙度、金属度、位移)保持线性。
把法线贴图也标成 sRGB,光照方向会被错误解释,表面细节看起来发脏。
渲染器的输出统一为 SRGBColorSpace,这条链路才闭合。
先定光照,再调颜色。同一份颜色在不同光照下完全不是同一种观感。 做专题图时,最好固定一套主光方向与强度,再按数据的视觉层级调材质颜色, 否则换一个场景就要重新配色。
经纬志(2026)。《材质与光照》。https://www.jingweizhi.com/three/tutorial/materials-lights.html(访问日期 2026-11-03)。