three.js Scene 类深度解析:场景背景、环境贴图、雾效与材质覆盖的完整配置指南
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
本篇技术指南以 three.js 官方 API 文档中Scene类的定义为主线,逐一讲解background、environment、fog、overrideMaterial等核心属性及其全部可调参数(含默认值与取值范围),并结合 src/scenes/Scene.js 的构造函数与 src/renderers/webgl/WebGLBackground.js、src/renderers/WebGLRenderer.js 的源码实现,说明每个属性在渲染管线中究竟如何生效。读完后你将能够独立完成一个带天空盒背景、IBL 环境光、指数雾和全局材质覆盖的 three.js 场景,并理解其底层渲染机制。
Scene 的继承体系与构造过程
Scene是 three.js 中"设置渲染内容与位置"的容器:所有需要被渲染的 3D 对象——网格(Mesh)、线条(Line)、灯光(Light)——都必须放入某个 Scene 中,由渲染器以该 Scene 为根遍历绘制。
从继承链看,Scene位于EventDispatcher → Object3D → Scene三级体系的最末端:它继承了Object3D的完整变换能力(position、rotation、scale、matrix 等)以及EventDispatcher的事件订阅机制,因此 Scene 本身也可以作为节点加入层级树、发出change事件。
在 src/scenes/Scene.js 中,构造函数做了如下事情:
class Scene extends Object3D { constructor() { super(); // 类型测试标志 this.isScene = true; this.type = 'Scene'; // 以下属性全部初始化为 null / 默认值 this.background = null; // 场景背景 this.environment = null; // 环境贴图(IBL) this.fog = null; // 雾效果 this.backgroundBlurriness = 0; // 背景模糊度 this.backgroundIntensity = 1; // 背景亮度衰减 this.backgroundRotation = new Euler(); // 背景旋转(弧度) this.environmentIntensity = 1; // 环境贴图亮度衰减 this.environmentRotation = new Euler(); // 环境贴图旋转(弧度) this.overrideMaterial = null; // 全局材质覆盖 if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); } } }两个值得注意的细节:
new Scene()无参构造,所有视觉属性都是惰性赋值的——不设置背景和环境贴图时,渲染器会回退到清屏颜色,场景本身不产生任何额外绘制开销。- 构造函数末尾的
__THREE_DEVTOOLS__分支(src/scenes/Scene.js#L115-L119):当页面加载了 three.js DevTools 浏览器扩展时,每个 Scene 的创建都会向扩展派发一个observe自定义事件,扩展借此把场景树挂到检查面板上(扩展源码见 devtools/ 目录)。这是一个"仅在 DevTools 存在时生效"的条件分支,不影响库的核心逻辑。
Scene通过 src/Three.Core.js#L10 导出(export { Scene } from './scenes/Scene.js'),因此在 ES 模块或全局THREE命名空间下都可以通过new Scene()/new THREE.Scene()访问。
属性总览
| 属性 | 类型 | 默认值 | 作用对象 |
|---|---|---|---|
.background | Color \| Texture | null | 整个画面的背景 |
.backgroundBlurriness | number(0~1 浮点) | 0 | 仅background上的环境贴图 |
.backgroundIntensity | number | 1 | 仅背景纹理 |
.backgroundRotation | Euler(弧度) | (0,0,0) | 仅background上的环境贴图 |
.environment | Texture | null | 场景内所有物理材质(PBR) |
.environmentIntensity | number | 1 | 仅environment上的环境贴图 |
.environmentRotation | Euler(弧度) | (0,0,0) | 使用environment的物理材质 |
.fog | Fog \| FogExp2 | null | 场景中一切被渲染对象 |
.isScene | boolean(只读) | true | 类型测试标志 |
.overrideMaterial | Material | null | 场景中所有对象(可被allowOverride豁免) |
background:三种背景形态及其渲染路径
.background接受三类输入,默认null:
- 纯色(Color):定义均匀的彩色背景;
- 普通纹理(Texture):定义平面纹理背景;
- 立方体贴图或等距柱状投影贴图(CubeTexture / equirectangular):定义天空盒(Skybox)。
从源码看,渲染器对这三种输入走了完全不同的分支。src/renderers/webgl/WebGLBackground.js 的render与addToRenderList函数中:
function getBackground( scene ) { let background = scene.isScene === true ? scene.background : null; if ( background && background.isTexture ) { const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background background = environments.get( background, usePMREM ); } return background; }三个分支的实际实现:
- Color 分支:直接把颜色写入 GL 清屏状态(
setClear( background, 1 )),不产生任何几何体,是最省开销的路径; - 立方体贴图 / CubeUV 分支:懒创建一个
BoxGeometry(1,1,1)的反面(BackSide)盒子,材质为BackgroundCubeMaterial(片元着色器见 src/renderers/shaders/ShaderLib/backgroundCube.glsl.js),每帧把盒子矩阵同步到相机世界位置(boxMesh.onBeforeRender),并关闭深度测试与雾(depthTest: false, fog: false),使天空盒永远贴在最远处; - 普通 2D 纹理分支:懒创建一个
PlaneGeometry(2,2)的平面,材质为BackgroundMaterial(着色器见 src/renderers/shaders/ShaderLib/background.glsl.js),通过uvTransformuniform 应用纹理自身的矩阵变换。
一个容易踩坑的点:两条几何体分支的材质都显式设置了allowOverride: false(src/renderers/webgl/WebGLBackground.js#L107),即背景永远不会被scene.overrideMaterial覆盖——调试模式下给场景套上红色 MeshBasicMaterial 时天空盒依然正常显示,这正是靠这个标志保证的。
等距柱状投影贴图(如全景图)作为背景时,仓库中的官方示例 examples/webgl_panorama_equirectangular.html 展示了典型用法:把TextureLoader加载的 equirectangular 贴图赋给scene.background,渲染器会自动识别其mapping并走 CubeUV 采样路径。
backgroundBlurriness、backgroundIntensity、backgroundRotation:背景的三个调参旋钮
这三个属性只对"环境贴图类背景"(立方体贴图 / 等距柱状 / CubeUV)或部分只对背景纹理生效,文档中明确标注了各自的作用域:
backgroundBlurriness : number(默认0)
背景模糊度,合法输入为0到1之间的浮点数。源码中它的作用点有两处:
- 在
getBackground里,backgroundBlurriness > 0会触发PMREM 预处理(environments.get( background, usePMREM )),把原始环境图离线转换为带各向异性模糊的 PMREM 纹理; - 在
backgroundCube片元着色器中,textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness )的第三个参数直接控制 Mipmap 级别(LOD),从而得到物理上正确的"环境反射模糊"效果,而不仅仅是高斯模糊滤镜。
设为1时背景完全糊化,常用于"背景虚化、突出前景物体"的布景效果。
backgroundIntensity : number(默认1)
衰减背景的颜色亮度,只对背景纹理起作用。在片元着色器中其作用是一句乘法:
// src/renderers/shaders/ShaderLib/backgroundCube.glsl.js uniform float backgroundBlurriness; uniform float backgroundIntensity; uniform mat3 backgroundRotation; void main() { vec4 texColor = textureCube( envMap, backgroundRotation * vWorldDirection ); // ...CubeUV 分支同理 texColor.rgb *= backgroundIntensity; }渲染器每帧把scene.backgroundIntensity写入 uniform(src/renderers/webgl/WebGLBackground.js#L138),因此动态修改该值即时生效。
backgroundRotation : Euler(默认(0,0,0))
背景绕原点的旋转,单位为弧度,同样只影响环境贴图类背景。渲染器把Euler转成Matrix3旋转矩阵后传入着色器:
// src/renderers/webgl/WebGLBackground.js(节选) boxMesh.material.uniforms.backgroundRotation.value .setFromMatrix4( _m1.makeRotationFromEuler( scene.backgroundRotation ) ).transpose();注意源码中的注释:矩阵是正交矩阵,所以这里用更高效的transpose()代替invert()来得到逆变换。另外对于非渲染目标纹理的立方体贴图,还会额外左乘一个水平翻转矩阵_m,以补偿立方体贴图与 CubeUV 贴图在 UV 方向上的差异。
environment:为全部 PBR 材质提供 IBL
.environment设置一个环境贴图,供场景中所有物理材质(物理渲染 PBR 材质)共享,默认null。文档特别强调了一条限制:它无法覆盖材质上已显式指派的envMap纹理——材质自带的envMap优先级更高。
src/renderers/WebGLRenderer.js 中的实现印证了这一点:
// L2198:只有这三类材质才会从场景取环境贴图 materialProperties.environment = ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) ? scene.environment : null; // L2729-L2731:仅当材质没有自己的 envMap 时,才回退到 scene.environment if ( ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) && material.envMap === null && scene.environment !== null ) { m_uniforms.envMapIntensity.value = scene.environmentIntensity; }也就是说,scene.environment实质上是"场景级环境贴图的缺省值(fallback)":MeshStandardMaterial、MeshLambertMaterial、MeshPhongMaterial在envMap为null时自动继承它,这省去了逐材质设置 IBL 的样板代码。典型工作流是先用PMREMGenerator把 HDR 全景图预处理成 PMREM 纹理,再赋给scene.environment,相关示例可参考 examples/webgl_materials_envmaps.html 与 examples/webgl_lights_physical.html。
environmentIntensity : number(默认1)
衰减环境贴图的颜色,写入材质的envMapIntensityuniform,仅影响经scene.environment生效的环境贴图。
environmentRotation : Euler(默认(0,0,0))
环境贴图旋转(弧度)。对应源码:
// src/renderers/WebGLRenderer.js L2203 materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation;只有当材质真正"吃到"了scene.environment(即自身envMap为null)时,才会使用场景级的environmentRotation;否则仍用材质自己的envMapRotation。
fog:线性雾与指数雾
.fog接受Fog或FogExp2实例,默认null。设置后,雾会作用于场景中一切被渲染的物体(背景几何体因材质fog: false而豁免)。
Fog(线性雾)
src/scenes/Fog.js 定义了随距离线性变浓的雾:
class Fog { constructor( color, near = 1, far = 1000 ) { this.isFog = true; this.color = new Color( color ); this.near = near; // 最小生效距离,近于此值的物体不受雾影响 this.far = far; // 最大距离,超过此值完全被雾吞没 } }near:距离相机小于该值的物体不受雾影响(默认1);far:距离相机大于该值的物体完全被雾覆盖(默认1000)。
文档给出的官方用法示例:
const scene = new THREE.Scene(); scene.fog = new THREE.Fog( 0xcccccc, 10, 15 );FogExp2(指数平方雾)
src/scenes/FogExp2.js 提供比线性雾更自然的"近处清晰、远处快速变浓"的指数平方衰减:
const scene = new THREE.Scene(); scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );其唯一参数density(密度,默认0.00025)决定雾变浓的速度,不需要指定 near/far,更适合开放世界场景。
overrideMaterial 与 allowOverride:全局材质覆盖
.overrideMaterial(默认null)强制场景中一切物体用指定材质渲染,是排查模型、调试线框/点云、做"全白场景"截图时的利器:
scene.overrideMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000 } );但文档同时指出可以豁免个别材质:把Material#allowOverride设为false即可排除。该标志定义在 src/materials/Material.js#L439(默认true),渲染器在两个渲染后端中都做了检查:
// src/renderers/WebGLRenderer.js L2132 if ( material.allowOverride === true && overrideMaterial !== null ) { // 使用 overrideMaterial 替换原始材质 }背景盒子/平面材质(allowOverride: false)正是利用这一机制免于被调试材质覆盖的,见上一节的WebGLBackground.js源码。
copy() 与 toJSON():场景的复制与序列化
Scene重写了两个核心方法,值得了解其行为边界:
copy( source, recursive )(src/scenes/Scene.js#L123-L144):
copy( source, recursive ) { super.copy( source, recursive ); if ( source.background !== null ) this.background = source.background.clone(); if ( source.environment !== null ) this.environment = source.environment.clone(); if ( source.fog !== null ) this.fog = source.fog.clone(); this.backgroundBlurriness = source.backgroundBlurriness; this.backgroundIntensity = source.backgroundIntensity; this.backgroundRotation.copy( source.backgroundRotation ); this.environmentIntensity = source.environmentIntensity; this.environmentRotation.copy( source.environmentRotation ); if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); this.matrixAutoUpdate = source.matrixAutoUpdate; return this; }要点:纹理类属性(background / environment / overrideMaterial)与雾都是深克隆(clone()),标量与 Euler 直接拷贝;源为null的属性则保持目标自身的null值不变。
toJSON( meta )(src/scenes/Scene.js#L146-L161):在父类序列化的基础上,额外写入fog、backgroundBlurriness、backgroundIntensity、backgroundRotation(转为数组)、environmentIntensity、environmentRotation。这是 glTF / 编辑器场景文件能还原场景雾效与背景调参的来源。
综合实战:一个带天空盒、IBL、雾与调试覆盖的完整场景
把上述全部属性组合起来,一个典型的 three.js 场景初始化如下:
import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; import { PMREMGenerator } from 'three'; const scene = new THREE.Scene(); // 1. 背景:环境贴图 + 模糊 + 降亮 + 旋转 const pmremGenerator = new THREE.PMREMGenerator( renderer ); scene.background = pmremGenerator.fromScene( new RoomEnvironment() ).texture; scene.backgroundBlurriness = 0.4; // 0~1,越大越糊(触发 PMREM 路径) scene.backgroundIntensity = 0.8; // 背景整体压暗 scene.backgroundRotation.set( 0, Math.PI / 4, 0 ); // 偏航旋转 45° // 2. 环境光:同一张 PMREM 图喂给所有 PBR 材质 scene.environment = scene.background; scene.environmentIntensity = 1.2; // 3. 雾:近处清晰、远处指数变浓 scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); // 4. 调试用材质覆盖(发布前删除) // scene.overrideMaterial = new THREE.MeshBasicMaterial( { wireframe: true } ); const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.set( 0, 1.6, 4 ); const renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); const controls = new OrbitControls( camera, renderer.domElement ); const cube = new THREE.Mesh( new THREE.BoxGeometry( 1, 1, 1 ), new THREE.MeshStandardMaterial( { color: 0x88aacc, roughness: 0.3, metalness: 0.4 } ) ); scene.add( cube ); // Scene 继承自 Object3D,用法与普通节点完全一致 renderer.setAnimationLoop( () => { controls.update(); renderer.render( scene, camera ); } );运行效果上:天空盒随backgroundRotation偏航 45°、整体压暗 20% 并带模糊;MeshStandardMaterial在未显式指定envMap的情况下自动继承scene.environment得到 IBL 反射;远处物体被FogExp2逐渐吞没。若临时打开overrideMaterial,除背景外的所有物体会变成红色/线框材质——这正是调试场景拓扑时的标准手法。
小结
Scene是 three.js 渲染体系中最轻量的"全局配置面板":
background三态(纯色 / 平面纹理 / 天空盒)对应三种渲染路径,backgroundBlurriness(0~1)、backgroundIntensity、backgroundRotation(弧度)是其专属调参旋钮;environment是场景级 IBL 缺省源,仅对envMap为null的 Standard/Lambert/Phong 材质生效,environmentIntensity/environmentRotation配套调节;fog支持Fog(color, near=1, far=1000)线性雾与FogExp2(color, density=0.00025)指数雾两种形态;overrideMaterial配合Material#allowOverride实现"全局覆盖 + 局部豁免"的调试能力;copy()对纹理类属性做深克隆,toJSON()序列化雾与背景/环境调参,支撑场景文件还原。
所有行为的最终实现均可在 src/scenes/Scene.js、src/scenes/Fog.js、src/scenes/FogExp2.js、src/renderers/webgl/WebGLBackground.js 与 src/renderers/WebGLRenderer.js 中逐行查证。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考