03 / 12 · Boot the renderer启动渲染器
Teach the GPU to draw a triangle教 GPU 画出第一个三角形
Connect shader stages, draw calls and interpolation to visible pixels.把 shader 阶段、draw call、插值与实际像素连接起来。
Three corners are enough to test most of the drawing path. If a triangle fails, loading a hundred thousand Gaussians will only hide the cause.三个顶点足以检查绘图链路的主要部分。三角形还没画对,就加载十万个 Gaussian,只会更难定位原因。
1. Give each invocation a vertex number1. 给每次执行一个顶点编号
draw(0..3, 0..1) requests three vertices for one instance. The GPU runs vs_main with vertex_index 0, 1 and 2. For this first milestone, positions and colors live in tiny WGSL arrays, so no vertex buffer is needed. A range’s end is excluded: 0..3 means exactly three vertices.draw(0..3, 0..1) 为一个实例请求三个顶点。GPU 用 vertex_index 0、1、2 执行 vs_main。第一阶段把位置和颜色直接放在很小的 WGSL 数组中,不需要顶点 buffer。Rust 区间不包含右端点,所以 0..3 正好是三个顶点。
2. Separate position from interpolated data2. 分开位置和需要插值的数据
The vertex shader returns builtin(position), a four-number clip-space position. With w = 1, dividing xyz by w leaves the numbers unchanged. The x and y range −1 to +1 spans the viewport; WebGPU depth after division is 0 to 1. A location field is a varying: the rasterizer interpolates it across covered samples.顶点 shader 输出 builtin(position),即四个数构成的裁剪空间位置。w = 1 时,xyz 除以 w 后数值不变。x、y 的 −1 到 +1 覆盖视口;WebGPU 除法后的深度范围是 0 到 1。location 字段是 varying,光栅器会在覆盖的采样点之间对它插值。
Our corner colors are red, green and blue. The fragment shader receives mixtures inside the triangle. No CPU loop visits each pixel. Fixed-function triangle rasterization determines coverage, then our fragment program supplies the color. Later, a Gaussian will use that same coverage machinery with a different color/alpha function.三个角的颜色分别是红、绿、蓝,片元 shader 在三角形内部收到混合颜色。CPU 没有逐像素循环:固定功能三角形光栅化负责覆盖,片元程序给出颜色。后面的 Gaussian 也会复用这套覆盖机制,只是换成另一种颜色与透明度函数。
3. A pipeline connects the pieces3. 用 pipeline 把它们连接起来
Create a shader module from WGSL, then a render pipeline using vs_main and fs_main. The output color format must match the surface. TriangleList is the default topology. Depth is disabled for these first experiments; depth testing is not a prerequisite for producing a triangle.从 WGSL 创建 shader module,然后用 vs_main 和 fs_main 创建 render pipeline。输出颜色格式必须与 surface 一致。默认拓扑是 TriangleList。前几个实验关闭深度测试;画出一个三角形不要求先实现深度缓冲。
The workshop keeps one pipeline across the toy stages and branches on a small stage uniform. That is a teaching convenience, not a recommendation to put every engine feature into one giant shader. Once behavior is understood, separate variants when their resource layouts or performance justify it.实验用同一个 pipeline 支持多个小阶段,通过 stage uniform 分支选择,这是教学上的便利,不意味着引擎所有功能都应堆进一个巨大 shader。理解行为后,若资源布局或性能需要,可拆分不同变体。
4. Debug from the outside inward4. 从外到内排查黑屏
First confirm clear/present works. Then verify the pipeline is bound, draw count is three, w is positive and clip positions are in range. Finally inspect fragment output. Change the shader to return solid magenta to isolate varying errors from coverage errors. Rebuild WASM after Rust or WGSL edits; a browser refresh alone cannot compile them.先确认清屏和显示正常,再检查 pipeline 是否绑定、顶点数是否为 3、w 是否为正、裁剪位置是否在范围内,最后看片元输出。让 shader 固定返回洋红色,便能把 varying 错误与覆盖错误分开。修改 Rust 或 WGSL 后要重新构建 WASM,只刷新浏览器不会编译它们。
RUN → OBSERVE运行 → 观察
Make this step concrete把这一步运行起来
A triangle with three colored corners and smooth color interpolation, surrounded by the clear color.一个三个角颜色不同、内部平滑插值的三角形,周围是清屏色。
Open GPU milestone 1 →打开 GPU 里程碑 1 →cargo run --locked --example workshop -- 1
# After a Rust/WGSL edit, rebuild the browser binary:
npm run build:wasmCommands 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 · L30–37
The first shader returns positions and colors directly.第一个 shader 直接返回位置和颜色。
if stage == 1u {
let positions = array<vec2<f32>, 3>(vec2(-0.7, -0.6), vec2(0.7, -0.6), vec2(0.0, 0.7));
let colors = array<vec3<f32>, 3>(vec3(1., 0., 0.), vec3(0., 1., 0.), vec3(0., 0., 1.));
out.clip = vec4(positions[vertex], 0.5, 1.0);
out.local = vec2(0.0);
out.rgba = vec4(colors[vertex], 1.0);
return out;
}Open full source with line numbers ↗打开带行号的完整源码 ↗src/workshop.rs · L196–229
Shader code becomes a pipeline compatible with the render target.Shader 代码组成兼容渲染目标的 pipeline。
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("workshop-shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("workshop.wgsl").into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("workshop-pipeline-layout"),
bind_group_layouts: &[Some(&layout)],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("workshop-pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: Default::default(),
depth_stencil: None,
multisample: Default::default(),
multiview_mask: None,
cache: None,
});Open full source with line numbers ↗打开带行号的完整源码 ↗Real-model render pass ↗真实模型渲染 pass ↗ · Real-model shader ↗真实模型 shader ↗
Predict it, change it, explain it先预测,再修改,再解释
Change the draw count from three to two, predict the result, then restore it. Why does a TriangleList not produce a line?把顶点数从 3 改成 2,预测结果,再还原。为什么 TriangleList 不会因此画出一条线?
Check your reasoning核对推理
Two vertices do not form a complete triangle, so no triangle is rasterized. The topology decides how vertices form primitives; a line requires a line topology, not merely fewer vertices.两个顶点无法组成完整三角形,因此不会光栅化三角形。拓扑决定顶点如何组成图元,画线需要线拓扑,不只是减少顶点数。
Keep the theory beside the code把原理放在代码旁边
Open the related derivation or lab ↗打开对应推导或实验 ↗