OutputShader 实战解析:为 three.js 后期处理链加入色调映射与色彩空间转换 📅 发布时间:2026/9/9 23:07:44 👁 浏览次数: OutputShader 实战解析为 three.js 后期处理链加入色调映射与色彩空间转换【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文聚焦 three.js 的 addon 着色器模块 OutputShader。它是一段可复用的全屏 GLSL 着色器描述专为“FX 后期处理工作流”的收尾阶段设计负责把线性 HDR 场景颜色经过色调映射tone mapping与色彩空间转换color space conversion后安全地输出到屏幕。阅读本文后你将掌握 OutputShader 的导入方式、uniform 与着色器内部结构、对渲染器色调映射/色彩空间设置的响应机制以及如何在 EffectComposer 后处理链中通过 OutputPass 正确使用它避免颜色过暗、过饱和或双重复用导致的问题。OutputShader 是什么后处理链的“收尾着色器”在常规直接渲染不使用 EffectComposer时three.js 的 WebGLRenderer 会在最终绘制阶段见 src/renderers/webgl/WebGLOutput.js自动完成色调映射与色彩空间转换。但一旦引入 EffectComposer 组成多 Pass 后处理链场景通常先被渲染到浮点类型的 RenderTarget 中绕过了渲染器自带输出阶段因此需要在链的末端主动补上这一步。OutputShader 正是这个角色官方注释将其定位为 “Performs tone mapping and color space conversion for FX workflows”即面向后期处理/FX 场景、以独立着色器形态复用的“色调映射 色彩空间转换”实现。它被输出 PassOutputPass使用而后者是 docs 中推荐的、位于每个后处理链末尾的标准收尾 Pass。OutputShader.js 的核心事实如下可在源码中直接验证它是一个常量对象const OutputShader { ... }并非ShaderMaterial实例而是ShaderMaterial ~ Shader形态的着色器描述shader object包含name、uniforms、vertexShader、fragmentShader四个字段其name为字符串OutputShader它通过 examples/jsm/Addons.js 的统一export * from ./shaders/OutputShader.js通道对外发布属于 examples/jsm 下的 addon 生态。Addon 导入方式OutputShader 与核心库分离属于 three.js 的 addon附加模块需要显式从three/addons/别名导入。项目中的实际模块路径为 examples/jsm/shaders/OutputShader.jsimport { OutputShader } from three/addons/shaders/OutputShader.js;绝大多数实际项目中你不会直接使用这个裸着色器对象而是搭配 OutputPassimport { OutputPass } from three/addons/postprocessing/OutputPass.js;uniforms 与着色器结构OutputShader 只依赖两个 uniformOutputShader.jsuniform默认值含义tDiffusenull上一 Pass 输出的场景纹理readBuffer作为输入源toneMappingExposure1曝光值色调映射前乘到颜色上与renderer.toneMappingExposure对应顶点着色器是标准的全屏四边形直通实现仅把uv传给片元阶段用modelViewMatrix/projectionMatrix处理position无任何额外逻辑。片元着色器是本模块的核心逻辑分三段从tDiffuse按vUv采样得到颜色通过#ifdef / #elif预编译分支根据外部注入的宏选择色调映射函数详见下一节若定义了SRGB_TRANSFER则执行 sRGB OETF 编码把线性颜色转为显示器的非线性 sRGB 信号。着色器通过两个#include复用渲染器内建的着色器 Chunk#include tonemapping_pars_fragment #include colorspace_pars_fragment这两个 Chunk 由 src/renderers/shaders/ShaderChunk.js 统一注册tonemapping_pars_fragment位于 src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl.jscolorspace_pars_fragment位于 src/renderers/shaders/ShaderChunk/colorspace_pars_fragment.glsl.js因此 OutputShader 内部的函数体与 WebGLRenderer 内置输出阶段完全一致不会出现颜色算法差异。支持的色调映射模式与底层算法片元着色器的色调映射段使用宏驱动分支OutputShader.js。OutputPass 会根据renderer.toneMapping的取值决定注入哪个宏二者对应关系如下常量定义见 src/constants.jsrenderer.toneMapping 取值注入宏调用的 GLSL 函数算法特征NoToneMapping默认值 0不注入无不做色调映射直接透传LinearToneMapping1LINEAR_TONE_MAPPINGLinearToneMapping()仅做曝光截断saturate(exposure * color)ReinhardToneMapping2REINHARD_TONE_MAPPINGReinhardToneMapping()color/(1color)高光软压缩、整体偏灰CineonToneMapping3CINEON_TONE_MAPPINGCineonToneMapping()Hejl Burgess-Dawson 电影级算子ACESFilmicToneMapping4ACES_FILMIC_TONE_MAPPINGACESFilmicToneMapping()ACES RRT/ODT 拟合含 D65↔D60 矩阵转换CustomToneMapping5CUSTOM_TONE_MAPPINGCustomToneMapping()默认恒等返回供你自行实现见下AgXToneMapping6AGX_TONE_MAPPINGAgXToneMapping()基于 Filament/Blender 的 rec2020 主色 AgX 算法NeutralToneMapping7NEUTRAL_TONE_MAPPINGNeutralToneMapping()三通道非线性压缩接近 filmic 的“中性”观感各函数的具体实现都集中在 tonemapping_pars_fragment.glsl.js每个函数内部第一步都是color * toneMappingExposure即曝光作用于色调映射之前ACESFilmicToneMapping该文件 L46-L74先做sRGB→XYZ→D65_2_D60→AP1输入矩阵再执行RRTAndODTFit最后经输出矩阵映射回 sRGB并使用1/0.6的缩放因子适配更亮的观看环境注释援引 three.js 的 #19621 讨论AgXToneMappingL113-L166把线性 sRGB 转到 rec2020 主色空间执行对数压缩与agxDefaultContrastApprox近似 sigmoid再转回线性 sRGB是当前观感较新的选择CustomToneMapping在 Chunk 内仅是return color的占位实现L199。若要启用自定义色调映射需要在调用 OutputPass 之前自行renderer.toneMapping CustomToneMapping并以某种方式注入自定义宏/函数体——原生 OutputShader 不提供用户函数注入点因此实际项目中“自定义色调映射 OutputPass”通常需要 fork 该着色器或替换 Chunk。注意着色器默认NoToneMapping即renderer.toneMapping保持默认 0下上述宏一个都不定义颜色直接透传不做任何压缩——请勿在 HDR RenderTarget 工作流中忘记设置渲染器的色调映射否则最终画面可能严重过曝或失真。色彩空间转换SRGB_TRANSFER 与 sRGBTransferOETF片段着色器的颜色空间段OutputShader.js只在定义了SRGB_TRANSFER宏时生效#ifdef SRGB_TRANSFER gl_FragColor sRGBTransferOETF( gl_FragColor ); #endifSRGB_TRANSFER宏的注入条件在 OutputPass 中判定当ColorManagement.getTransfer( renderer.outputColorSpace ) SRGBTransfer时定义OutputPass.js。也就是说只有当渲染器配置为 sRGB 输出色彩空间如默认的SRGBColorSpace时才会做 sRGB 编码若输出到LinearSRGBColorSpace例如面向后续合成软件的线性流程则不做转换。sRGBTransferOETF()实现在 colorspace_pars_fragment.glsl.jsvec4 sRGBTransferOETF( in vec4 value ) { return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a ); }即标准 sRGB OETF线性值低于约 0.0031308 时使用 12.92 线性段高于该阈值时采用 1.055x^(1/2.4)−0.055 的幂律段。alpha 通道透传。它作用于整个vec4因此输出帧的 alpha 不会受色调映射影响只有 RGB 经历“曝光→色调映射→sRGB 编码”三步。OutputPassOutputShader 的实际载体与动态宏编译OutputShader 不直接渲染而是被 OutputPass 包装后参与后处理链。理解 OutputPass 的实现能帮你正确使用 OutputShader构造阶段用UniformsUtils.clone( OutputShader.uniforms )克隆一份独立 uniform并用OutputShader的 name/两段着色器源码创建RawShaderMaterialOutputPass.js L56-L68配合FullScreenQuad执行全屏绘制渲染阶段同步渲染器状态每次render()把readBuffer.texture写入tDiffuse、把renderer.toneMappingExposure写入曝光 uniformL90-L93所以渲染器上的曝光调节会实时传导到 OutputShader宏按需重建OutputPass缓存上次的outputColorSpace与toneMapping仅当两者任一变化时才清空并重填material.definesL97-L116随后置needsUpdate true触发重编译——在运行时切换色调映射算法成本是可控的输出目标分流renderToScreen true时直接渲染到默认帧缓冲屏幕否则渲染进writeBuffer以支持链式合成L120-L131资源释放提供dispose()同时释放材质与全屏四边形L139-L144。从这些实现细节可以看出OutputShader 的所有“工作参数”都来自 WebGLRenderer 的toneMapping、toneMappingExposure与outputColorSpace三个属性它本身不含任何状态。在 EffectComposer 链中的标准用法官方推荐的用法是把 OutputPass 放在每一条后处理链的最末端。仓库示例 webgl_postprocessing_unreal_bloom_selective.html 展示了多链协作下的编排方式import { EffectComposer } from three/addons/postprocessing/EffectComposer.js; import { RenderPass } from three/addons/postprocessing/RenderPass.js; import { UnrealBloomPass } from three/addons/postprocessing/UnrealBloomPass.js; import { ShaderPass } from three/addons/postprocessing/ShaderPass.js; import { OutputPass } from three/addons/postprocessing/OutputPass.js; // 链 1bloom 提取渲染到浮点 RenderTarget不直接输出 const bloomComposer new EffectComposer( renderer ); bloomComposer.renderToScreen false; bloomComposer.addPass( renderScene ); bloomComposer.addPass( bloomPass ); // 链 2最终合成 输出 const outputPass new OutputPass(); const finalComposer new EffectComposer( renderer ); finalComposer.addPass( renderScene ); // 或任意中间效果ShaderPass 等 finalComposer.addPass( outputPass ); // OutputShader 经 OutputPass 收尾 renderer.toneMapping THREE.ACESFilmicToneMapping; renderer.toneMappingExposure 1.0; renderer.outputColorSpace THREE.SRGBColorSpace;要点把 OutputPass 放在链尾它读取上一 Pass 的缓冲完成“色调映射 → sRGB 编码”保证中间 PassBloom、DOF、SSAO 等都在线性 HDR 空间中计算避免中间滤镜作用于已编码的颜色sRGB 输入型 Pass 的排位规则若某个 Pass 依赖 sRGB 编码输入典型如 FXAA它必须位于 OutputPass 之后见 OutputPass 类注释否则拿到的还是线性颜色不要把 OutputShader 再塞进其他 Pass它与 OutputPass 是 1:1 配套关系直接 new 一个以 OutputShader 为材质的 ShaderPass 再叠加 OutputPass会导致二次色调映射/二次 sRGB 编码画面会明显发灰、发暗或颜色错误。如需对照更完整的调用链可在仓库中继续阅读 OutputPass 文档、EffectComposer 相关示例 以及 OutputShader 的完整实现 examples/jsm/shaders/OutputShader.js与渲染器内置输出阶段的算法一致性可对照 src/renderers/webgl/WebGLOutput.js 和上文两个 ShaderChunk 源码验证。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考