场景对象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提供了两种雾:Fog和FogExp2。
- Fog(线性雾):雾的浓度随距离线性增加。你需要设置
near和far两个值。在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属性可以接受三种类型:Color、Texture和CubeTexture。
- 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场景管理就算入门了。