一、Scene 到底是什么?别再把它当“容器”这么简单

在 three.js 里,Scene 常被一句话带过:

Scene 是一个容器,用来放物体、灯光和相机。

这句话没错,但非常不够

更准确地说:

Scene 是 three.js 世界的“根节点(Root)”,是一次渲染的完整上下文。

它承担了三件非常核心的事情:

  1. 组织层级结构(Scene Graph)
  2. 决定哪些对象参与渲染
  3. 作为 Renderer 渲染时的入口

当你调用:

renderer.render(scene, camera)

实际上发生的是:

  • scene 这个根节点开始
  • 深度遍历整个对象树
  • 找出可见的 Mesh、Light、Fog、Background
  • 结合 camera 进行一次完整的绘制流程

👉 Scene = 一次渲染世界的“宇宙起点”


二、Scene Graph:three.js 的“世界观”

Scene 的底层,本质是一个树形结构(Scene Graph)

Scene
 ├── Group
 │    ├── Mesh
 │    └── Mesh
 ├── Light
 └── Camera(可选)

1️⃣ Scene 继承关系

Object3D
  └── Scene

这意味着:

  • Scene 本身也是一个 Object3D

  • 它拥有:

    • position / rotation / scale
    • children
    • add / remove / traverse 等方法

但有一点很关键:

Scene 的 transform(位置、旋转)几乎不会被用到

你不会把整个世界再平移一遍,对吧?


2️⃣ add 到 Scene 的对象,都会被“接管”

scene.add(mesh)

这一步意味着:

  • mesh 成为 scene 的子节点
  • mesh 的世界坐标 = 父节点变换 × 自身变换
  • renderer 在渲染时会自动遍历到它

⚠️ 注意:
只有挂在 scene 下的对象,才有可能被渲染

这也是为什么很多初学者会遇到:

“我 mesh 明明创建了,但怎么什么都没显示?”

90% 的原因:忘了 add 到 scene


三、Scene 里到底能放什么?

1️⃣ Mesh(最常见)

const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

这是你看到“东西”的根本来源。


2️⃣ Light(灯光)

灯光一定要在 scene 里,否则不会生效:

const light = new THREE.DirectionalLight(0xffffff, 1)
scene.add(light)

很多人灯光没效果,本质原因是:

  • 灯光创建了
  • 但没 add 到 scene

3️⃣ Camera(可选)

技术上讲,相机不一定要加到 scene

renderer.render(scene, camera)

camera 是单独传进去的。

但在一些场景下,把 camera 放进 scene 是非常有用的:

scene.add(camera)

比如:

  • 相机要跟随某个物体
  • 相机作为某个 Group 的子节点
  • 做“第一人称 / 第三人称”结构

4️⃣ Group(强烈推荐使用)

Scene 里不要直接堆一堆 Mesh。

正确的工程化做法是:用 Group 管理结构。

const car = new THREE.Group()
car.add(body)
car.add(wheel1, wheel2)

scene.add(car)

好处非常多:

  • 整体移动 / 缩放 / 旋转
  • 逻辑结构清晰
  • 后期动画、交互非常好写

四、Scene 的常用属性(被忽略但很重要)

1️⃣ background:背景

scene.background = new THREE.Color(0x000000)

或者用贴图:

const texture = new THREE.TextureLoader().load('bg.jpg')
scene.background = texture

💡 背景不是 DOM,也不是 CSS,而是 WebGL 世界的一部分。


2️⃣ fog:雾效(提升空间感神器)

scene.fog = new THREE.Fog(0x000000, 10, 100)

雾效的作用:

  • 提升纵深感
  • 遮挡远处细节
  • 大场景性能优化(视觉层面)

新手很少用,但一用就“质感暴涨”


3️⃣ environment:环境贴图(PBR 场景必备)

scene.environment = envMap

在使用 MeshStandardMaterial / MeshPhysicalMaterial 时,环境贴图会直接影响金属、反射、质感。


五、Scene 的生命周期与性能意识

1️⃣ Scene 不应该频繁 new

❌ 错误做法:

function render() {
  const scene = new THREE.Scene()
  renderer.render(scene, camera)
}

✔ 正确做法:

const scene = new THREE.Scene()

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}

Scene 通常是长生命周期对象


2️⃣ 清理 Scene(很重要)

当你做:

  • 页面切换
  • 场景重建
  • 模型替换

一定要注意释放资源:

scene.traverse(obj => {
  if (obj.isMesh) {
    obj.geometry.dispose()
    obj.material.dispose()
  }
})

否则:

  • GPU 显存泄漏
  • 页面越用越卡
  • 刷新才能恢复

六、一个“工程级”的 Scene 组织示例

const scene = new THREE.Scene()

// 背景 & 雾
scene.background = new THREE.Color('#111')
scene.fog = new THREE.Fog('#111', 10, 80)

// 灯光组
const lights = new THREE.Group()
lights.add(new THREE.AmbientLight(0xffffff, 0.4))
lights.add(new THREE.DirectionalLight(0xffffff, 1))
scene.add(lights)

// 物体组
const world = new THREE.Group()
scene.add(world)

// 模型
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(),
  new THREE.MeshStandardMaterial({ color: 'orange' })
)
world.add(cube)

这种结构的优点:

  • Scene 只负责“世界”
  • lights / world 职责清晰
  • 后期扩展非常轻松

七、初中级开发者常见误区总结

❌ 把 Scene 当数组一样乱塞对象
❌ 所有 Mesh 都直接 add 到 scene
❌ 不用 Group,后期动画一团乱
❌ 忽略 fog / background / environment
❌ 不清理 Scene,显存慢慢爆掉


八、你真正需要记住的三句话

1️⃣ Scene 是 three.js 世界的根节点
2️⃣ 渲染从 Scene 开始,一切结构都源于 Scene Graph
3️⃣ 写 three.js 项目,Scene 设计 ≈ 架构设计

更多推荐