05 / 12 · Build the splat构建 splat
Turn a rectangle into a soft splat把矩形变成柔软的 Gaussian
Implement Gaussian falloff, premultiplied alpha and order-dependent composition.实现高斯衰减、预乘透明度以及依赖顺序的合成。
The rectangle only decides where fragments may run. The Gaussian function decides how much each fragment contributes. Separating those two ideas explains both the soft shape and why large splats can be expensive.矩形只决定哪些区域可能运行片元 shader,高斯函数决定每个片元贡献多少。分清这两件事,就能解释柔软形状,也能理解大 splat 为什么耗性能。
1. Put a local coordinate on the rectangle1. 给矩形放一套局部坐标
The vertex shader assigns local coordinates from −3 to +3 along both axes. The rasterizer interpolates them. At the center, local = (0, 0); one standard deviation to the right is (1, 0). These coordinates describe distance inside this Gaussian, not meters in the scene.顶点 shader 给两个方向赋予 −3 到 +3 的局部坐标,光栅器负责插值。中心是 (0, 0),往右一个标准差是 (1, 0)。它描述在当前 Gaussian 内的距离,不是场景中的米。
2. Calculate one fragment by hand2. 手算一个片元
Square the two coordinates, add them, multiply by −1/2 and exponentiate. At (0,0) the weight is 1. At (1,0) it is about 0.6065. With base opacity 0.8, alpha is about 0.4852 there. Stage 3 now reveals a soft orange splat inside the same two triangles. We discard alpha below 1/255 and cap it at 0.99, matching the teaching baseline.把两个坐标分别平方,相加,乘 −1/2,再求指数。(0,0) 的权重是 1;(1,0) 约为 0.6065。若基础不透明度为 0.8,这里的 alpha 约为 0.4852。阶段 3 会在同样两个三角形中显出柔软的橙色 splat。实验与教学基线一样,丢弃小于 1/255 的 alpha,并限制最高为 0.99。
3. Pre-multiply exactly once3. 颜色只预乘一次
The shader returns (alpha × RGB, alpha), so the pipeline uses PREMULTIPLIED_ALPHA_BLENDING. New foreground color replaces a fraction of the old background: C = alpha × foreground + (1 − alpha) × background. Returning straight RGB into that blend rule makes transparent edges too bright; multiplying again in the blend makes them too dark.Shader 返回 (alpha × RGB, alpha),所以 pipeline 使用 PREMULTIPLIED_ALPHA_BLENDING。新前景替换旧背景的一部分:C = alpha × 前景 + (1 − alpha) × 背景。若在这种规则下返回未预乘 RGB,透明边缘会过亮;若在混合时再乘一次,则会过暗。
4. Reverse two splats and predict the color4. 反转两个 splat,预测颜色
Stage 4 draws a far blue-tinted splat (0, 0.25, 1) then a near red splat (1, 0, 0), each with center opacity 0.5. Over black, their ideal center RGB is (0.5, 0.0625, 0.25). Reverse the order and it becomes (0.25, 0.125, 0.5). Pixel centers rarely coincide exactly with the mathematical center, so measured pixels differ slightly.阶段 4 先画远处的蓝色 splat (0, 0.25, 1),再画近处的红色 splat (1, 0, 0),中心透明度都是 0.5。黑底上理想中心 RGB 为 (0.5, 0.0625, 0.25),反转顺序则变为 (0.25, 0.125, 0.5)。像素中心通常不会刚好与数学中心重合,所以实测会略有差别。
This path draws back to front and lets fixed-function blending compose colors. The common compute rasterizer instead walks sorted splats front to back while accumulating transmittance. Both can express the same over operation, but their loop direction and update equations must agree.此路径从后往前绘制,由固定功能混合完成合成。常见 compute 光栅器则从前往后遍历排序后的 splat,同时累计透射率。两者可以表达相同的 over 运算,但循环方向必须与更新公式匹配。
RUN → OBSERVE运行 → 观察
Make this step concrete把这一步运行起来
Stage 3: one soft orange splat. Stage 4: overlapping red and blue; reversing order changes the overlap color.阶段 3:一个柔软橙色 splat。阶段 4:红蓝重叠,反转顺序会改变重叠颜色。
Open GPU milestone 4 →打开 GPU 里程碑 4 →cargo run --locked --example workshop -- 3
# Press 4, or open stage 4 online and toggle Reverse draw order.
cargo run --locked --example workshop_pixelsCommands use the reference repository. Rust/WGSL changes need a native rebuild or npm run build:wasm, followed by a browser reload.命令以参考仓库为基础。修改 Rust/WGSL 后需重新编译桌面程序,或运行 npm run build:wasm 后刷新浏览器。
IDEA → IMPLEMENTATION想法 → 实现
Follow the running code沿着真实代码看
Excerpts are extracted from the repository at site build time. Expand only the part you are studying; full files include context and line numbers.片段在网站构建时直接从仓库提取。只展开正在研究的部分,完整文件包含上下文与行号。
src/workshop.wgsl · L72–79
These five operations turn coverage into Gaussian opacity and color.这几个操作将覆盖区域变成高斯透明度和颜色。
@fragment fn fs_main(input: Out) -> @location(0) vec4<f32> {
let stage = u32(globals.viewport_stage.z);
if stage <= 2u { return vec4(input.rgba.rgb, 1.0); }
let weight = exp(-0.5 * dot(input.local, input.local));
let alpha = min(0.99, input.rgba.a * weight);
if alpha < 1.0 / 255.0 { discard; }
return vec4(input.rgba.rgb * alpha, alpha);
}Open full source with line numbers ↗打开带行号的完整源码 ↗Real-model render pass ↗真实模型渲染 pass ↗ · Real-model shader ↗真实模型 shader ↗
Predict it, change it, explain it先预测,再修改,再解释
Before toggling order, write down which channel will get stronger. Then set both opacities near zero in Rust and predict the background.切换顺序前,写下哪个颜色通道会变强。再在 Rust 里把两个不透明度都调到接近零,预测背景。
Check your reasoning核对推理
Putting blue last raises blue and lowers red. Near-zero opacity approaches the background. Turning on depth writes for these transparent quads would discard contributions rather than correctly solve transparency.最后画蓝色会让蓝色增强、红色减弱。不透明度接近零时,结果趋近背景。给透明四边形打开深度写入会丢掉贡献,并不能正确解决透明合成。
Keep the theory beside the code把原理放在代码旁边
Open the related derivation or lab ↗打开对应推导或实验 ↗