jk's notes
  • 快速笔记

快速笔记

前言

本书使用的 Three.JS 版本为 r147. 需要注意的是, 安装不同的版本, 使用 TS 的时候 @types/three 的版本也需要对应.

其他没什么重要的, 然后介绍了每一章的主要内容.

ch01 使用 Three.js 创建第一个3D场景

快速介绍了 three.js 的背景, 以及常见的应用场景. 并给出了即将介绍的三个案例:

  • 官网的一个示例
  • 几何图形 (这里案例会在本章详细介绍)
  • 加载现有模型

使用 three.js 可以实现:

  • 在浏览器中创建并渲染 3D 几何体
  • 创建 3D 场景的动画
  • 在对象上使用纹理与材质
  • 使用不同的灯光来展示场景
  • 使用其他程序导出的 3D 模型
  • 添加后期特效
  • 使用不同着色器 (shader)
  • 粒子云
  • VR (virtual reality) 与 AR (augmented reality) 场景

简要说明了实现 3D 特效还可以使用 CSS 技术. 但这不在本书讨论范围内.

作者简要介绍了基本要求

  • 文本编辑器 (推荐 VSCode)
  • 浏览器 (作者使用 火狐)
  • git 等工具, 然后下载随书源代码, 并介绍如果演示与运行, 以及修改代码并预览

作者使用 webpack 并封装了一些工具, 这里不会详细介绍.

作者 node 环境 使用 16.14.0.

然后详细介绍了几何体的创建过程

  1. HTML 骨架
  2. JS 骨架
    1. 每一个 3d 应用都需要三个对象, 场景, 摄像机, 以及渲染器
    2. 基本步骤:
      1. 创建场景
      2. 创建摄像机
      3. 创建渲染器
      4. 创建灯光
      5. 创建几何体: 立方体, 环面扭结体
      6. 创建一个大的平面
      7. 创建轨迹控制工具
      8. 添加帧数统计工具
      9. 渲染场景

然后是一步步演示代码

注意, 作者在创建场景后, 设置场景的背景色使用 backgroundColor, 该语法有误. 应该使用 background 属性, 赋值为 THREE.Color(0xffffff).

案例添加的步骤为:

  1. 创建静态场景, 添加立方体, 曲面扭结体
  2. 添加平面, 灯光等
  3. 介绍动画, 并说明了 requestAnimationFrame 与 setTimeout 的区别.
  4. 让几何体运行起来
  5. 介绍第三方工具 lil-gui, 并引入修改运动速度
  6. 介绍内置的统计帧数模块
  7. 介绍内置的轨迹控制器 (OrbitControls)

虽然书中没有介绍, 但现在使用 vite 是主流工具, 官方网站在文档中已经加以说明. 直接基于 vite 和 TS 进行开发.

整个代码都很 OOP, 创建对象, 设置属性, 添加到场景. 对于工具, 创建工具, 设置属性, 加入页面 (DOM), 在动画中 render 或 update.

创建几何对象的基本思路:

  1. 创建骨架数据 (Geometry 对象)
  2. 创建材质 (material 对象), 用于设置颜色等外观
  3. 创建网格 (mesh 对象), 利用骨架数据和材质数据来生成几何体
  4. 在将几何体添加到场景中

最后演示了保时捷的模型加载, 仅仅是演示. 然后介绍了一个辅助工具 (也是演示), 来显示坐标系, 平面坐标网格, 极坐标网格.

ch02 组成 threejs 应用的基本组件

本章可以理解为第一章那个案例的进阶. 本章会介绍构成 threejs 应用的基本组件. 并且一些高级组件的用法与基础组件的用法思路一致.

本章主题:

  1. 创建场景
  2. 几何体 (Geometry) 与网格 (Mesh) 是如何关联的
  3. 在不同场景 (Scene) 使用不同的相机 (Camera)

2.1 创建场景

从 ch01 了解到, 一个应用应该包含: 相机, 灯光, 网格, 与渲染器.

而场景是一个主容器, 其中包含了需要渲染的所有内容. 场景常常被称为场景图, 如其名, 它并不是一个数组, 而是一个树结构. three.js 提供了 THREE.Group 对象作为容器, 存储几何对象. 而该对象, 以及其他几何对象均继承自 THREE.Object3D 对象. 而场景也继承自 THREE.Object3D, 从抽象树的角度来看, 就可以统一维护了.

