
three.js TSLSobelOperatorNode 边缘检测后处理节点的用法与实现原理【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsSobelOperatorNode 是 three.js WebGPU 渲染管线TSL中的显示类后处理节点用于对渲染结果做 Sobel 算子边缘检测让画面呈现线稿/描边风格。本文基于仓库中的 API 文档与 SobelOperatorNode 源码实现完整梳理其导入方式、构造函数与属性、updateBefore/setup生命周期方法以及 Sobel 卷积核在 GPU 上的具体计算方式读完后你能在自己的 WebGPU 项目中正确接入该效果并理解其底层实现。继承体系与节点定位根据 SobelOperatorNode 官方 API 文档该节点的继承链为EventDispatcher → Node → TempNode → SobelOperatorNode其文档定位为Post processing node for detecting edges with a sobel filter. A sobel filter should be applied after tone mapping and output color space conversion.即它是一个 Sobel 滤波器边缘检测后处理节点且应当在完成色调映射tone mapping和输出色彩空间转换之后应用——这一约束直接决定了它在渲染管线中的挂载位置见下文实战章节。从源码结构看SobelOperatorNode继承自 TempNode。TempNode 的作用是通过缓存管理cache management避免同一节点被多处引用时产生重复计算当节点在generate构建阶段被多个依赖方使用时它会在build()中把中间结果提升为临时变量builder.getVarFromNode再让多处引用共享该变量见 TempNode.js#L51-L84。SobelOperatorNode声明了static get type() { return SobelOperatorNode }并构造时以super(vec4)指定输出类型为 vec4。导入方式SobelOperatorNode 是 addon附加模块必须显式导入而不是随three/tsl核心导出import { sobel } from three/addons/tsl/display/SobelOperatorNode.js;注意three/tsl核心导出的是sobel( node )对应的各 TSL 函数列表见 docs/TSL.md 中 Display 分类下的sobel( node ) | Creates a sobel edge detection effect.但SobelOperatorNode类本身及其实用封装函数sobel位于 addons 目录需要通过three/addons/路径引入。sobel是一个 TSL 工厂函数源码实现如下SobelOperatorNode.js#L160-L168export const sobel ( node ) new SobelOperatorNode( convertToTexture( node ) );convertToTexture( node )是关键的一步它会把任意输入节点如一个pass的 ScenePassNode转换为 TextureNode从而保证构造函数收到的始终是符合TextureNode约定的纹理采样节点。构造函数与属性new SobelOperatorNode( textureNode )textureNode : TextureNode—— 表示效果输入图像的纹理节点。构造函数内部初始化SobelOperatorNode.js#L25-L53constructor( textureNode ) { super( vec4 ); this.textureNode textureNode; // 每帧由 updateBefore() 更新一次 this.updateBeforeType NodeUpdateType.FRAME; // 输入纹理尺寸的倒数用于计算相邻 texel 的 UV 偏移 this._invSize uniform( new Vector2() ); }.textureNode : TextureNode表示效果输入的纹理节点即边缘检测作用的源图像。.updateBeforeType : string文档指出由于该节点需要在每帧开始阶段刷新其内部 uniformupdateBeforeType被设置为NodeUpdateType.FRAME默认值为frame覆盖了 TempNode 的同名属性。这一点在源码中可以得到印证updateBefore()每帧都会读取textureNode.value底层 DataTexture的实际尺寸并更新_invSizeuniformSobelOperatorNode.js#L60-L66updateBefore( /* frame */ ) { const map this.textureNode.value; this._invSize.value.set( 1 / map.image.width, 1 / map.image.height ); }从源码结构看这意味着如果输入 RenderTarget 发生尺寸变化例如窗口自适应 DPR 缩放滤波器的采样步长会自动跟随输入纹理的实际分辨率无需手动同步。私有属性 ._invSize一个UniformNodevec2保存输入纹理分辨率的倒数(1/width, 1/height)是 3×3 邻域 UV 偏移计算的步长来源。核心实现Sobel 卷积核与梯度计算setup( builder )方法负责生成效果的 TSL 代码返回一个ShaderCallNodeInternal。其内部通过Fn定义了一个可复用的 Sobel 计算函数完整流程如下SobelOperatorNode.js#L74-L1541. UV 与采样函数const uvNode textureNode.uvNode || uv(); const sampleTexture ( uv ) textureNode.sample( uv );优先使用输入节点自带的uvNode否则回退到当前片元的内置uv。sampleTexture对给定的 UV 坐标采样输入纹理。2. 定义 Sobel 卷积核const Gx mat3( - 1, - 2, - 1, 0, 0, 0, 1, 2, 1 ); // x direction kernel const Gy mat3( - 1, 0, 1, - 2, 0, 2, - 1, 0, 1 ); // y direction kernel这是经典的一阶导数 Sobel 算子水平梯度 Gx垂直梯度 Gy-1 0 1-1 -2 -1-2 0 20 0 0-1 0 11 2 1源码注释特意说明「in glsl matrices are filled in column-major order」GLSL 矩阵按列优先填充因此mat3( -1, -2, -1, 0, 0, 0, 1, 2, 1 )按列优先读出来正好是 Gx 的标准形式。3. 采集 3×3 邻域亮度值const texel this._invSize; // first column const tx0y0 luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, - 1 ) ) ) ).xyz ); const tx0y1 luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 0 ) ) ) ).xyz ); // ... 依次采集 3×3 邻域的 9 个像素对中心 UV 加/减texel即_invSize偏移共采样 9 个像素并通过luminance()把每个采样值的 RGB 分量折算为标量亮度——这是 Sobel 检测的经典做法在灰度空间上做梯度运算避免彩色边缘产生伪影。4. 双方向梯度卷积const valueGx add( Gx[ 0 ][ 0 ].mul( tx0y0 ), Gx[ 1 ][ 0 ].mul( tx1y0 ), // ... 9 项逐元素相乘再求和 ); const valueGy add( /* Gy 同构展开 */ );由于 TSL 中矩阵元素索引会产生独立运算实现上把 3×3 卷积显式展开为 9 项muladd分别得到 x 方向与 y 方向的梯度值。5. 合成梯度幅值const G valueGx.mul( valueGx ).add( valueGy.mul( valueGy ) ).sqrt(); return vec4( vec3( G ), 1 );按欧氏范数 $|G| \sqrt{G_x^2 G_y^2}$ 合成总梯度梯度越大的位置即边缘越亮平坦区域梯度接近 0输出为黑色。最终把标量梯度复制进 RGB 三个通道、alpha 置 1输出vec4灰度线稿。这与示例截图中的黑白描边效果完全一致。实战在 WebGPURenderer 渲染管线中接入仓库自带的 examples/webgpu_postprocessing_sobel.html 示例演示了标准用法核心代码为import * as THREE from three/webgpu; import { pass, renderOutput } from three/tsl; import { sobel } from three/addons/tsl/display/SobelOperatorNode.js; // ... 场景、相机、WebGPURenderer 初始化省略 ... renderer.toneMapping THREE.LinearToneMapping; // postprocessing const renderPipeline new THREE.RenderPipeline( renderer ); renderPipeline.outputColorTransform false; // 输出色变换交给 renderOutput 完成 const scenePass pass( scene, camera ); // 先做色调映射与输出色彩空间转换再做 Sobel 边缘检测 renderPipeline.outputNode sobel( renderOutput( scenePass ) );这个组合方式精确落实了文档中「sobel 应在 tone mapping 与输出色彩空间转换之后应用」的要求pass( scene, camera )把场景渲染到 TSL 纹理节点renderOutput( scenePass )在节点内部套用渲染器的输出设置色调映射 色彩空间转换因此示例中同时设置renderPipeline.outputColorTransform false避免管线末端再做一次重复转换sobel( ... )将renderOutput的结果经convertToTexture转为 TextureNode 后送入SobelOperatorNode最终结果赋给renderPipeline.outputNode由 WebGPU 后端编译执行。示例还保留了 GUI 开关勾选params.enabled时走renderPipeline.render()带 Sobel取消勾选时直接renderer.render( scene, camera )显示原画面方便对照检查滤镜效果。注意适用前提SobelOperatorNode 属于 WebGPU 渲染管线three/webgpuRenderPipeline的 TSL 后处理不适用于 WebGL 的EffectComposer。仓库中对应的 WebGL 版 Sobel 是独立的着色器模块见 docs/pages/module-SobelOperatorShader.html.md 记录的 SobelOperatorShader两者不要混淆。关键设计要点小结设计点说明源码依据输入必须是 TextureNode工厂函数sobel()内部用convertToTexture统一转换SobelOperatorNode.js#L168每帧刷新采样步长updateBeforeType NodeUpdateType.FRAMEupdateBefore()根据纹理实际尺寸更新_invSizeSobelOperatorNode.js#L43-L44、#L60-L66亮度空间卷积每个邻域采样都先经luminance()转为标量SobelOperatorNode.js#L97-L111列优先矩阵展开GLSLmat3按列填充Gx/Gy 按列优先写法初始化SobelOperatorNode.js#L88-L91临时变量缓存继承 TempNode被多处引用时自动提升为临时变量防重复计算TempNode.js#L51-L84相关资源API 文档docs/pages/SobelOperatorNode.html.md、基类 docs/pages/TempNode.html.mdTSL Display 函数总览含sobel签名说明docs/TSL.md源码实现examples/jsm/tsl/display/SobelOperatorNode.js可运行示例examples/webgpu_postprocessing_sobel.html该目录下的同类显示节点如 SharpenNode.js、ChromaticAberrationNode.js可作进一步扩展参考【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考