ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

three.js LightProbeGenerator 深度解析:从立方体环境贴图生成光照探针(Light Probe)

three.js LightProbeGenerator 深度解析:从立方体环境贴图生成光照探针(Light Probe) three.js LightProbeGenerator 深度解析从立方体环境贴图生成光照探针Light Probe【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsLightProbeGenerator 是 three.js 提供的实用工具类用于把以立方体环境贴图cube map形式存在的 radiance 环境光照数据重编码为可直接放入场景的三阶球谐SH光照探针LightProbe。本文基于其官方 API 文档LightProbeGenerator.html.md结合 源码实现 与仓库内的四个官方示例完整讲解两个静态方法的使用前提、参数细节、底层球谐投影算法与可运行的接入代码。阅读完本文后你将掌握如何用一张 HDR/LDR 立方体贴图在数毫秒内生成漫反射光照探针fromCubeTexture如何在运行时用 CubeCamera 实时捕捉场景并异步生成动态探针fromCubeRenderTarget以及 WebGL / WebGPU 两种渲染后端下的差异与注意事项。LightProbeGenerator 是什么光照探针的“编码器”在 three.js 中LightProbe 是一类特殊光源它不直接发光而是把“穿过三维空间的光照信息”预先编码起来渲染时用探针数据近似计算打到物体上的光。正如 LightProbe 官方文档 所述three.js 当前实现的是漫反射光照探针diffuse light probe其功能等价于一张辐照度环境贴图irradiance environment map。LightProbeGenerator正是 LightProbe 的“编码器”输入是一张已包含场景辐射亮度radiance的立方体环境贴图输出是一个可直接scene.add()的LightProbe实例。它对外仅暴露两个静态方法分别处理两种常见的数据载体静态方法输入载体返回fromCubeTexture( cubeTexture )已加载的CubeTexture离线贴图LightProbe同步fromCubeRenderTarget( renderer, cubeRenderTarget )CubeRenderTarget/WebGLCubeRenderTarget离线或运行时渲染的渲染目标Promise.LightProbe异步它属于 examples 下的addon附加组件而非 three.js 核心构建产物。除 源码本身 外它也被聚合导出到 examples/jsm/Addons.jsexport * from ./lights/LightProbeGenerator.js因此可通过统一的three/addons/命名空间导入。Importaddon 必须显式导入由于 LightProbeGenerator 不在核心模块中使用前必须像其他 addon 一样显式导入import { LightProbeGenerator } from three/addons/lights/LightProbeGenerator.js;仓库内的四个官方示例均使用这一写法例如 webgl_lightprobe.htmlWebGL、webgpu_lightprobe.htmlWebGPU、webgl_lightprobe_cubecamera.html 与 webgpu_lightprobe_cubecamera.html。静态方法详解.fromCubeRenderTarget( renderer, cubeRenderTarget ) : Promise. 异步从指定的 radiance 环境贴图创建光照探针要求环境贴图以立方体渲染目标cube render target表示static async fromCubeRenderTarget( renderer, cubeRenderTarget ) { // ... return new LightProbe( sh ); }参数约定rendererWebGPURenderer | WebGLRenderer。源码中通过renderer.coordinateSystem WebGLCoordinateSystem ? -1 : 1源码计算坐标翻转因子并通过renderer.isWebGLRenderer区分两个后端的像素读取路径因此两个渲染器都受支持。cubeRenderTarget环境贴图。该立方体渲染目标的纹理必须为 RGBA 格式即需保证cubeRenderTarget.texture.format为RGBAFormat——这是为了让内部的readRenderTargetPixels类像素回读能正常工作源码注释亦明确说明见 源码。返回值一个 Promiseresolve 后得到创建好的LightProbe。纹理数据类型适配方法读取cubeRenderTarget.texture.type并针对三种类型分别解码像素源码texture.type读取的数组类型像素解码FloatTypeFloat32Array直接取r/g/b浮点值HalfFloatTypeUint16Array经DataUtils.fromHalfFloat()还原浮点其他默认视为UnsignedByteTypeUint8Array除以 255 归一化到[0,1]若当前为UnsignedByteType即 LDR 数据内部会先做线性化处理。像素回读WebGL 后端走renderer.readRenderTargetPixelsAsync( cubeRenderTarget, 0, 0, width, height, data, faceIndex )WebGPU 后端则将第 6 个参数固定为 0、以第 7 个参数传入faceIndex源码。这正是该方法需要async并返回 Promise 的原因——它依赖一次异步的 GPU→CPU 像素回读。.fromCubeTexture( cubeTexture : CubeTexture ) : LightProbe从指定的 radiance 立方体纹理创建光照探针返回同步创建的LightProbestatic fromCubeTexture( cubeTexture ) { // ... return new LightProbe( sh ); }cubeTexture环境贴图。此时像素数据已经存在于 CPU 侧的cubeTexture.image[faceIndex]每面一张ImageBitmap/HTMLImageElement等。方法内部通过临时canvas与2d上下文drawImage/getImageData取出每面像素源码因此它本质上是 CPU 同步计算适合离线烘焙、贴图加载完成后一次性生成探针的场景。两条方法在核心算法上完全一致下面详述差异仅在于“如何拿到 6 个面的原始像素”一个来自渲染目标回读一个来自贴图解码。底层原理把立方体贴图“投影”为三阶球谐系数两个方法最终都执行同一套 SH 投影流水线。理解它能帮你判断何时可用、结果精度如何。代码开头注释引用了 Peter-Pike Sloan 关于 SH 编码的经典讲义StupidSH36见 源码核心流程如下① 对 6 个面逐像素遍历每个像素按 RGBA 取色并线性化color.setRGB( data[ i ] / 255, data[ i 1 ] / 255, data[ i 2 ] / 255 ); convertColorToLinear( color, cubeTexture.colorSpace );线性化由模块内私有函数convertColorToLinear完成源码当色彩空间为SRGBColorSpace时调用convertSRGBToLinear()LinearSRGBColorSpace与NoColorSpace则原样通过遇到其余色彩空间会输出console.warn警告。这意味着LDR 的 sRGB 立方体贴图会被正确线性化后再参与球谐累加避免探针偏暗。② 把像素坐标映射为单位立方体上的方向并计算像素权重。球谐在球面上做积分因此每个像素必须按其“立体角”加权。源码用透视投影近似立体角权重源码const lengthSq coord.lengthSq(); const weight 4 / ( Math.sqrt( lengthSq ) * lengthSq ); totalWeight weight; dir.copy( coord ).normalize();其中coord由像素的行列索引经col -1 (pixelIndex % imageWidth 0.5) * pixelSize、row 1 - (floor(pixelIndex / imageWidth) 0.5) * pixelSize得到再按 6 个面各自的朝向case 0..5摆放到单位立方体表面。立方体贴图纹理本身被假定为正方形imageWidth取自单边宽高源码注释 “assumed to be square”见 源码。③ 在方向dir上求值三阶 SH 基函数并加权累加 9 个系数SphericalHarmonics3.getBasisAt( dir, shBasis ); for ( let j 0; j 9; j ) { shCoefficients[ j ].x shBasis[ j ] * color.r * weight; shCoefficients[ j ].y shBasis[ j ] * color.g * weight; shCoefficients[ j ].z shBasis[ j ] * color.b * weight; }三阶球谐共 9 个系数band 0~2分别存于 SphericalHarmonics3.coefficients 的 9 个Vector3中。基础方向求值由三阶 SH 的解析公式给出getBasisAt可参考 SphericalHarmonics3因此整个投影只需做像素遍历与累加无需解线性方程组。④ 归一化并返回 LightProbeconst norm ( 4 * Math.PI ) / totalWeight; // 对全部 9 个系数乘以 norm ... return new LightProbe( sh );4π是整球立体角除以totalWeight得到平均化因子。最终产出的LightProbe以sh编码光照方向分布信息其intensity默认值为1可参考 LightProbe 构造函数。坐标系与后端差异WebGL vs WebGPU从 源码 可以看出一个容易忽略的坑立方体贴图的面序/轴向约定与渲染后端相关。方法开头的flip因子即用于此const flip renderer.coordinateSystem WebGLCoordinateSystem ? - 1 : 1;随后映射coord时按flip翻转向量例如 WebGL 后端case 0用coord.set( -1 * flip, row, col * flip )见 源码。因此在接入自定义渲染管线时务必保证传给fromCubeRenderTarget的 renderer 与你实际使用的渲染器是同一个实例否则方向翻转错误会导致探针光照“左右/前后颠倒”的诡异效果。实战一用 CubeTexture 生成静态探针fromCubeTexture官方示例 webgl_lightprobe.html 展示了最典型的离线用法加载一张多面 Pisa 立方体贴图 → 生成探针 → 用标准材质观察光照。核心片段如下import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { LightProbeGenerator } from three/addons/lights/LightProbeGenerator.js; import { LightProbeHelper } from three/addons/helpers/LightProbeHelper.js; // 准备场景、相机、渲染器此处省略 lightProbe new THREE.LightProbe(); scene.add( lightProbe ); // 构造 6 面 URLpx/nx/py/ny/pz/nz const urls genCubeUrls( textures/cube/pisa/, .png ); new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) { scene.background cubeTexture; // 立方体贴图同时作为背景 lightProbe.copy( LightProbeGenerator.fromCubeTexture( cubeTexture ) ); lightProbe.intensity 1.0; // 注意lightProbe.position 不参与场景光照计算仅 LightProbeHelper 跟随其位置 const material new THREE.MeshStandardMaterial( { color: 0xffffff, metalness: 0, roughness: 0, envMap: cubeTexture, // 材质仍可使用原贴图做高光反射 envMapIntensity: 1 } ); scene.add( new THREE.Mesh( new THREE.SphereGeometry( 5, 64, 32 ), material ) ); // 可视化探针一个用 SH 系数实时着色的球体 scene.add( new LightProbeHelper( lightProbe, 1 ) ); renderer.render( scene, camera ); } );关键点返回值直接lightProbe.copy(...)即可LightProbe.copy内部会复制sh系数见 LightProbe.copy。探针只编码漫反射低频光照镜面高光仍依赖envMap。示例中材质同时设置了envMap与探针正是“探针补漫反射、envMap 补反射”的经典组合。用 GUI 同时调节lightProbeIntensity、directionalLightIntensity与envMapIntensity可以直观对比三种光照来源见 webgl_lightprobe.html。该示例对应 WebGPU 版本为 webgpu_lightprobe.htmlfromCubeTexture的调用方式完全一致——因为它不依赖渲染器。实战二运行时用 CubeCamera 捕捉动态探针fromCubeRenderTarget如果需要探针随场景内容变化例如移动光源、动态物体、切换背景可以用 CubeCamera 每帧/每段间隔把场景渲染进立方体渲染目标再异步生成探针。官方示例 webgl_lightprobe_cubecamera.html 的做法import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { LightProbeGenerator } from three/addons/lights/LightProbeGenerator.js; import { LightProbeHelper } from three/addons/helpers/LightProbeHelper.js; // 1. 创建立方体渲染目标与 CubeCamera const cubeRenderTarget new THREE.WebGLCubeRenderTarget( 256 ); cubeCamera new THREE.CubeCamera( 1, 1000, cubeRenderTarget ); // 2. 场景先准备一个空的 LightProbe lightProbe new THREE.LightProbe(); scene.add( lightProbe ); // 3. 立方体贴图加载完成后先把场景渲染到 cube 渲染目标 new THREE.CubeTextureLoader().load( urls, async function ( cubeTexture ) { scene.background cubeTexture; cubeCamera.update( renderer, scene ); // 将场景渲染进 cubeRenderTarget6 个面 // 4. 异步回读 6 个面的像素并生成探针 const probe await LightProbeGenerator.fromCubeRenderTarget( renderer, cubeRenderTarget ); lightProbe.copy( probe ); scene.add( new LightProbeHelper( lightProbe, 5 ) ); renderer.render( scene, camera ); } );WebGPU 版本 webgpu_lightprobe_cubecamera.html 的差异点渲染目标为 WebGPU 侧的CubeRenderTarget对应 src/renderers/common/CubeRenderTarget.js二者都继承自RenderTarget并创建 6 面CubeTexture调用cubeCamera.update( renderer, scene )前需先await renderer.init()见 webgpu_lightprobe_cubecamera.html确保 GPU 上下文就绪。适用前提与限制可从 WebGLCubeRenderTarget 源码 确认渲染目标纹理格式需为 RGBA默认即如此fromCubeRenderTarget会依据texture.type是FloatType/HalfFloatType/UnsignedByteType选择对应解码分支推荐用半浮点以兼顾动态范围与带宽渲染目标被假定为正方形示例用 256×256该路径包含一次异步 GPU→CPU 回读readRenderTargetPixelsAsync属于阻塞型操作适合低频更新环境明显变化时触发不建议每帧在移动端执行。让探针“看得见”LightProbeHelper 可视化调试时可通过 helper 直观检查探针编码是否正确。WebGL 侧用 examples/jsm/helpers/LightProbeHelper.jsconst helper new LightProbeHelper( lightProbe, 5 ); // 第二个参数为球体大小 scene.add( helper );其原理是用一个ShaderMaterial球体在片元着色器中通过法线求值 9 个 SH 系数的辐照度shGetIrradianceAt再乘intensity输出为颜色见 LightProbeHelper 着色器。球体每个方向上的颜色即是该方向探针“感受到”的辐照度帮助判断贴图方向、翻转与强度是否正确。该类文档注释指出其仅适用于 WebGLRendererWebGPU 需改用LightProbeHelperGPU.js变体且辅助球体会跟随lightProbe.position——但如前所述探针位置本身不参与漫反射光照计算只影响 helper 的摆放。总结与选型建议综合文档与源码LightProbeGenerator的本质是一段“cube map → 三阶球谐”的 CPU/异步投影器对外收敛为两个方法贴图已离线就绪、无需回读 GPU→ 用fromCubeTexture同步、零 GPU 依赖适合静态场景、固定 HDR 环境需要烘焙当前实时场景/动态切换环境→ 用fromCubeRenderTarget异步、跨 WebGL/WebGPU配合CubeCamera每帧/按需更新代价是一次像素回读无论哪种路径都要求输入为radiance 立方体贴图、RGBA 格式、正方形尺寸且fromCubeRenderTarget必须传入与渲染管线一致的那个 renderer 实例以保证坐标系正确。生成后的LightProbe本质是辐照度环境贴图的球谐等价物适合为 PBR 材质补充全局漫反射间接光尤其适合烘焙探针后离线复用、或通过 WebXR 拿到外部光照估计数据做真实感 AR。想深入探索时可以从三处继续阅读仓库源码LightProbeGenerator 投影实现、LightProbe 光源对象、以及 SphericalHarmonics3 数学封装。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表