场景的基本功能

要展示场景的功能与配置, 参考随书示例 (chapter-2/basic-scene.html), 运行起来后, 利用 lil-GUI 来修改配置, 直观的查看 (作者来做了提示可以用鼠标交互).

image-20241212164030197

开始加载项目时, 只有一个地板, 似乎什么也没有

image-20241212164632462

但实际上其中至少有三个对象

  • THREE.Mesh, 用于表示地板对象.
  • THREE.PerspectiveCamera 用于展示可以看到的内容.
  • 其中添加了环境光源 (THREE.AmbientLight) 与直线光源 (THREE.DirectionalLight).

然后作者说明了源代码的位置, 以及其引用了什么代码. 然后作者抽象出代码逻辑:

  1. 创建透视相机
  2. 创建渲染器
  3. 创建场景
  4. 创建光源
  5. 创建地板
const camera = new THREE.PerspectiveCamera(75, w / h, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ antialias: true })
const scene = new THREE.Scene()

// 添加光源
scene.Add(new THREE.AmbientLisght(0x666666))
scene.Add(THREE.DirectionalLight(0xaaaaaa))

// 创建地板
const geo = new THREE.BoxBufferGeometry(10, 0.25, 10, 10, 10, 10)
const mat = new THREE.MeshStandardMaterial({ color: 0xffffff })
const mesh = new THREE.Mesh(geo, mat)
scene.Add(mesh)

然后作者对代码进行了解释, 并说明可以加入动画, 以及轨迹控制等内容.

最后作者提示用户如何操作案例, 操控右上角的控制工具.

添加与移除对象

lil-gui 使用很方便, 使用函数会自动加载成按钮, 使用 boolean 值自动转换为复选框, 使用数字, 定义范围会成为拖拽框, 使用数组就是下拉框.

  • 添加立方体就是, 随机生成位置坐标, 颜色, 尺寸, 然后添加到场景中 (数据会存储在 scene.children 中).
  • 移除立方体就是 scene.children.pop().
const addCube = (scene) => {
  const color = randomColor()
  const pos = randomVector({
    xRange: { fromX: -4, toX: 4 },
    yRange: { fromY: -3, toY: 3 },
    zRange: { fromZ: -4, toZ: 4 }
  })

  const rotation = randomVector({
    xRange: { fromX: 0, toX: Math.PI * 2 },
    yRange: { fromY: 0, toY: Math.PI * 2 },
    zRange: { fromZ: 0, toZ: Math.PI * 2 }
  })

  const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5)
  const cubeMaterial =
    new THREE.MeshStandardMaterial({
      color: color,
      roughness: 0.1,
      metalness: 0.9
    })

  let cube = new THREE.Mesh(geometry, cubeMaterial)
  
  cube.position.copy(pos)
  cube.rotation.setFromVector3(rotation)
  cube.castShadow = true
  scene.add(cube)
}

const removeCube = (scene) => {
  scene.children.pop()
}

其中 randomVector 就是在指定范围生成随机数, 然后返回 THERE.Vector3 实例. randomColor 是随机生成三原色来创建 THREE.Color 对象.

然后作者介绍了这段代码的逻辑. 其中强调了 name 属性, 随书示例代码中为每一个创建的立方体添加了一个名字 cube.name = 'cube-' + parent.children.length.

该名字有一个非常重要的作用, 可用于调试. 使用场景的 getObjectByName(name) 方法可以直接获得该对象的引用, 调试中可以直接设置其属性来进行调试.

Three.js 的 Scene 提供了很多有用的方法:

  • add() 方法, 将对象添加到场景中. 与 DOM 一样, 一个对象只允许有一个父节点, 如果对象已经在某个节点下, 那么会被移动到新位置中.
  • attach() 方法, 附加对象, 作用与 add 类似, 但会保留该对象被作用的平移与旋转 (这个有待测试).
  • getObjectById() 方法. 当对象添加到场景中时, 会根据添加的顺序 (base-1) 生成 id 属性值. 根据 id 值, 该方法可以获得该对象的引用.
  • getObjectByName() 方法. 基于 name 来查找对象 (需要提前为对象的 name 赋值).
  • remove() 删除对象.
  • clear() 清空所有对象.

