three.js WebGPU 色彩分级:Lut3DNode 三维查找表(3D LUT)后处理节点全解析

📅 发布时间:2026/9/8 23:21:28
three.js WebGPU 色彩分级:Lut3DNode 三维查找表(3D LUT)后处理节点全解析
three.js WebGPU 色彩分级Lut3DNode 三维查找表3D LUT后处理节点全解析【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文围绕 three.js 节点材质与后处理体系中的Lut3DNode展开讲解如何用三维查找表Look-Up TableLUT对 WebGPU 渲染管线输出做专业级色彩分级color grading。你将学会它的导入方式、构造函数与 TSL 工厂函数lut3D()、节点属性、setup()背后的采样着色算法以及如何把它接入RenderPipeline后处理链并实时切换 LUT最终把商业 LUT 文件.CUBE/.3dl/ PNG变成可复用的电影感滤镜。一、Lut3DNode 是什么Lut3DNode是一个用于通过查找表进行色彩分级的后处理节点post processing node for color grading via lookup tables。它的类继承链为EventDispatcher → Node → TempNode → Lut3DNode本质是一个从 TempNode 派生的临时输出节点构造时声明输出类型为vec4即它返回 RGBA 颜色。源码位置examples/jsm/tsl/display/Lut3DNode.js静态类型标识Lut3DNode由static get type()返回所属目录examples/jsm/tsl/display/与FXAANode、BloomNode、SSAONode、SMAANode等 TSL 显示后处理节点并列与传统的把 LUT 硬编码进 ShaderMaterial方案不同Lut3DNode 被设计为 TSLThree Shading Language节点图的一部分你可以把它当作一条可编程后处理链中的一环输入任意颜色节点、输出一个vec4结果节点最终赋给RenderPipeline的outputNode。二、导入方式与使用前提Lut3DNode 属于插件模块addon必须显式导入import { lut3D } from three/addons/tsl/display/Lut3DNode.js;使用时需要引入对应的 WebGPU 构建与 TSL 工具函数。参考官方示例 examples/webgpu_postprocessing_3dlut.html 的 import map 配置script typeimportmap { imports: { three: ../build/three.webgpu.js, three/webgpu: ../build/three.webgpu.js, three/tsl: ../build/three.tsl.js, three/addons/: ./jsm/ } } /script在模块脚本中同时引入节点类、TSL 辅助函数与 LUT 加载器import * as THREE from three/webgpu; import { pass, renderOutput, texture3D, uniform } from three/tsl; import { lut3D } from three/addons/tsl/display/Lut3DNode.js; import { LUTCubeLoader } from three/addons/loaders/LUTCubeLoader.js; import { LUT3dlLoader } from three/addons/loaders/LUT3dlLoader.js;从源码结构看该节点被设计为配合 WebGPU 渲染器WebGPURenderer与RenderPipeline的新后处理栈使用manual/pages/webgpu-postprocessing.html其着色逻辑依赖three/tsl导出的nodeObject、Fn、float、uniform、vec3、vec4、mix等原语。三、构造函数与 TSL 工厂函数3.1 new Lut3DNode( inputNode, lutNode, size, intensityNode )new Lut3DNode( inputNode, lutNode, size, intensityNode )参数类型说明inputNodeNode表示效果输入通常是某一条 pass 的颜色输出节点lutNodeTextureNode表示查找表的纹理节点一般由texture3D()包装的 3D 纹理sizenumber查找表的尺寸即 N×N×N 立方体每边像素数intensityNodeNode.float控制效果强度0–1参与与原始颜色的mix对应源码构造过程Lut3DNode.jsconstructor( inputNode, lutNode, size, intensityNode ) { super( vec4 ); this.inputNode inputNode; this.lutNode lutNode; this.size uniform( size ); // number 被封装为 UniformNodefloat this.intensityNode intensityNode; }值得注意的细节size传入后立刻被uniform( size )封装为UniformNodefloat。这意味着size 可以像 uniform 一样在运行时被改写见下文运行时更新而无需重建节点——这正是示例中切换不同尺寸 LUT 的实现基础。3.2 TSL 工厂函数 lut3D()除了new构造源码文件底部还导出了一个同名便捷函数Lut3DNode.jsexport const lut3D ( node, lut, size, intensity ) new Lut3DNode( nodeObject( node ), nodeObject( lut ), size, nodeObject( intensity ) );它等价于自动把普通值包装成节点对象再构造其中intensity既可以是Nodefloat也可以是普通number内部经nodeObject归一。它在 docs/TSL.md 的 TSL 函数表中被记录为lut3D( node, lut, size, intensity ) → 创建 LUT 色彩分级效果在 TSL.html.md 中有完整 API 条目.lut3D( node : Node, lut : TextureNode, size : number, intensity : Node.float | number ) : Lut3DNode—— TSL function for creating a LUT node for color grading via post processing.推荐日常开发使用lut3D()写法更接近 TSL 惯用风格const lutPass lut3D( outputPass, texture3D( lut.texture3D ), lut.texture3D.image.width, uniform( 1 ) );四、属性详解属性类型含义.inputNodeNode效果输入节点.lutNodeTextureNode查找表纹理节点.sizeUniformNode.float查找表尺寸注意它是 uniform可运行时更新.intensityNodeNode.float效果强度控制对应源码中的type注释Lut3DNode.js即可确认。由于这些属性都被直接保留为公共字段而非 getter/setter 封装运行时可以直接对.value赋值来实时调节例如lutPass.intensityNode.value 0.6; // 降低分级强度 lutPass.lutNode.value newLutTexture; // 换一张 LUT lutPass.size.value newSize; // 同步 LUT 尺寸五、setup() 方法与内部着色算法setup( builder )方法用于组装该效果节点的 TSL 代码覆盖自 TempNode#setup.setup( builder : NodeBuilder ) : ShaderCallNodeInternal—— builder 为当前 NodeBuilderOverrides:TempNode#setup。从实现看Lut3DNode.js它并不接收 builder 参数而是直接返回一个由Fn()函数体调用的ShaderCallNodesetup() { const { inputNode, lutNode } this; const sampleLut ( uv ) lutNode.sample( uv ); const lut3D Fn( () { const base inputNode; // pull the sample in by half a pixel so the sample begins at // the center of the edge pixels. const pixelWidth float( 1.0 ).div( this.size ); const halfPixelWidth float( 0.5 ).div( this.size ); const uvw vec3( halfPixelWidth ).add( base.rgb.mul( float( 1.0 ).sub( pixelWidth ) ) ); const lutValue vec4( sampleLut( uvw ).rgb, base.a ); return vec4( mix( base, lutValue, this.intensityNode ) ); } ); const outputNode lut3D(); return outputNode; }这个函数体揭示了 3D LUT 色彩分级的核心采样算法共四步像素宽度归一化pixelWidth 1.0 / sizehalfPixelWidth 0.5 / size。size即 LUT 立方体边长例如 33、64。这里计算的是每个 LUT 格子在归一化 UV 空间中的宽度。坐标收缩与半像素内缩uvw halfPixelWidth base.rgb * (1.0 - pixelWidth)。注释明确说明这是为了把采样点向中心拉进半个像素使采样从边缘像素的中心开始避免因线性过滤而在 LUT 立方体的边界处采到越界/混合错误的颜色。从向量运算可以看出输入的 RGB 被当作三维采样坐标R→X、G→Y、B→Z分别映射到 3D 纹理的三个采样轴。采样与通道保留lutValue vec4( sampleLut( uvw ).rgb, base.a )。只取 LUT 的 RGB 作为分级后颜色Alpha 通道沿用输入base.a——即该节点只做颜色映射不影响透明。按强度混合mix( base, lutValue, intensityNode )。当强度为 0 时完全保留原色为 1 时完全使用 LUT 映射结果中间值获得渐变的滤镜浓度因此可以实现强度为 0–1 的平滑淡入淡出。六、端到端接入把 LUT 挂到 RenderPipeline 后处理链Lut3DNode 的典型使用场景是3D LUT 色彩分级官方为此提供了可运行示例 examples/webgpu_postprocessing_3dlut.html含咖啡杯烘焙场景 烟雾着色器并内置多组商业 LUT 供切换。6.1 关闭默认颜色变换用 renderOutput() 控制顺序manual/pages/webgpu-postprocessing.html 明确指出使用后处理时tone mapping 与色彩空间转换默认会在效果链末端自动应用如果要对 FXAA、Lut3DNode 做色彩分级应先关闭自动 tone mapping 与色彩空间转换再自行用renderOutput()编排顺序。示例中的关键代码const renderPipeline new THREE.RenderPipeline( renderer ); // ignore default output color transform ( toneMapping and outputColorSpace ) renderPipeline.outputColorTransform false; const scenePass pass( scene, camera ); const outputPass renderOutput( scenePass ); // 在这里先完成 tone mapping 色彩空间转换由于色彩分级通常作用于色调映射后的 sRGB 画面示例刻意让 LUT 节点作用在renderOutput()的结果之上。6.2 构建 LUT 后处理节点并挂到管线const lut lutMap[ params.lut ]; // 加载好的 LUT含 texture3D 与尺寸 lutPass lut3D( outputPass, // inputNoderenderOutput 的结果 texture3D( lut.texture3D ), // lutNode3D 纹理经 texture3D() 包装 lut.texture3D.image.width, // size取 3D 纹理宽度如 64 uniform( 1 ) // intensityNode初始强度 1 ); renderPipeline.outputNode lutPass; // 赋给管线输出对应的 TSL 一行式写法是lut3D( outputPass, texture3D( lut.texture3D ), lut.texture3D.image.width, uniform( 1 ) )示例源码。size直接取 3D 纹理的image.width因为加载器保证width height depth size。6.3 动画循环内实时更新后处理链建好后可以在每帧或交互时动态改写节点属性示例源码async function animate() { controls.update(); lutPass.intensityNode.value params.intensity; // GUI 拖动的强度 if ( lutMap[ params.lut ] ) { const lut lutMap[ params.lut ]; lutPass.lutNode.value lut.texture3D; // 切换不同 LUT lutPass.size.value lut.texture3D.image.width;// 尺寸随之更新 } renderPipeline.render(); }配合调试面板const gui renderer.inspector.createParameters( Settings ); gui.add( params, lut, Object.keys( lutMap ) ); gui.add( params, intensity, 0, 1 );即可在运行时从多组 LUT 间实时切换、平滑调节滤镜浓度。七、LUT 数据从哪来加载器与 Data3DTextureLut3DNode 需要一张真实的三维 LUT 纹理N×N×N 体素立方体three.js 提供三种加载器全部位于 examples/jsm/loaders/加载器适用格式说明LUTCubeLoader.CUBEAdobe/通用 Cube 格式文本解析后返回{ title, size, domainMin, domainMax, texture3D }LUT3dlLoader.3dlAutodesk 3D LUT 格式文本解析结果结构同上LUTImageLoader排列成网格的 LUT PNG 图片内部拆分/重建为 3D 纹理以LUTCubeLoader.parse()为例LUTCubeLoader.js最终会构造一个Data3DTextureconst texture3D new Data3DTexture(); texture3D.image.data data; texture3D.image.width size; texture3D.image.height size; texture3D.image.depth size; texture3D.type this.type; texture3D.magFilter LinearFilter; // 三线性插值保证分级过渡平滑 texture3D.minFilter LinearFilter; texture3D.wrapS ClampToEdgeWrapping; texture3D.wrapT ClampToEdgeWrapping; texture3D.wrapR ClampToEdgeWrapping; texture3D.generateMipmaps false; texture3D.needsUpdate true;这些纹理设置与 Lut3DNode 的采样算法高度配合LinearFilter的 min/mag让 GPU 对 LUT 体素做三线性插值这是色彩分级连续、平滑的关键三个轴全部ClampToEdgeWrapping配合半像素内缩坐标杜绝坐标越界不生成 mipmap避免 LOD 导致的颜色串扰。官方示例中的 LUT 素材存放在 examples/luts/ 目录例如Bourbon 64.CUBE64³、Chemical 168.CUBE168³、Clayton 33.CUBE33³、Cubicle 99.CUBE99³、Remy 24.CUBE24³以及.3dl格式的Presetpro-Cinematic.3dl等。可见size 没有固定值随 LUT 文件内容而定这也是为什么size需要显式传参、并作为可写 uniform 暴露。八、项目内的高级使用Inspector 色彩分级扩展在仓库的 examples/jsm/inspector/extensions/color-grading/ColorGrading.js 中可以看到 Lut3DNode 在编辑器工具内的另一类用法——把整个 Inspector 面板的调色流程白平衡、曝光、色轮、曲线、饱和度、对比度等最终落地为一张 LUT 并交给lut3D()处理import { lut3D } from three/addons/tsl/display/Lut3DNode.js; // ... this.lutSize 32; // 默认生成 32³ 的 LUT this.lutPassNode null;并通过对RenderPipeline.prototype.render的 Hook 记录当前 pipeline 后把lut3D节点写入管线输出。这印证了 Lut3DNode 的两个设计取向适合作为整条调色链的最后一公里——各种颜色运算先烘焙进一张 LUT 纹理再由 GPU 用一次三线性采样完成分级性能极高size 语义统一——无论是32³的运行时生成 LUT还是64³的外部.CUBE文件都通过同一个sizeuniform 驱动。九、小结与排错提示检查项建议值/做法导入路径three/addons/tsl/display/Lut3DNode.js仅导出lut3D工厂函数必须给lutNode传3D 纹理用texture3D( lut.texture3D )包装Data3DTexture二维 LUT 贴图不适用size必须与 LUT 一致直接取lut.texture3D.image.width切换 LUT 时同步更新size.value强度调节修改intensityNode.value0–1内部以mix()实现渲染顺序建议outputColorTransform falserenderOutput()让 LUT 作用在色调映射后的颜色上兼容性前提面向 WebGPU 渲染器与 TSL/RenderPipeline 后处理体系不支持传统 WebGLRenderer 的EffectComposer进一步参考节点源码examples/jsm/tsl/display/Lut3DNode.js官方示例examples/webgpu_postprocessing_3dlut.html后处理手册manual/pages/webgpu-postprocessing.htmlTSL API 索引docs/pages/TSL.html.md函数表见 docs/TSL.mdLUT 加载器LUTCubeLoader、LUT3dlLoader、LUTImageLoader调试扩展实现ColorGrading.js【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考