第 10 课:后处理

返回教程目录 · 打开本课练习

从物体表面走到整张画面

前九课的片元 Shader 给单个物体表面上色。色彩校正、暗角、模糊、Bloom 和屏幕故障效果通常需要处理已经渲染完成的整张图像。Three.js 先把场景画进离屏纹理,再用覆盖屏幕的两个三角形读取这张纹理。此时 vUv 对应屏幕图像的 0–1 范围;同一公式会作用于所有物体及背景。

本课用 EffectComposer 管理 Pass 链:RenderPass(scene,camera) → 一个或多个 ShaderPass → OutputPass。每个 Pass 从上一张纹理的 tDiffuse 采样,把结果写入另一张 render target;Composer 在两张目标之间交换读写角色。最后的 OutputPass 负责 Three.js 输出颜色空间转换。运行代码在 exercises/post-runtime.js,每题自己的 shader.frag 保留要学习的公式。

数据流是:JS 创建场景与材质 → 几何体 attribute 经物体顶点着色器 → 插值与普通片元着色器形成原始图像 → RenderPass 写入纹理 → 全屏顶点 Shader 传递 vUv → 后处理片元 Shader 采样 tDiffuse → 输出到屏幕。全屏 Pass 不知道原场景的模型坐标或法线;如果需要深度和法线,必须另行提供缓冲。

第一类:每像素独立的颜色变换

反色 1.0 - color、灰度 dot(color, vec3(0.2126,0.7152,0.0722)) 只读当前 UV 的一个像素,通常比邻域滤镜便宜。权重对应线性 RGB 的近似亮度感受;对 sRGB 编码值直接套线性亮度公式会产生不同结果。本课 RenderPass 的颜色在后处理链内按线性值使用,输出时由 OutputPass 转成显示颜色。

暗角根据 length(vUv - 0.5) 计算中心距离,再用 smoothstep 形成平滑衰减。其范围取决于画布宽高比:这里使用归一化 UV,非正方形画布上暗角的屏幕像素形状可能拉伸。需要圆形暗角时,把 UV 的横向距离乘以宽高比。色差把 R、G、B 分别从略微错位的 UV 读取,边缘产生彩边,代价是额外纹理采样。

第二类:读取相邻像素

模糊对相邻采样加权平均。uTexelSize = 1.0 / vec2(textureWidth,textureHeight) 表示一个纹理像素的 UV 距离;因此 uv + uTexelSize*vec2(1,0) 是右邻像素。单方向五次采样使横向边缘变软。二维 5×5 核需要 25 次采样;若核权重可分离,先横向五次,再纵向五次,只需约十次,但需要额外一个 Pass 和目标纹理。阅读第 4、5 题时,注意采样次数和中间纹理内存都属于成本。

Bloom 先保留亮部,再模糊并加回原图。本课第 6 题使用 Three.js 的 UnrealBloomPass 完成这组步骤;自写的 shader.frag 先提高亮部,便于观察阈值。strength 加大提高光晕贡献,threshold 提高则减少参与发光的区域。Bloom 是亮度扩散,不是场景中真实的光线传播;高分辨率下通常需要降采样或多尺度模糊。

第三类:改变采样位置与风格

CRT 先把 vUv 变成以中心为零的坐标,再按半径轻微放大采样位置,形成桶形弯曲;超出 0–1 的区域用黑色显示。扫描线按纹理像素行计算亮度,因此改动分辨率会改变线密度。故障效果把 UV 的 Y 划成条带,结合离散时间生成稳定随机数,只让少数条带水平偏移;若每个像素都使用独立随机数,画面会成为杂乱噪声。

水彩题先做两个方向的小半径模糊,再用 floor(color*levels+0.5)/levels 减少色阶。这是便于学习的风格化近似,没有纸张纤维、真实颜料扩散或边缘积色。想深化效果,可以把噪声、边缘检测或纸纹加入新的 Pass,同时监测纹理采样与显存占用。

日常开发取舍与排查

多个效果可串成 Pass 链,但每增加一层通常意味着一次全屏绘制、至少一次纹理读取,以及中间 render target 的内存。移动端先减少画布像素比、模糊半径或 Pass 数;简单色调变换可以合并到一个 Pass。水彩和 Bloom 的中间纹理不要在每帧重新创建。窗口缩放时要更新 renderer 和 Composer 尺寸,页面离开时调用 composer.dispose()、材质和几何体的 dispose()。

画面全黑时,先确认 RenderPass 在最前、OutputPass 在最后,并把自定义 Pass 暂时改为 gl_FragColor = texture2D(tDiffuse, vUv)。模糊方向错误时检查 uTexelSize 的宽高顺序;彩边过宽时按真实纹理像素调整偏移;Bloom 不亮时先确认输入亮度和 threshold。后处理测得的 draw call 会增加,但 draw call 数本身不能代替 GPU 时间。

练习

难度 题目 可见验收
⭐ EffectComposer 全屏反色 场景整体反色
⭐ 亮度权重灰度 灰度且层次保留
⭐⭐ 径向暗角 四周渐暗
⭐⭐ 五次采样的横向模糊 横向边缘变软
⭐⭐ 横向加纵向的可分离模糊 双方向模糊
⭐⭐⭐ 亮部提取与 Bloom 可调亮部光晕
⭐⭐⭐ RGB 通道色差 边缘彩色分离
⭐⭐⭐ CRT 弯曲与扫描线 弯曲与细密条纹
⭐⭐⭐⭐ 分块错位故障效果 局部条带闪动
⭐⭐⭐⭐ 水彩式模糊与色调分离 软边和色阶