这些方法由 THREE.Object3D 提供, THREE.Scene 继承自该类. 这些方法会贯穿整本书, 用于操作场景中的对象.

添加 fog

fog 属性用于在场景中添加雾影特效. 即远离相机的界面会慢慢消失.

添加 fog 的方法就是实例化, 为场景的 fog 属性赋值即可. 有两个对象可以使用: THREE.Fog 和 THREE.FogExp2.

  • THREE.Fog 设置 near 和 far, 用于设置雾的范围, 它是线性变化的.
  • THREE.FogExp2 设置颜色与浓度. 它是指数变化的.

按照作者的意思, 建议在使用时进行测试选择.

修改背景

修改背景的方法可以调用 renderer.clearColor(颜色数值) 来实现. 还可以通过设置场景的 background 属性来修改. 有三种选项:

  • 修改为固定颜色.
  • 可以使用 texture 纹理. 它本质是一张图片, 以展开来填满整个屏幕. 第10章会进行讨论.
  • 使用环境题图. 它本质也是纹理 (texture), 但它完全包裹相机, 支持随相机的移动而移动.

这里是设置的 canvas 的背景色, 而不是页面的 body 背景色, 如果需要一个透明的 canvas, 可以在渲染器中设置 alpha 为 true.

const renderer = new THREE.WebGLRenderer({ alpha: true })

在随书案例中, 右上角 lil-GUI 的 backGround 下拉框中可以演示不同的效果.

image-20241212181419399

第10章会讨论 Texture 和 Cubemap 的细节. 下面仅仅是快速看看其如何实现.

// 设置 null, 清空
scene.background = null;
// 设置简单颜色
scene.background = new THREE.Color(0xff0000);
// 使用 TextureLoader 来加载纹理
const textureLoader = new THREE.TextureLoader();
textureLoader.load('path.jpg', loaded => {
  scene.background = loaded;
});

// cubemap 也使用 TextureLoader 来加载
textureLoader.load('path.jpg', loaded => {
  loaded.mapping = THREE.EquirectangularReflectionMapping;
  scene.background = loaded;
});

注意加载纹理是异步的.

更新场景中的所有材质

之前看到创建一个立方体, 需要准备 Geometry 和指定的 Material, 场景可以强制更新场景中所有对象的材质. 场景有两个属性可以修改所有的材质.

  • 一是为场景 (THREE.Scene) 实例的 overrideMaterial 属性赋值. 会覆盖场景中所有的材质.
  • 一是创建环境映射 (environment map), 它可模拟网格所在环境, 并可提供反光等特性, 让材质看起来更为真实.

创建环境映射的步骤:

  1. 加载材质
  2. 设置材质的 mapping 属性
  3. 为场景 (Scene) 的 environment 属性赋值
textureLoader.load('path.jpg', loaded => {
  loaded.mapping = THREE.EquirectangularReflectionMapping
  scene.environment = loaded
})

然后作者提示在案例中体验其区别, 使用环境映射的方法, 会有表面反光特效, 这一点与单一的颜色是不同的.

2.2 几何体与网格的关联

每次在使用几何体时, 都会创建几何对象, 材质对象, 然后创建网格对象将其整合. 最后才会将网格对象添加到场景中.

geometry 的功能与属性

three.js 内置了很多几何对象. 随书案例中 (chapter-2/geometries) 列举了一些几何体:

image-20241213143655886

在第 5 章与第 6 章会讨论几何体. 这里仅仅讨论几何体是什么.

threejs 中所有的几何体, 所有的 3D 模型都是由 3D 空间中的点集所构成. 这些点也称为顶点. 而面都通过这些顶点连接起来. 以立方体为例:

  • 立方体有 8 个顶点, 这些顶点可以定义 x, y, z 轴. 每一个顶点都是 3D 空间中的点
  • 立方体有 6 个面. 每一个角都有一个顶点. 3D 模型中, 每一个面都是由三个顶点构成的三角形来组成. 因此立方体的每一个面都是由两个三角形所构成.

置于三角形什么的, 可以深入一下图形学的资料.

如果使用 three.js 提供的几何体, 不需要我们来创建顶点与面, 只需要使用对应的类 (构造函数), 与参数, three.js 会生成正确的顶点与面, 来构成几何体.

也可以手动的创建:

