拓冰建站拓冰建站
首页 / 资讯中心 / 正文

three.js CubeCamera 深入解析:用立方体相机实时捕获环境反射的完整实现

three.js CubeCamera 深入解析用立方体相机实时捕获环境反射的完整实现【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsCubeCamera 是 three.js 中用于「实时环境捕获」的特殊相机它被放置在 3D 空间中的某个位置将周围的场景渲染进一个立方体渲染目标Cube Render Target再将该目标作为环境贴图Environment Map驱动场景中的实时反射。阅读本文后你将掌握 CubeCamera 的构造参数、update()六面渲染流程、activeMipmapLevel与coordinateSystem两个进阶属性以及从源码层面理解六台子相机的排布、WebGL/WebGPU 坐标系切换与 PMREM 更新机制。一、CubeCamera 定位实时环境反射的核心组件CubeCamera继承自EventDispatcher → Object3D见 src/cameras/CubeCamera.js它本身不是一个独立的可视相机而是一个「容器」——内部包含 6 台PerspectiveCamera分别朝 X、-X、Y、-Y、Z、-Z 六个方向观察把整个 360° 视野拍进WebGLCubeRenderTarget的六个面中。典型应用场景包括实时反射材质铬合金车身、镜面球等把动态场景映射到envMap上环境贴图动态化将背景立方体贴图实时重采样供PMREMGenerator处理LightProbe 生成先把场景拍成立方体贴图再用LightProbeGenerator.fromCubeRenderTarget()转为光照探针。官方文档代码示例文档给出的标准用法来自 src/cameras/CubeCamera.js 的 JSDoc 注释与官方 API 文档一致// 1. 创建立方体渲染目标开启 mipmap用于平滑的环境采样 const cubeRenderTarget new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); // 2. 创建立方体相机near1, far100000 const cubeCamera new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); scene.add( cubeCamera ); // 3. 创建一辆「镀铬」汽车把立方体贴图作为环境贴图 const chromeMaterial new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); const car new THREE.Mesh( carGeometry, chromeMaterial ); scene.add( car ); // 4. 更新渲染目标先隐藏汽车自身再把相机移到汽车位置拍摄 car.visible false; cubeCamera.position.copy( car.position ); cubeCamera.update( renderer, scene ); // 5. 恢复正常渲染 car.visible true; renderer.render( scene, camera );这段代码中有三个关键细节generateMipmaps: trueLinearMipmapLinearFilter环境贴图在不同距离/粗糙度下会被以不同 mipmap 级别采样开启 mipmap 后反射过渡更平滑这也是示例特意声明这两项配置的原因car.visible false拍摄时必须把「会被反射的物体本身」隐藏否则它会拍到自己在自己内部的投影产生错误的自反射cubeCamera.position.copy( car.position )相机位置即反射观察点放在物体中心才能让反射角度与物理镜像一致。二、构造函数near、far 与 renderTargetnew CubeCamera( near : number, far : number, renderTarget : WebGLCubeRenderTarget )参数类型说明nearnumber六台子相机的近裁剪平面距离相机小于该值的几何体不会被拍入立方体贴图farnumber六台子相机的远裁剪平面决定反射「能看多远」示例中常用1000甚至100000以覆盖大场景renderTargetWebGLCubeRenderTarget渲染目标其.texture即可直接赋给材质的envMap从源码看src/cameras/CubeCamera.js构造过程做了一件很巧妙的事const fov - 90; // negative fov is not an error const aspect 1;6 台子相机全部使用负值 fov -90、aspect 1构造。负 fov 在 three.js 中是合法的它表示视角方向被反转配合子相机朝六个轴向的lookAt使每台相机正好覆盖该轴向的一个 90° 视锥六台拼起来恰好无缝铺满整个立方体内部视野且各面之间不重叠、不遗漏。const cameraPX new PerspectiveCamera( fov, aspect, near, far ); cameraPX.layers this.layers; this.add( cameraPX ); // … cameraNX / cameraPY / cameraNY / cameraPZ / cameraNZ 同理注意cameraPX.layers this.layers每台子相机的图层直接引用CubeCamera 的layers对象。这意味着通过cubeCamera.layers.enable( n )控制可见图层时六台子相机会同步生效——这正是官方示例中实现「选择性捕获」比如只拍部分物体做镜像效果的底层支撑。三、属性详解3.1.activeMipmapLevel : number默认0当前激活的 mipmap 级别。update()内部会将renderer.setRenderTarget( renderTarget, face, activeMipmapLevel )的第三个参数设为该值从而决定本次渲染写入哪个 mipmap 层。仓库中的实战用例在 examples/webgl_materials_cubemap_render_to_mipmaps.html它把一张高分辨率立方体贴图逐层手动降采样循环里同时调整 viewport 与 mipmap 级别const cubeCamera new THREE.CubeCamera( 1, 10, cubeMapRenderTarget ); for ( let mipmap 0; mipmap mipmapCount; mipmap ) { material.uniforms.mipIndex.value mipmap; material.needsUpdate true; // 缩小视口到 1/2^mipmap cubeMapRenderTarget.viewport.set( 0, 0, cubeMapRenderTarget.width mipmap, cubeMapRenderTarget.height mipmap ); cubeCamera.activeMipmapLevel mipmap; // 关键写入目标 mipmap 层 cubeCamera.update( renderer, mesh ); }这是activeMipmapLevel的典型用法在硬件 mipmap 生成不可靠或需要自定义降采样过滤如 CubemapFilterShader时用 CubeCamera 逐层重采样整个立方体。3.2.coordinateSystem : WebGLCoordinateSystem | WebGPUCoordinateSystem默认null当前激活的坐标系约定。由于 WebGL 与 WebGPU 在剪贴空间 Z 方向上约定不同z ∈ [0, 1]vsz ∈ [-1, 1]CubeCamera 需要知道自己在哪个渲染后端下工作才能给六台子相机设置正确的朝向。该属性默认null首次update()时会自动同步为renderer.coordinateSystem之后若切换渲染器例如从 WebGLRenderer 换到 WebGPURenderer会自动检测到差异并重新调用updateCoordinateSystem()修正朝向。3.3.renderTarget : WebGLCubeRenderTarget对立方体渲染目标的引用。常用派生操作读取贴图cubeCamera.renderTarget.texture赋给material.envMap或scene.background尺寸renderTarget.width / height构造时传入的size如new WebGLCubeRenderTarget( 256 )即为 256×256 每面清理renderTarget.clear( renderer )会逐面清空颜色、深度与模板缓冲src/renderers/WebGLCubeRenderTarget.js。WebGLCubeRenderTarget本身继承自WebGLRenderTarget构造时会创建一个带isRenderTargetTexture true标记的CubeTexturesrc/renderers/WebGLCubeRenderTarget.js。源码注释解释了这一标记的由来按 RenderMan 传统约定立方体贴图使用左手系描述 px/nx 面而 three.js 场景是右手系isRenderTargetTexture让渲染器在采样时自动完成 px/nx 的交换保证外部加载的旧式立方体贴图与实时渲染的立方体贴图都能正确显示。四、方法深度解析4.1.update( renderer, scene )用给定渲染器把场景渲染进立方体渲染目标。参数rendererRenderer | WebGLRendererscene要拍摄的Scene。从源码实现看src/cameras/CubeCamera.jsupdate()内部流程可拆解为五步update( renderer, scene ) { if ( this.parent null ) this.updateMatrixWorld(); // ① 未入场景时手动刷新矩阵 const { renderTarget, activeMipmapLevel } this; if ( this.coordinateSystem ! renderer.coordinateSystem ) { this.coordinateSystem renderer.coordinateSystem; this.updateCoordinateSystem(); // ② 坐标系变更时重排子相机 } const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] this.children; // ③ 保存现场并做环境快照 const currentRenderTarget renderer.getRenderTarget(); const currentActiveCubeFace renderer.getActiveCubeFace(); const currentActiveMipmapLevel renderer.getActiveMipmapLevel(); const currentXrEnabled renderer.xr.enabled; renderer.xr.enabled false; // 关闭 XR避免立体渲染干扰六面拍摄 const generateMipmaps renderTarget.texture.generateMipmaps; renderTarget.texture.generateMipmaps false; // ④ 前 5 面禁用 mipmap 生成 // ⑤ 依次渲染 6 个面face 0~4 renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); renderer.render( scene, cameraPX ); // … face 1/2/3/4 对应 NX / PY / NY / PZ // 最后一面恢复 mipmap 开关mipmap 在最后一次 render() 调用时生成 renderTarget.texture.generateMipmaps generateMipmaps; renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); renderer.render( scene, cameraNZ ); // 恢复渲染器状态 renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); renderer.xr.enabled currentXrEnabled; renderTarget.texture.needsPMREMUpdate true; // 通知 PMREM 需要重新生成 }其中值得展开的几个机制mipmap 延迟生成update()会临时把renderTarget.texture.generateMipmaps置为false前 5 个面渲染完后再恢复。源码注释说明了原因mipmap 是在最后一次render()调用时统一生成的此时立方体的六个面都已就绪mipmap 链才完整。若六面都开着自动生成中间面会基于尚未渲染完成的数据计算 mipmap。反转深度缓冲reversed depth buffer兼容若渲染器处于反转深度模式且autoClear false每个面渲染前会先clearDepth()避免上一次面的深度数据污染当前面。状态保存/恢复update()是「有副作用的调用」——它会覆盖当前渲染目标与 XR 状态并在结束前完整恢复。因此它可以安全地嵌在任何渲染循环中例如先cubeCamera.update()再renderer.render( scene, camera )。needsPMREMUpdate true每次更新后都会触发。对应 src/textures/Texture.js 中的 setterset needsPMREMUpdate( value ) { if ( value true ) { this.pmremVersion ; // 版本号递增PMREMGenerator 据此判断需要重新处理 } }这解释了为什么使用PMREMGenerator消费 CubeCamera 结果时每次场景变化后 PMREM 都会自动重新生成——机制靠的就是这个版本号。此外WebGLCubeRenderTarget.fromEquirectangularTexture()内部同样复用了 CubeCamerasrc/renderers/WebGLCubeRenderTarget.js用一个CubeCamera( 1, 10, this )拍摄一个背面着色、按等距圆柱坐标采样片元的盒子从而把等距圆柱全景图转换为立方体贴图。这印证了 CubeCamera 在 three.js 内部是「一切立方体捕获」的通用基础设施。4.2.updateCoordinateSystem()当 CubeCamera 的坐标系约定发生变化时必须手动调用。它根据this.coordinateSystem的值重排六台子相机的up与lookAt方向src/cameras/CubeCamera.jsWebGLCoordinateSystemcameraPX.up.set( 0, 1, 0 ); cameraPX.lookAt( 1, 0, 0 ); cameraNX.up.set( 0, 1, 0 ); cameraNX.lookAt( - 1, 0, 0 ); cameraPY.up.set( 0, 0, - 1 ); cameraPY.lookAt( 0, 1, 0 ); cameraNY.up.set( 0, 0, 1 ); cameraNY.lookAt( 0, - 1, 0 ); cameraPZ.up.set( 0, 1, 0 ); cameraPZ.lookAt( 0, 0, 1 ); cameraNZ.up.set( 0, 1, 0 ); cameraNZ.lookAt( 0, 0, - 1 );WebGPUCoordinateSystemX 面朝向对调PX 看 -1,0,0、所有up相应翻转以适配 WebGPU 的剪贴空间与 Y 轴约定。其他值直接抛出Invalid coordinate system错误。由于update()已经内置了坐标系一致性检查与自动重排绝大多数场景下不需要显式调用updateCoordinateSystem()手动调用主要出现在「先设定coordinateSystem再渲染」的初始化序列或跨渲染器迁移场景中。五、典型实战模式5.1 动态环境反射每帧或按需更新仓库示例 examples/webgl_materials_cubemap_dynamic.html 展示了最小化的动态反射流程创建WebGLCubeRenderTarget( 256 )与CubeCamera( 1, 1000, cubeRenderTarget )在controls的change回调里调用cubeCamera.update( renderer, scene )把cubeRenderTarget.texture挂到材质的envMap上。注意其 near 取1而非文档示例的1、far 取1000——near/far 的具体数值应贴合场景尺度过小会丢远处反射过大则压缩深度精度。5.2 从立方体贴图生成 LightProbeexamples/webgl_lightprobe_cubecamera.html 是一条完整的「几何→光照」管线const cubeRenderTarget new THREE.WebGLCubeRenderTarget( 256 ); cubeCamera new THREE.CubeCamera( 1, 1000, cubeRenderTarget ); // … cubeCamera.update( renderer, scene ); // 先把场景含天空盒背景拍成立方体贴图 const probe await LightProbeGenerator.fromCubeRenderTarget( renderer, cubeRenderTarget ); lightProbe.copy( probe ); scene.add( new LightProbeHelper( lightProbe, 5 ) );这里 CubeCamera 承担的是「离屏环境采样」角色把当前场景的漫反射信息固化为 256×256 的立方体贴图再交由LightProbeGenerator分解成球谐系数SH最终让 PBR 材质获得基于几何的间接光照。同目录下的 WebGPU 版本 examples/webgpu_lightprobe_cubecamera.html 与 examples/webgpu_cubemap_dynamic.html 验证了同一套 API 在 WebGPU 渲染器下的等价性——update()内部会根据renderer.coordinateSystem自动切换到 WebGPU 朝向业务代码无需改动。5.3 镜像/反射球效果examples/webgl_animation_skinning_ik.html 中用new THREE.CubeCamera( 0.05, 50, cubeRenderTarget )给球体做局部镜像near 收到0.05说明镜像观察点紧贴球体表面far 只需50覆盖局部即可。这个参数组合是「近距离局部反射」的推荐写法与全景反射near 1 / far 上千形成对照。六、测试与继承验证单元测试位于 test/unit/src/cameras/CubeCamera.tests.js验证了三条基本契约CubeCamera可实例化new CubeCamera() instanceof Object3D true继承链EventDispatcher → Object3Dobject.type CubeCamera类型标记供序列化与渲染器类型分发使用。七、实践要点总结参数速查new CubeCamera( near, far, renderTarget )near/far 按反射可见范围取renderTarget 尺寸128~512按反射精细度权衡显存与耗时。拍摄时机在renderer.render()之前调用cubeCamera.update( renderer, scene )update()会自动保存并恢复渲染器状态但拍摄对象自身需要手动visible false。mipmap 配置用于 PBR 环境反射时建议generateMipmaps: trueLinearMipmapLinearFilterupdate()内部对 mipmap 生成时机的处理已经保证了六面完整性。跨渲染器WebGL 与 WebGPU 渲染器下 API 完全一致坐标系差异由coordinateSystem属性与updateCoordinateSystem()自动处理。进阶activeMipmapLevel支持手工逐层重采样立方体见 examples/webgl_materials_cubemap_render_to_mipmaps.htmllayers的引用共享支持选择性捕获needsPMREMUpdate机制保证 PMREM 缓存与实时立方体贴图同步失效。核心源码与资料索引实现src/cameras/CubeCamera.js渲染目标src/renderers/WebGLCubeRenderTarget.jsPMREM 失效机制src/textures/Texture.js单元测试test/unit/src/cameras/CubeCamera.tests.js实战示例examples/webgl_lightprobe_cubecamera.html、examples/webgl_materials_cubemap_dynamic.html、examples/webgl_shaders_sky.html【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门