Three.js HorizontalBlurShader 详解:水平高斯模糊着色器的原理、参数设置与两个实战集成方案
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
本篇技术指南围绕 Three.js 官方文档中 HorizontalBlurShader 模块页 所描述的着色器展开,深入剖析其 9 采样、标准差 2.7 的高斯模糊算法与tDiffuse、h两个 uniform 的含义,并结合仓库中的后处理与实时阴影示例,讲解它在 EffectComposer 与 ShaderMaterial 两种使用场景下的完整接法。读完本文你将能独立实现任意分辨率的水平/垂直两级模糊流水线,并学会如何为它设置正确的模糊步长。
HorizontalBlurShader 是什么
HorizontalBlurShader是 Three.js 以**插件(addon)**形式提供的一段预置 GLSL 着色器描述对象,存放于 examples/jsm/shaders/HorizontalBlurShader.js。官方文档(module-HorizontalBlurShader.html.md)对它的定位只有一句话,但信息密度很高:
Two pass Gaussian blur filter (horizontal and vertical blur shaders). —— 它自身只是"两趟高斯模糊"中的水平方向那一半,必须与配套的 VerticalBlurShader 组合,先水平后垂直地依次执行,才能在两个轴上形成完整的二维高斯模糊效果。
之所以拆成两次一维卷积而不是一次二维卷积,是因为二维高斯核具有可分离性:对N×N大小的核做全卷积每个像素需要N²次纹理采样,而拆成水平一趟、垂直一趟后每像素只需2×N次采样。以本文的 9 采样核为例,两趟总共 18 次采样,远小于单趟二维 9×9=81 次采样,是后期处理中典型的性能优化手段。
Import:显式导入与 addons 路径映射
与核心模块不同,HorizontalBlurShader 属于examples/jsm下的附加代码,需要显式导入。文档给出了标准写法:
import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js';这里的three/addons/*不是虚拟路径,而是由当前仓库 package.json 中的导出映射声明的:
"./examples/jsm/*": "./examples/jsm/*", "./addons": "./examples/jsm/Addons.js", "./addons/*": "./examples/jsm/*"也就是说,three/addons/shaders/HorizontalBlurShader.js实际解析到本仓库的 examples/jsm/shaders/HorizontalBlurShader.js。如果不使用打包器别名,也可以直接在 HTML 模块脚本中按仓库内真实路径导入:
import { HorizontalBlurShader } from './examples/jsm/shaders/HorizontalBlurShader.js';此外,聚合入口 examples/jsm/Addons.js 通过export * from './shaders/HorizontalBlurShader.js';把它统一导出了,因此习惯整包引用的项目也可以从three/addons一并获得。
着色器对象结构源码解析
从 HorizontalBlurShader.js 的源码可见,该对象由name、uniforms、vertexShader和fragmentShader四个字段构成,符合ShaderMaterial~Shader的标准对象结构,可直接喂给THREE.ShaderMaterial或后处理ShaderPass:
const HorizontalBlurShader = { name: 'HorizontalBlurShader', uniforms: { 'tDiffuse': { value: null }, 'h': { value: 1.0 / 512.0 } }, vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`, fragmentShader: /* glsl */` uniform sampler2D tDiffuse; uniform float h; varying vec2 vUv; void main() { vec4 sum = vec4( 0.0 ); sum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * h, vUv.y ) ) * 0.051; sum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * h, vUv.y ) ) * 0.0918; sum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * h, vUv.y ) ) * 0.12245; sum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * h, vUv.y ) ) * 0.1531; sum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633; sum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * h, vUv.y ) ) * 0.1531; sum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * h, vUv.y ) ) * 0.12245; sum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * h, vUv.y ) ) * 0.0918; sum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * h, vUv.y ) ) * 0.051; gl_FragColor = sum; }` };- 顶点着色器非常简单:把模型自带 UV 原样透传到片段阶段,再照常计算裁剪空间位置,负责让一个全屏四边形恰好覆盖一帧画面;
- 片段着色器仅在
vUv.x方向偏移采样,vUv.y保持不变——这正是"水平"二字的由来。对称的 VerticalBlurShader.js 只是把所有偏移换到了vUv.y,uniform 名相应改为v,其余完全相同。
两个 uniform:tDiffuse 与 h
文档与源码共同确认了水平着色器只暴露两个 uniform,含义如下:
| uniform | 类型 | 默认值 | 含义 |
|---|---|---|---|
tDiffuse | sampler2D | null | 待模糊的输入纹理,通常是上一趟渲染得到的RenderTarget.texture |
h | float | 1.0 / 512.0 | 水平方向相邻纹素在 UV 坐标上的步长,即"每偏移 1 个像素"对应的 UV 距离 |
h的取值逻辑非常关键。纹理坐标vUv的取值范围是[0, 1],而"1 个像素"在这套坐标系里恰是1 / 纹理宽度。因此文档明确要求:
"h" and "v" parameters should be set to "1 / width" and "1 / height"
即水平趟把h设为输入纹理宽度的倒数,垂直趟把v设为输入纹理高度的倒数。例如在 webgl_postprocessing_advanced.html 示例中,width = window.innerWidth,随后用到了1 / 宽度量级的赋值。默认值1.0 / 512.0对应"输入纹理宽 512 像素"时的经验取值,当渲染目标分辨率改变时必须重新设置,否则模糊半径会与像素数脱节。
9 采样权重与标准差 2.7 的高斯核
文档给出了三组算法特征参数,与片段着色器代码一一对应:
- 9 samples per pass:每趟在中心像素两侧各取 4 个样本,连同中心共 9 次纹理采样(偏移量
-4h, -3h, -2h, -1h, 0, +h, +2h, +3h, +4h); - standard deviation 2.7:权重分布按标准差 σ≈2.7 的高斯曲线
w(k) ∝ e^(−k²/(2σ²))计算,中心权重最高、向两侧快速衰减; - 权重和为 1:9 个权重
2×(0.051 + 0.0918 + 0.12245 + 0.1531) + 0.1633 = 1.0,保证模糊后图像整体亮度不发生变化。
各采样点的偏移与权重可整理为下表:
| 偏移量 | −4h | −3h | −2h | −1h | 0 | +1h | +2h | +3h | +4h |
|---|---|---|---|---|---|---|---|---|---|
| 权重 | 0.051 | 0.0918 | 0.12245 | 0.1531 | 0.1633 | 0.1531 | 0.12245 | 0.0918 | 0.051 |
代码中也体现了这一对称结构(同一偏移量的两次采样共用一个权重,故九行采样仅出现五个权重常量)。对每个采样点都乘上高斯权重再累加,等效于对每个纹素做一次离散的一维高斯加权平均;水平趟结束再交给垂直趟,两个方向各自独立卷积后相乘,即得到完整的二维高斯模糊。从权重比例反推(中心权重 0.1633、邻域 0.1531,比值约 0.94 对应e^(−1/(2σ²))),可验证文档标注的 σ=2.7 是自洽的。
集成方案一:EffectComposer 中的后处理模糊链
在"渲染→后期处理"架构里,HorizontalBlurShader 最常见的归宿是被 ShaderPass 包装进EffectComposer。仓库示例 webgl_postprocessing_advanced.html 展示了它与 VerticalBlurShader 的成对导入:
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js'; import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js'; import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js';随后构造两个 ShaderPass 并写入正确的步长(示例画面被一分为二,故取半屏分辨率):
const effectHBlur = new ShaderPass( HorizontalBlurShader ); const effectVBlur = new ShaderPass( VerticalBlurShader ); effectHBlur.uniforms[ 'h' ].value = 2 / ( width / 2 ); effectVBlur.uniforms[ 'v' ].value = 2 / ( height / 2 );把二者按"先 H 后 V"的顺序挂进合成器即可:composerScene.addPass( effectHBlur )之后再添加垂直趟及其他效果(颜色滤镜、晕影、Gamma 校正等)。注意:
ShaderPass会把着色器对象包装成内部材质,运行期间通过pass.uniforms访问并驱动;tDiffuse由 ShaderPass 自动接入上一趟的输出纹理,无需手工指定;- 模糊趟应放在需要柔化的画面效果之后、色调映射/Gamma 校正之前。
集成方案二:作为 ShaderMaterial 柔化实时接触阴影
不经过 EffectComposer 时,还可以直接把该着色器对象交给new THREE.ShaderMaterial( HorizontalBlurShader ),再结合离屏RenderTarget手工编排渲染顺序。仓库示例 webgl_shadow_contact.html 正是这么做的——先用深度着色器把阴影区域绘制到一张 256 分辨率渲染目标上,再对其做水平+垂直两次模糊,得到柔化的半影边缘:
const horizontalBlurMaterial = new THREE.ShaderMaterial( HorizontalBlurShader ); horizontalBlurMaterial.depthTest = false; const verticalBlurMaterial = new THREE.ShaderMaterial( VerticalBlurShader ); verticalBlurMaterial.depthTest = false;示例中的每帧模糊流程(见 webgl_shadow_contact.html)形成一条清晰的乒乓渲染链:
renderTarget → blurPlane(水平模糊) → renderTargetBlur → blurPlane(垂直模糊) → renderTargetfunction blurShadow( amount ) { blurPlane.visible = true; // 水平方向模糊,写入 renderTargetBlur blurPlane.material = horizontalBlurMaterial; blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture; horizontalBlurMaterial.uniforms.h.value = amount * 1 / 256; renderer.setRenderTarget( renderTargetBlur ); renderer.render( blurPlane, shadowCamera ); // 垂直方向模糊,写回 renderTarget blurPlane.material = verticalBlurMaterial; blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture; verticalBlurMaterial.uniforms.v.value = amount * 1 / 256; renderer.setRenderTarget( renderTarget ); renderer.render( blurPlane, shadowCamera ); blurPlane.visible = false; }这段代码有几点值得注意的实操细节:
- 这里把
h写成amount * 1/256,正对应"渲染目标宽度为 256"时的1/width规则,amount相当于以像素计的模糊半径缩放系数; tDiffuse不再由框架注入,而是手工把上一趟渲染目标的纹理赋给材质 uniform;- 模糊所用的
blurPlane是一块受shadowCamera正交投影控制的平面,因为模糊只关心 UV 采样,不参与实际深度测试,因此材质关闭了depthTest; - 两次渲染分别写往两个不同的渲染目标再写回,避免了在同一纹理上读写导致的采样脏数据。
这套"H 趟→V 趟"的模式,本质上与 EffectComposer 方案相同,只是渲染流程完全由你掌控,适合作为某个中间步骤(如阴影、反射、辉光预滤波)嵌入到自定义多趟渲染管线中。
使用要点与易错点
- 必须成对使用:HorizontalBlurShader 只沿 x 轴卷积,仅使用它会出现"单向拉伸"的模糊条纹;务必紧跟一趟 VerticalBlurShader。
- 按实际渲染目标分辨率设置步长:默认值
1/512只适配 512 宽输入。换用不同分辨率时,把h设为1/宽度、v设为1/高度;若要维持相同的"像素级模糊半径",还需要同步缩放偏移倍数。 tDiffuse不能留空:直接使用ShaderMaterial时必须手动喂入待模糊纹理;EffectComposer/ShaderPass场景下则由框架逐趟传递。- 注意渲染顺序:作为后期效果应置于场景渲染与最终颜色输出之间;需要与 GammaCorrectionShader 等配合时,模糊趟通常在线性空间完成。
- 大模糊半径时可改用多趟或替代方案:9 采样的固定核在离线渲染目标(如 256 接触阴影)上表现良好;若需要极宽的模糊,可考虑叠加多次执行,或选用仓库内其它专用方案,如可调核宽度的 ConvolutionShader、深度感知的 DepthLimitedBlurShader 以及模拟移轴效果的 HorizontalTiltShiftShader。
- 调试技巧:若模糊后画面与输入几乎无差别,先检查
h是否被设成了 0(零步长使 9 个采样点重叠于中心,加权后等于原图);若只有水平方向可见模糊,则说明垂直趟缺失或v未生效。
小结
HorizontalBlurShader 是 Three.js 附加着色器集合里结构最简、用途最广的成员之一:一个name、两个 uniform、一段透传 UV 的顶点着色器和一段 9 采样的水平高斯卷积片段着色器。它与 VerticalBlurShader 构成的两趟模糊管线,既是 webgl_postprocessing_advanced.html 演示的通用后期模糊步骤,也是 webgl_shadow_contact.html 中柔化实时接触阴影的核心工具。理解"h = 1/width"这一纹素步长约定,就能把它无缝迁移到任何分辨率与任何自定义多趟渲染流程中。
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考