const v = [
  [1, 3, 1],
  [1, 3, -1],
  [1, -1, 1],
  [1, -1, -1],
  [-1, 3, -1],
  [-1, 3, 1],
  [-1, -1, -1],
  [-1, -1, 1]
]
const faces = new Float32Array([
  ...v[0], ...v[2], ...v[1],
  ...v[2], ...v[3], ...v[1],
  ...v[4], ...v[6], ...v[5],
  ...v[6], ...v[7], ...v[5],
  ...v[4], ...v[5], ...v[1],
  ...v[5], ...v[0], ...v[1],
  ...v[7], ...v[6], ...v[2],
  ...v[6], ...v[3], ...v[2],
  ...v[5], ...v[7], ...v[0],
  ...v[7], ...v[2], ...v[0],
  ...v[1], ...v[3], ...v[4],
  ...v[3], ...v[6], ...v[4]
])
const bufferGeometry = new THREE.BufferGeometry()
bufferGeometry.setAttribute('position', new THREE.BufferAttribute(faces, 3))
bufferGeometry.computeVertexNormals()

这是生成 Geometry 数据的代码. 配合材质与网格后:

const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true })
const cube = new THREE.Mesh(bufferGeometry, material)
scene.add(cube)

得到下面效果

image-20241213160609951

然后作者介绍了每一段代码的含义.

  • 在数组 v 中定义了构成该立方体需要的点. 使用这些点来创建面.
  • 在 three.js 中需要在 Float32Array 数组中提供所有面的信息. 每一个面由三个顶点构成, 因此我们创建一个面需要 9 个数字, 构成三个顶点的 x, y, z 值. 然后作者简要解释了解构运算符, 以及得到的结果.
  • 需要注意的是构成面的数组的顺序. 顺序决定了 three.js 是否判定其为正面.
    • 使用顺时针方向表示面对相机方向
    • 使用逆时针表示背对相机方向

这个逆时针与顺时针需要验证.

添加辅助用的坐标系

const axes = new THREE.AxesHelper(20)
scene.add(axes)

image-20241213181405815

其中 x, y, z 轴分别对应红, 绿, 蓝色. 使用右手标架.

可以利用 position 属性来调整 x, y, z 值来验证.

然后作者简要说明了四边形方格与三角形方格. 一般选用方格更容易建模, 但在游戏等渲染要求高的情况, 三角方格效率更高. 在随书案例中 chapter2/custom-geometry 演示了这个矩形, 并提供了工具栏, 来控制各个参数, 可以调整形状.

但是为了性能, three.js 会有一个假定, 就是每一个对象在其生命周期中都是不会变的 (和字符串逻辑一样). 如果修改背后的数组, 即修改本例中为 const faces = new Float32Array([...]) 数组, 那么需要通知 three.js 更新. 这里需要修改属性 needsUpdate:

mesh.geometry.attributes.position.needsUpdate = true;
mesh.geometry.computeVertexNormals();

这里需要重新计算的很多, 细节会在 ch10 章中详细讨论.

作者对代码进行了较高度的封装.

Geometry 实例有一个 clone() 方法可以赋值几何体, 使用该方法可以很容易的创建出不同材质的网格. 获得点数组后, 就可以修改坐标.

const cloneGeometry = scene => {
  const clonedGeometry = bufferGeometry.clone()
  const backingArray = clonedGeometry.getAttribute('position').array
  // 修改点的 x 坐标值
  for (const i in backingArray) {
    if ((i + 1) % 3 === 0) {
      backingArray[ i ] = backingArray[ i ] + 3
    }
  }
  clonedGeometry.getAttribute('position').needsUpdate = true
  const cloned = meshFromGeometry(clonedGeometry)
  cloned.name = 'clonedGeometry'

  const p = scene.getObjectByName('clonedGeometry')
  if (p) scene.remove(p)

  scene.add(cloned)
}
const meshFromGeometry = geometry => {
  const materials = [
    new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }),
    new THREE.MeshLambertMaterial({ opacity: 0.1, color: 0xff0044, transparent: true })
  ]
  const mesh = createMultiMaterialObject(geometry, materials)
  mesh.name = 'customGeometry'
  mesh.children.forEach(e => {
    e.castShadow = true
  })
  return mesh
}

