three.js TSL SharpenNode:基于 RCAS 对比度自适应锐化后处理节点的实现原理与实战指南
2026/9/8 17:23:51 网站建设 项目流程

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 的继承链为:

EventDispatcherNodeTempNodeSharpenNode

从源码结构看(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.jsSSAANode.jsTAAUNode.jsTRAANode.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 )

参数类型默认值说明
textureNodeTextureNode必填效果输入的纹理节点
sharpnessNode.<float>0.2锐化强度。语义注意是反向的:0 = 最大锐化,2 = 无锐化
denoiseNode.<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()— 释放内部RenderTargetNodeMaterial,效果不再需要时必须调用,避免 GPU 资源泄漏。

RCAS 算法的 TSL 实现解析

setup()中定义的rcas函数(SharpenNode.js L174-L235)是从 AMD FidelityFX FSR 1(ffx_fsr1.h)移植而来的 RCAS 实现。其核心步骤:

  1. 5 采样十字形邻域:通过textureLoad对输入纹理做整数像素级采样,取中心像素e与上b、左d、右f、下h四个邻域,而不是依赖纹理单元的双线性采样。
  2. 近似亮度luma = b + (g + r) * 0.5(luma 的 2 倍近似,权重 0.25 / 0.5 / 0.25 的整数化形式),用于后续噪声统计。
  3. 用户锐化系数con = exp2(-sharpness),作为用户参数映射到实际权重。
  4. 对比度极限(Limiter):取环域 RGB 最小值mn4与最大值mx4,按 FSR 原始公式计算hitMin/hitMax,得到自适应的 lobe 权重。源码中RCAS_LIMIT = 0.25 - 1.0 / 16.0(即 0.1875),最终 lobe 被钳制在[-0.1875, 0]区间再乘以con——这个下限防止极端锐化产生振铃(ringing)。
  5. 噪声衰减nz是四个邻域亮度均值与中心亮度的偏差,nzRange是 5 个亮度值的极差;nzFactor = 1 - saturate(|nz| / max(nzRange, 1/65536)) * 0.5。当denoisetrue时用nzFactor削弱 lobe,使噪声区域锐化趋近于零。
  6. 解析(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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询