28 Three.js 光照系统配置与阴影渲染

Three.js 光照系统配置与阴影渲染

关联:索引

要解决的问题

本讲定位(与上一讲衔接,避免重复)

章节内容(本讲核心)

环境与先修(默认沿用上一讲工程)

先修要求:

如你需要补装依赖(仅在未安装时执行):

# 安装 Three.js 运行时依赖
npm i three

# 仅在 TypeScript 报 “找不到声明文件” 时再安装(老工程更常见)
npm i -D @types/three

解释:


在 Three.js 里,一个最容易踩的坑是:你换成“受光材质”(Lambert/Phong/Standard),但没有任何光源,结果模型几乎全黑。

你要记住的工程结论:

只复习一句话,避免重复上一讲:

不是所有材质都受光照影响:MeshBasicMaterial 不吃灯光;Lambert/Phong/Standard 会随灯光变化。

快速自检方法:

1) 环境光 AmbientLight:整体补光、降低对比

适用场景(工业常用):

import * as THREE from 'three';

// 环境光:提供全局补光(无方向、无阴影)
const ambient = new THREE.AmbientLight(0xffffff, 0.35);

// 将光源加入场景,才会生效
scene.add(ambient);

解释:

2) 方向光 DirectionalLight:方向性主光,塑造明暗与阴影

适用场景(工业最常用):

import * as THREE from 'three';

// 方向光:模拟高位主光(可产生阴影)
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);

// position 决定光照方向(从该点“照向” target)
dirLight.position.set(5, 8, 3);

// target 指定方向光照向的目标点
dirLight.target.position.set(0, 0, 0);

scene.add(dirLight);
scene.add(dirLight.target);

解释:

3) 点光 PointLight:局部补光、工位灯、指示灯

适用场景(工业常见):

import * as THREE from 'three';

// 点光:局部补光(颜色、强度、影响距离、衰减)
const point = new THREE.PointLight(0xffddaa, 0.8, 10, 2);

// 放在需要照亮的工位附近
point.position.set(1.2, 1.6, 0.5);

scene.add(point);

解释:

你调光不要盲调,按下面顺序走,最快收敛:

  1. 先让“能看清结构”:从 AmbientLight 开始
  1. 再加“主光制造层次”:DirectionalLight 的方向优先于强度
  1. 最后用“点光做局部强调”:PointLight 不要抢主光
  1. 颜色的工程原则:白光为主,暖/冷做辅

没有阴影时,工业场景最常见的观感问题:

工程结论:

你要把这三条当作“阴影能不能出”的门禁条件:

  1. 渲染器开启阴影
    // 全局开启阴影(不开这个,后面所有 cast/receive 都不会生效)
    renderer.shadowMap.enabled = true;
    
    // 柔化阴影边缘(更自然,但更吃性能)
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    

解释:

  1. 光源开启阴影(并且该光源支持阴影)
    // 允许该光源投射阴影(AmbientLight 不支持阴影)
    dirLight.castShadow = true;
    

解释:

  1. 物体设置投射/接收阴影
    // 地面/台面通常接收阴影
    floor.receiveShadow = true;
    
    // 关键物体投射阴影
    part.castShadow = true;
    table.castShadow = true;
    table.receiveShadow = true;
    

解释:

本工坊目标(与“学生任务”一致):

  1. 开启渲染器阴影 + 提供开关
    import * as THREE from 'three';
    import { ref, watch } from 'vue';
    
    // UI 开关:用于对比“开启/关闭阴影”的差异
    const shadowsEnabled = ref(true);
    
    // onMounted 内:创建 renderer 后
    // shadowMap.enabled 必须为 true 才会渲染阴影
    renderer.shadowMap.enabled = shadowsEnabled.value;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    
    watch([shadowsEnabled], () => {
      if (!renderer) return;
      renderer.shadowMap.enabled = shadowsEnabled.value;
    });
    

解释:

  1. 方向光开启阴影,并配置阴影贴图分辨率与相机范围
    // onMounted 内:创建 dirLight 后
    // 允许方向光投射阴影
    dirLight.castShadow = true;
    
    // 阴影贴图分辨率:越大越清晰,但更吃 GPU
    dirLight.shadow.mapSize.set(1024, 1024);
    
    // 阴影相机:决定“哪些区域会生成阴影”
    // 范围太大 -> 阴影更糊;范围太小 -> 阴影被裁切
    dirLight.shadow.camera.near = 0.5;
    dirLight.shadow.camera.far = 30;
    dirLight.shadow.camera.left = -6;
    dirLight.shadow.camera.right = 6;
    dirLight.shadow.camera.top = 6;
    dirLight.shadow.camera.bottom = -6;
    
    // 阴影瑕疵常用修正:bias / normalBias
    // 通常需要结合场景尺度微调
    dirLight.shadow.bias = -0.0002;
    dirLight.shadow.normalBias = 0.02;
    
    // 阴影柔化半径(不是所有类型都明显生效,取决于 shadowMap.type)
    dirLight.shadow.radius = 2;
    

解释:

  1. 物体设置投射/接收阴影(落到你场景里的具体 Mesh 上)