使用 clone() 函数获得几何体对象的点集副本. 通过修改点集对象的值就可以修改坐标 (也可以使用 TranslateX ). 这里使用了自定义方法 meshFromGeometry(). 这个方法中, 在一个网格中使用了多个材质, threejs 扩展库中提供了一个方法 createMultiMaterialObject 来实现. 这个实现本质上是创建了一个 Group, 在同一个位置创建了多个实体, 不同实体使用不同的材质. 让将所有实体存储在这个 Group 中.

import { createMultiMaterialObject } from "three/examples/jsm/utils/SceneUtils";

需要注意的是, 如果要使用阴影, 表示每一个对象都需要设置.

有种图层的感觉.

上述代码中使用 createMultiMaterialObject 来添加线框, three.js 还有一个新方法来添加线框: THREE.WrieframeGeometry. 例, 有一个 Geometry 名为 geom, 那么可以使用:

const wireframe = new THREE.firewareGeometry(geom)

然后使用 THREE.LineSegments 对象来绘制线

const line = new THREE.LineSegments(wireframe)

接着将其添加到场景中:

scene.add(line)

该辅助方法内部是使用 THREE.Line 对象, 可以设置样式来控制显示, 例如:

line.material.linewidth = 2

网格 (mesh) 的属性与方法

来看下面属性

  • position 表示对象在父容器中的位置.
  • rotation 用于控制对象沿轴线的旋转. three.js 提供了几个快捷方法: rotateX(), rotateY(), 和 rotateZ().
  • scale 控制对象沿某轴进行缩放.
  • translateX()/translateY() / translateZ() 控制对象沿某轴移动.
  • lookAt() 用于指向控制, 用这也算是手动控制旋转的一种方法.
  • visible 控制该网格是否被渲染.
  • castShadow 控制是否渲染阴影. 考虑性能, 默认是 false.

旋转对象就是将对象在其轴上进行旋转 (轴可以是 x, y, z 三个轴). 有三个方法控制旋转

  • rotateN(), 这里的 N 指 X, Y, Z. 它表示在 局部空间 (local space) 中进行旋转. 即 3D 对象的父容器中旋转. 如果对象直接放在场景中, 就是针对场景的轴进行旋转; 如果对象放在某个组中, 那么就以该对象的父容器的轴进行旋转.
  • rotateOnWorldAxis(), 基于世界坐标系进行旋转.
  • rotateOnAxis() 基于对象空间 (object space) 进行旋转.

在随书案例中 chapter-2/mesh-properties 可以通过控制右侧的控制来演示查看特效.

这部分都待验证. 并验证其代码实现.

通过 position 属性来设置网格位置

有三种用法:

  • 分别设置

    cube.position.x = 10
    cube.position.y = 3
    cube.position.z = 1
    
  • 一次性设置

    cube.position.set(10, 3, 1)
    
  • 对象赋值

    cube.position = new THREE.Vector3(10, 3, 1)
    
使用 rotation 属性定义网格旋转

可用方法

cube.rotation.x = 0.5 * Math.PI
cube.rotation.set(0.5 * Math.PI, 0, 0)
cube.rotation = new THREE.Vector3(0.5 * Math.PI, 0, 0)

默认使用弧度制. 如果想要使用角度制, 需要进行转换

cube.rotation.x = degrees * (Math.PI / 180)

three.js 中提供了 MathUtils 类, 来提供一些辅助方法.

jk: 置于提供了什么方法, 书中的案例没有详细的介绍.

使用 translate 来修改位置

使用 translate 可以修改对象的位置. 该方法不是设置绝位置, 而是从当前对象的位置进行调整. 在随书案例的 chepter-2/mesh-properties 中.

设置 visible 可以控制对象的显示与隐藏, 连同阴影也会显示与隐藏.

2.3 使用不同的相机

three.js 提供了两种相机:

  • 透视相机 PerspectiveCamera
  • 正交相机 OrthographicCamera

three.js 还支持一些特殊的相机 (和 VR 等有关), 有关细节不在本书范围, 可以参考:

  • Anaglyph effect
  • Parallax barrier
  • Stereo effect

基本上只要切换相机, 其他内容交给 Threejs 即可.

要体验简单的 VR 相机, 只需要使用 THREE.StereoCamera 来创建 3D 场景.

有关 WebVR 的细节可以参考 https://webvr.info/developers/

