课程 30 - 后处理与渲染图
本节课中我们会回顾下传统基于 Shader 后处理的图像处理手段。
基于后处理的效果
基于 Shader 可以实现常见的图像处理效果,例如高斯模糊、Perlin 噪音、Glitch 等,当然还有最近火热的“液态玻璃”:


更多效果详见
在实现中,Pixi.js filters 会根据对象的包围盒计算应用区域,将对象渲染到一个临时的渲染纹理(render texture)上,然后再对该纹理应用着色器效果。
渲染到 RenderTarget 中
在 Pixi.js 中
ts
// src/filters/FilterSystem.ts
const quadGeometry = new Geometry({
attributes: {
aPosition: {
buffer: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
format: 'float32x2',
stride: 2 * 4,
offset: 0,
},
},
indexBuffer: new Uint32Array([0, 1, 2, 0, 2, 3]),
});采样
接下来需要从整个画布的纹理中,对目标图形所在的区域进行采样:
glsl
void main() {
v_Uv = a_Position * (u_OutputFrame.zw / u_InputSize.xy) + u_OutputFrame.xy / u_InputSize.xy;
gl_Position = vec4((a_Position * 2.0 - 1.0) * u_OutputTexture.xy / u_OutputFrame.zw * u_OutputTexture.z, 0.0, 1.0);
}全屏三角形
这里就可以使用全屏三角形了,相比 Quad 可以减少一个顶点:
ts
this.#bigTriangleVertexBuffer = this.device.createBuffer({
viewOrSize: new Float32Array([1, 3, -3, -1, 1, -1]),
usage: BufferUsage.VERTEX,
hint: BufferFrequencyHint.DYNAMIC,
});Vertex shader 非常简单,只需要正确映射 v_Uv 纹理坐标即可:
glsl
void main() {
v_Uv = 0.5 * (a_Position + 1.0);
gl_Position = vec4(a_Position, 0.0, 1.0);
#ifdef VIEWPORT_ORIGIN_TL
v_Uv.y = 1.0 - v_Uv.y;
#endif
}Brightness
我们可以使用 CSS filter 语法,例如 filter: brightness(0.4);
glsl
uniform sampler2D u_Texture;
in vec2 v_Uv;
layout(std140) uniform PostProcessingUniforms {
float u_Brightness;
};
out vec4 outputColor;
void main() {
outputColor = texture(SAMPLER_2D(u_Texture), v_Uv);
outputColor.rgb *= u_Brightness;
}渲染图
Render Graph(有时称为 FrameGraph)是一种将渲染过程抽象为有向无环图(DAG)的现代渲染架构。在这一架构下,每个渲染 Pass 以及它们使用的资源都被视为图节点与边,通过图结构自动管理资源状态转换、同步和生命周期。

实现
rs
// @see https://docs.rs/bevy/latest/bevy/render/render_graph/struct.RenderGraph.html
let mut graph = RenderGraph::default();
graph.add_node(Labels::A, MyNode);
graph.add_node(Labels::B, MyNode);
graph.add_node_edge(Labels::B, Labels::A);