推荐改法:让 buildIndustrialBase 返回对象引用(示例):

type IndustrialBase = {
  floor: THREE.Mesh;
  table: THREE.Mesh;
  part: THREE.Mesh;
};

function buildIndustrialBase(scene: THREE.Scene): IndustrialBase {
  // 通过返回引用,便于第二课时在外部统一开启 cast/receiveShadow
  const floorGeo = new THREE.PlaneGeometry(10, 10);
  const floorMat = new THREE.MeshLambertMaterial({ color: 0x2b2f36 });
  const floor = new THREE.Mesh(floorGeo, floorMat);
  floor.rotation.x = -Math.PI / 2;
  scene.add(floor);

  const tableGeo = new THREE.BoxGeometry(3, 0.3, 1.6);
  const tableMat = new THREE.MeshLambertMaterial({ color: 0x4b5563 });
  const table = new THREE.Mesh(tableGeo, tableMat);
  table.position.set(0, 0.15, 0);
  scene.add(table);

  const partGeo = new THREE.CylinderGeometry(0.18, 0.18, 0.5, 24);
  const partMat = new THREE.MeshPhongMaterial({ color: 0x93c5fd, shininess: 80 });
  const part = new THREE.Mesh(partGeo, partMat);
  part.position.set(0.6, 0.55, 0.2);
  scene.add(part);

  return { floor, table, part };
}

然后在 onMounted 里拿到引用再设置阴影:

const base = buildIndustrialBase(scene);

// 接收阴影(地面/台面)
base.floor.receiveShadow = true;

// 投射阴影(关键物体)
base.table.castShadow = true;
base.table.receiveShadow = true;
base.part.castShadow = true;

解释:

  1. 加一个“阴影调优面板”(最小可用,不引入额外库)
    在模板面板里加两项(示例):

    <label>
      开启阴影
      <input v-model="shadowsEnabled" type="checkbox" />
    </label>
    
    <label>
      阴影分辨率(mapSize)
      <select v-model.number="shadowMapSize">
        <option :value="512">512</option>
        <option :value="1024">1024</option>
        <option :value="2048">2048</option>
      </select>
    </label>
    

对应脚本增加两个状态并同步到方向光:

import { ref, watch } from 'vue';

// UI 选择:阴影贴图分辨率(越大越清晰,但更吃资源)
const shadowMapSize = ref(1024);

watch([shadowMapSize], () => {
  if (!dirLight) return;
  // 运行时调整 shadow map 分辨率(用于对比画质与性能)
  dirLight.shadow.mapSize.set(shadowMapSize.value, shadowMapSize.value);
});

解释:

四、阴影渲染失败的常见问题(速查表)

按“最短路径”排查(建议照顺序来):

  1. 阴影完全没有:
  1. 阴影有但“很糊/很淡”:
  1. 阴影出现“锯齿/抖动/闪烁”:
  1. 阴影“漏光/漂浮/一片脏”:

为之前搭建的工业基础场景添加光照系统,配置环境光 + 方向光组合,开启阴影渲染,调试光照参数,实现真实感基础场景。

  1. 配置环境光与方向光,调整光照强度、颜色:
  1. 开启渲染器、光源、几何体的阴影相关配置:
  1. 调试阴影模糊度、分辨率,优化阴影渲染效果:
  1. 对比无光照、有光照、有阴影的场景差异:

大模型任务(可直接复制的 AI 指令模板)

任务 1:生成工业 3D 场景最优光照配置代码

把下面提示词复制给 AI(你可以替换场景描述):

你是 Three.js 工业可视化工程师。我有一个工业基础场景:地面 Plane、工作台 Box、一个工件 Cylinder,使用 Vue3 + TS + Three.js。请给出“环境光 + 方向光”为主的光照配置代码,并开启阴影渲染(renderer、光源、物体 cast/receive),同时给出阴影画质调优参数(mapSize、shadow camera、bias/normalBias、shadow type)。代码要可直接粘贴进 Vue <script setup lang="ts">,不引入额外库。最后附上自检清单:阴影没出现时如何排查。

期望输出校验点:

任务 2:根据场景需求推荐光照类型组合

提示词:

给我 3 套工业场景的光照组合方案:①室内均匀照明(工厂车间)②有明显方向光(天窗/侧窗)③局部工位重点照明。每套写出需要的光源类型(Ambient/Directional/Point),给出建议强度范围、颜色倾向(中性/偏暖/偏冷),并说明优缺点与适用条件。

期望输出校验点:

任务 3:排查阴影渲染失败的常见问题及解决方案

提示词:

我在 Three.js 里开了阴影但看不到影子。请按最短排查路径列出 10 个常见原因与对应修复方法,要求覆盖:renderer.shadowMap、light.castShadow、object.castShadow/receiveShadow、shadow.camera 范围、mapSize、bias/normalBias、材质是否受光、光源类型是否支持阴影、相机裁剪、性能/显卡限制。输出要像“排障手册”一样可直接照做。

期望输出校验点:

作业

Markdown 与代码自检(发布前已检查)