正交相机与透视相机

随书案例为 chapter2/cameras

image-20241217162149476

正交相机将所有的立方体渲染成相同尺寸, 不在意物体间的距离和相机距离的时候使用. 一般在一些 2D 游戏中使用.

透视相机的属性

首先看看 THREE.PerspectiveCamera.

作者说, 可以在随书案例中修改对应参数来查看效果.

image-20241217162820500

  • fov Field of View, 视野, 表示在相机中可以看到的场景内容. 人类的视野大概是 180 度. 但是鸟类可以达到 360 度. 但是一般屏幕无法达到该角度. 一般在游戏中会使用 60 到 90 度. 默认会使用 50 度.
  • aspect 宽高比, 一般会使用 window.innerWidth / window.innerHeight.
  • near 表示渲染的场景可以离相机有多近, 一般会是一个很小的值, 通常使用 0.1.
  • far 类似于 near, 但是取值不能太小, 否则超出的不显示. 一般可以取 100.
  • zoom 允许在场景中缩放. 取值如果大于 1 则是放大 (zoom in), 小于 1 则是缩小 (zoom out).

zoom 没演示出来其作用.

下图展示了这些参数的作用

image-20241217165934872

fov 表示水平视野的角度, 基于 aspect 决定垂直视角. 渲染的内容只会渲染在 near 到 far 之间的内容. 超出的不渲染.

正交相机

正交相机不是近大远小, 将所有的尺寸按照等尺寸渲染. 在定义一个正交相机时, 就是在定义一个渲染的矩形空间 (立方体空间)

  • left
  • right
  • top
  • bottom
  • near
  • far
  • zoom

image-20241217171544035

export class OrthographicCamera extends Camera {
    /**
     * @param left Camera frustum left plane.
     * @param right Camera frustum right plane.
     * @param top Camera frustum top plane.
     * @param bottom Camera frustum bottom plane.
     * @param [near=0.1] Camera frustum near plane.
     * @param [far=2000] Camera frustum far plane.
     */
    constructor(left?: number, right?: number, top?: number, bottom?: number, near?: number, far?: number);
  ...

Looking at specific points

前面介绍的是如何创建相机, 以及如何摆放相机. 一般相机会指向 (0, 0, 0) 的位置. 使用下面语法来更改指向:

camera.lookAt(new THREE.Vector3(x, y, z))

jk: 但是演示后不晓得为什么没有效果

使用该方法可以实现相机跟随某个几何体 camera.lookAt(<mesg>.position).

调试相机内看到的内容

chapter-2/debug-camera

使用两个相机, 通过一个相机来查看另一个相机的位置与视角. 代码片段

const helper = new THREE.CameraHelper(camera)
scene.add(helper)
// 然后在渲染动画函数中写入
helper.update()

ch03 使用灯光

前面介绍了如何搭建一个 3D 应用, 涉及的对象包括: 场景, 摄像机, 渲染器, 几何体, 材质, 网格, 动画等.

但是没有灯光, 只能显示纯色或网格几何体, 无法显示正常的样式的对象.

本章介绍可用的灯光类型, 以及怎么进行选择.

注意 WebGL 中并不包括灯光, 如果没有 three.js 我们需要自己编写着色器, 这是比较困难的. 入门可以参考文档.

3.1 Three.JS 提供了哪些灯光

不同的灯光有不同的效果, 我们将讨论下面灯光:

  • THREE.AmbientLight. 这是基础灯光. 其颜色是场景中当前对象的颜色.
  • THREE.PointLight. 该灯光是空间中的一个点光, 光线会朝所有方向发射. 该光线会产生阴影.
  • THREE.SpotLight. 该灯光是一个锥形效果, 如同桌上的台灯. 该灯光会产生阴影.
  • THREE.DirectionalLight. 平行光, 也称为无限光, 如同太阳发出的光. 该光会产生阴影.
  • THREE.HemisphereLight. 这是一中特殊光源, 用于模拟室外的自然光.
  • THREE.RectAreaLight. 区别于点光源, 用一个区域来呈现光源.
  • THREE.LightProbe. 这是一种特殊光源, 基于环境地图, 创建一个环境光源来照亮场景.
  • THREE.LensFlare. 这不是一个光源, 但可以用来创建光晕效果.
Last Updated:
Contributors: jk