场景对象Scene:Scene的属性和方法、雾效(Fog)、背景色与纹理、场景的层级管理

好,咱们今天来聊聊Three.js里最核心的一个东西——Scene(场景)

你可以把Scene想象成一个舞台。所有你看到的东西,模型、灯光、相机,都得放到这个舞台上才能被渲染出来。没有Scene,Three.js啥也干不了。我刚开始学Three.js的时候,就犯过一个低级错误:创建了Mesh,忘了加到Scene里,盯着黑屏看了十分钟……嗯,这种经历一次就够了。

Scene的属性和方法

Scene本身其实并不复杂。它继承自Object3D,所以它自己也是一个“容器”,可以嵌套子对象。但除了继承来的东西,它有几个专属的属性和方法,咱们得拎清楚。

属性/方法 类型 说明
background Color / Texture / CubeTexture 场景的背景色或纹理。默认是黑色。
fog Fog / FogExp2 场景的雾效。可以制造景深感。
overrideMaterial Material 强制场景中所有物体使用同一个材质。调试时很有用。
autoUpdate Boolean 是否自动更新场景中的矩阵。默认true,一般不动它。
add(object) 方法 向场景中添加对象。
remove(object) 方法 从场景中移除对象。
traverse(callback) 方法 递归遍历场景树中的所有子节点。

这里我想特别提一下overrideMaterial。我个人习惯在项目初期用它来快速检查模型结构。比如你导入了一个复杂的glTF模型,想看看它的网格拓扑,直接把所有材质替换成一个线框材质,一眼就能看出问题。

// 强制所有物体显示为线框
scene.overrideMaterial = new THREE.MeshBasicMaterial({
    wireframe: true,
    color: 0x00ff00
});
// 想恢复?设为null就行
scene.overrideMaterial = null;
小技巧:traverse可以批量修改场景中所有物体的属性。比如你想把所有Mesh的阴影都关掉,一行代码搞定。

雾效(Fog)

雾效是提升场景氛围的利器。说白了,就是让远处的物体逐渐模糊、消失。Three.js提供了两种雾:FogFogExp2

  • Fog(线性雾):雾的浓度随距离线性增加。你需要设置nearfar两个值。在near距离内完全清晰,超过far完全看不见。
  • FogExp2(指数雾):雾的浓度随距离指数增长。只有一个参数density,密度越大,雾越浓。效果更自然,像真实世界的雾气。

我在项目中遇到过一个问题:加了雾效之后,远处的模型边缘出现了奇怪的“切面”效果。后来发现是相机的far平面设置得太近了,物体被裁剪了,而不是被雾“吞没”了。所以记住:雾效的far值一定要小于相机的far值,否则雾还没起作用,物体就被相机裁剪掉了。

// 线性雾:从10单位开始模糊,到50单位完全消失
scene.fog = new THREE.Fog(0x87CEEB, 10, 50);

// 指数雾:密度0.05,效果更柔和
scene.fog = new THREE.FogExp2(0x87CEEB, 0.05);
注意: 雾效的颜色最好和背景色一致。否则你会看到物体消失在一种颜色里,背景却是另一种颜色,非常突兀。我曾经把雾设成灰色,背景设成蓝色,结果场景看起来像被污染了……

背景色与纹理

Scene的background属性可以接受三种类型:ColorTextureCubeTexture

  • Color:最简单的纯色背景。比如scene.background = new THREE.Color(0x87CEEB),天蓝色。
  • Texture:一张2D图片作为背景。注意,它不会随相机旋转而移动,适合做静态背景。
  • CubeTexture:立方体贴图,也就是天空盒。六张图拼成一个环绕的环境,相机旋转时背景也跟着转,沉浸感极强。

你想想看,如果你的场景是一个户外场景,用纯色背景会显得很假。用一张高清的HDR环境贴图作为背景,效果立马不一样。我个人习惯在项目初期先用纯色背景调试,最后再换上纹理或天空盒。

// 纯色背景
scene.background = new THREE.Color(0x222222);

// 图片纹理背景(注意:图片不会随相机旋转)
const textureLoader = new THREE.TextureLoader();
scene.background = textureLoader.load('path/to/background.jpg');

// 天空盒(CubeTexture)
const cubeTextureLoader = new THREE.CubeTextureLoader();
scene.background = cubeTextureLoader.load([
    'px.jpg', 'nx.jpg',
    'py.jpg', 'ny.jpg',
    'pz.jpg', 'nz.jpg'
]);
核心要点: 背景纹理和雾效是两回事。背景是“最远”的层,雾效是“中间”的过渡。两者配合使用,可以做出非常丰富的景深效果。

场景的层级管理

Scene本身是一个树状结构。你添加的每一个对象,都可以有自己的子对象。这就是层级管理。

为什么要搞层级?举个例子:你有一个汽车模型,车身、车轮、车窗都是独立的Mesh。如果你把它们直接都加到Scene里,想移动整个车,你得一个一个去改位置。但如果把它们都加到一个Group里,再把Group加到Scene里,你只需要移动Group,所有部件跟着动。

const carGroup = new THREE.Group();

const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
const wheel1 = new THREE.Mesh(wheelGeometry, wheelMaterial);
const wheel2 = new THREE.Mesh(wheelGeometry, wheelMaterial);

carGroup.add(body);
carGroup.add(wheel1);
carGroup.add(wheel2);

scene.add(carGroup);

// 移动整个车
carGroup.position.set(10, 0, 0);

层级管理还有一个好处:你可以控制某个层级是否可见。比如你想隐藏整个汽车,只需要carGroup.visible = false,所有子对象都跟着隐藏。不用一个一个去设。

我记得有一次做智慧城市项目,场景里有上千个建筑。每个建筑都是一个Group,里面包含楼体、窗户、楼顶装饰。我需要根据LOD(细节层次)来切换显示。如果不用层级管理,代码会变得极其混乱。用了层级之后,我只需要遍历Scene的子节点,根据距离判断是否显示,逻辑清晰多了。

避坑指南: 我曾经在遍历场景时,直接用scene.children去修改子对象,结果不小心把Group本身给移除了,导致整个组消失。后来我改用traverse,并且只操作isMesh为true的对象,安全很多。

好了,关于Scene的核心内容就这些。记住:Scene是你的舞台,雾效是氛围,背景是底色,层级是组织方式。把这几个点吃透,你的Three.js场景管理就算入门了。