three.js TSL SharpenNode:基于 RCAS 对比度自适应锐化后处理节点的实现原理与实战指南
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
SharpenNode 是 three.js 中 TSL(Three Shading Language)后处理体系里的一个显示类节点(display node),用于对场景渲染结果执行 RCAS(Robust Contrast-Adaptive Sharpening,鲁棒对比度自适应锐化)后处理。本文基于其官方文档页与源码实现,完整讲解sharpen()的导入方式、构造参数与属性、底层 RCAS 算法在 TSL 中的实现细节,以及它在仓库示例中配合 TAAU 上采样、TAA 反走样使用的真实接法,帮助你在 WebGPU 渲染管线中为低分辨率渲染输出加上一层可实时调节、噪声感知的锐化。
节点定位与继承关系
SharpenNode 的继承链为:
EventDispatcher→Node→TempNode→SharpenNode
从源码结构看(examples/jsm/tsl/display/SharpenNode.js),它继承自 TempNode,并在构造时声明输出类型为'vec4'。它不是直接内联进场景材质的普通计算节点,而是一个独立的后处理 Pass 节点:节点内部持有一个私有的RenderTarget,每帧在updateBefore()中把整个效果渲染到该目标,再通过passTexture将结果暴露为一个PassTextureNode供下游管线采样。
其文档注释明确说明这是"Post processing node for contrast-adaptive sharpening (RCAS)",算法参考 AMD 的 FidelityFX Super Resolution(FSR)项目。它位于examples/jsm/tsl/display/目录下,与BloomNode.js、SSAANode.js、TAAUNode.js、TRAANode.js等同属一组 display 类后处理节点。
导入方式
SharpenNode 是一个 addon,需要显式导入(参考 官方文档页 中的 Import 说明,addons 的安装方式见 three.js 安装文档的 Addons 一节):
import { sharpen } from 'three/addons/tsl/display/SharpenNode.js';注意导出的是一个 TSL 函数sharpen,而不是SharpenNode类本身。从源码看(SharpenNode.js 尾部):
export const sharpen = ( node, sharpness, denoise ) => new SharpenNode( convertToTexture( node ), sharpness, denoise );sharpen()内部先对输入 node 调用convertToTexture(),把任意Node<vec4>输入归一化为纹理节点,再构造SharpenNode。这意味着你可以把任何 vec4 节点(场景 Pass、其他后处理节点的结果等)直接作为输入,而不必自己包一层纹理。
构造函数与参数
new SharpenNode( textureNode, sharpness = 0.2, denoise = false )
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
textureNode | TextureNode | 必填 | 效果输入的纹理节点 |
sharpness | Node.<float> | 0.2 | 锐化强度。语义注意是反向的:0 = 最大锐化,2 = 无锐化 |
denoise | Node.<bool> | false | 是否在噪声区域衰减锐化强度 |
sharpness的取值语义是理解该节点的关键:数值越小锐化越强。这一点在源码中可以直接印证——构造器用nodeObject( sharpness )把参数包装成可求值的节点(SharpenNode.js L58),而在片段计算中,锐化系数由指数映射得出:
const con = exp2( -sharpness ); // sharpness=0 → con=1(最大权重);sharpness=2 → con=0.25即con = 2^(-sharpness),sharpness = 0时得到满权重,sharpness增大后权重按 2 的幂衰减,2附近锐化基本消失。文档给出的默认值0.2对应con ≈ 0.87,即默认就带较明显的锐化。
denoise默认为false;开启后,节点会用局部亮度统计量计算一个噪声抑制因子nzFactor,在噪声(高频抖动)区域自动削弱锐化权重,避免锐化放大噪声。
属性
.sharpness : Node.<float>— 锐化强度,默认0.2。由于是 Node 类型,可以在运行时直接改.value实现实时调节。.denoise : Node.<bool>— 噪声衰减开关,默认false。.textureNode : TextureNode— 效果输入的纹理节点。.isSharpenNode : boolean (readonly)— 类型测试标志,恒为true。.updateBeforeType : string— 默认'frame'(即NodeUpdateType.FRAME)。因为该节点的效果需要每帧渲染一次,所以它注册在"帧更新"阶段执行updateBefore(),而不是延迟到 shader 构建阶段。该属性覆盖自 TempNode。
方法
.getTextureNode() : PassTextureNode— 返回效果结果的纹理节点,把它接到下游管线即可(例如赋给renderPipeline.outputNode)。.setSize( width, height )— 设置效果输出尺寸(像素)。实际上无需手动调用:节点每帧会用渲染器的drawingBufferSize自动调整内部 RenderTarget 尺寸,保证与输出分辨率一致。.setup( builder ) : PassTextureNode— 由节点构建器调用,用于生成该 Pass 的 TSL 代码:创建NodeMaterial、绑定fragmentNode为 RCAS 函数、记录输入纹理节点。.updateBefore( frame )— 每帧执行一次:保存并重置渲染器状态 → 按当前绘制缓冲区尺寸调整 RenderTarget → 以共享QuadMesh全屏绘制锐化 Pass → 恢复渲染器状态。.dispose()— 释放内部RenderTarget与NodeMaterial,效果不再需要时必须调用,避免 GPU 资源泄漏。
RCAS 算法的 TSL 实现解析
setup()中定义的rcas函数(SharpenNode.js L174-L235)是从 AMD FidelityFX FSR 1(ffx_fsr1.h)移植而来的 RCAS 实现。其核心步骤:
- 5 采样十字形邻域:通过
textureLoad对输入纹理做整数像素级采样,取中心像素e与上b、左d、右f、下h四个邻域,而不是依赖纹理单元的双线性采样。 - 近似亮度:
luma = b + (g + r) * 0.5(luma 的 2 倍近似,权重 0.25 / 0.5 / 0.25 的整数化形式),用于后续噪声统计。 - 用户锐化系数:
con = exp2(-sharpness),作为用户参数映射到实际权重。 - 对比度极限(Limiter):取环域 RGB 最小值
mn4与最大值mx4,按 FSR 原始公式计算hitMin/hitMax,得到自适应的 lobe 权重。源码中RCAS_LIMIT = 0.25 - 1.0 / 16.0(即 0.1875),最终 lobe 被钳制在[-0.1875, 0]区间再乘以con——这个下限防止极端锐化产生振铃(ringing)。 - 噪声衰减:
nz是四个邻域亮度均值与中心亮度的偏差,nzRange是 5 个亮度值的极差;nzFactor = 1 - saturate(|nz| / max(nzRange, 1/65536)) * 0.5。当denoise为true时用nzFactor削弱 lobe,使噪声区域锐化趋近于零。 - 解析(Resolve):
result = ( (b + d + f + h) * effectiveLobe + e ) / ( effectiveLobe * 4 + 1 )即邻域加权和与中心像素的归一化混合,effectiveLobe越负(锐化越强)时邻域贡献权重越大;最终输出vec4(result, e.a)保留原始 alpha。
这一实现有几个工程细节值得注意:内部RenderTarget使用HalfFloatType且不带深度缓冲(L74-L75),保证 HDR 数据在 Pass 间流转不丢精度;textureLoad要求输入是可直接整数采样的渲染目标纹理,这也是sharpen()入口要先convertToTexture的原因之一。
仓库示例中的真实用法
与 TAAU 上采样配合(推荐场景)
webgpu_upscaling_taau.html 展示了 SharpenNode 最典型的应用:低分辨率场景 Pass → TAAU 时域抗锯齿上采样 → RCAS 锐化。关键接法:
import { sharpen } from 'three/addons/tsl/display/SharpenNode.js'; const scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' ); const taauNode = taau( scenePassColor, scenePassDepth, scenePassVelocity, camera ); const sharpenNode = sharpen( taauNode.getTextureNode(), params.sharpness ); // 用 GUI 开关切换是否启用锐化 renderPipeline.outputNode = params.sharpening ? sharpenNode : taauNode; renderPipeline.needsUpdate = true;该示例的 GUI 提供sharpening布尔开关和sharpness滑块(范围 0–2、步长 0.05),运行时通过sharpenNode.sharpness.value = value实时调整强度(示例 L159-L164)。这演示了两个实践要点:
- 切换
sharpenNode是否接入renderPipeline.outputNode后,必须置renderPipeline.needsUpdate = true触发管线重建; - 调节强度只需写
sharpness.value,无需重建管线。
与 TAA 反走样配合(SSR 去噪管线)
webgpu_postprocessing_ssr_denoise.html 中,锐化作为整条后处理链的最后一步,加在 TAA 之后:
function applyPostProcessing( source ) { return sharpen( traa( applyGrading( source ), scenePassDepth, scenePassVelocity, camera ), 0 ); }这里sharpness直接传0,即满强度锐化——这是"上采样后补偿模糊"的典型参数选择:时域抗锯齿在平滑噪点的同时会让画面发软,RCAS 在对比度受限的前提下把边缘重新拉锐。
使用建议与注意事项
- 适用前提:SharpenNode 位于
three/addons/tsl/display/,依赖 TSL 与 WebGPU 渲染器(three/webgpu)环境;在纯 WebGL 后处理(EffectComposer 体系)中不可直接使用。 - 参数方向易错:
sharpness是"越小越锐"。默认0.2已经很锐;做 TAAU/TAA 上采样补偿时示例常用0(最大),日常场景按 0–1 区间调节即可。 - 噪声重的输入请开 denoise:如果输入来自低采样或高噪点路径(如未充分时域滤波的 SS 结果),传入
denoise = true可让噪声区域自动降低锐化。 - 生命周期管理:节点持有独立的 RenderTarget 与 NodeMaterial,场景卸载或管线重建时调用
dispose()释放 GPU 资源;尺寸无需手动管理,节点每帧按drawingBufferSize自适应。 - 文档与源码索引:官方 API 文档见 docs/pages/SharpenNode.html.md,实现源码见 examples/jsm/tsl/display/SharpenNode.js,基类行为见 src/nodes/core/TempNode.js。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考