docs: eliminate shader walkthrough duplication between S4 and shader-basics

This commit is contained in:
2026-05-30 20:22:51 -05:00
parent cad48bd58d
commit ecea7ce77e

View File

@@ -68,60 +68,18 @@ where `w0 + w1 + w2 = 1.0` and the weights are [[barycentric coordinates]](GLOSS
This interpolation is free. It is a dedicated hardware unit inside every GPU. You do not write the code. You do not pay an algorithmic cost. The rasterizer hardware computes barycentric weights and blends every vertex shader output automatically. The fragment shader receives pre-blended values and does not need to know how they were computed. This interpolation is free. It is a dedicated hardware unit inside every GPU. You do not write the code. You do not pay an algorithmic cost. The rasterizer hardware computes barycentric weights and blends every vertex shader output automatically. The fragment shader receives pre-blended values and does not need to know how they were computed.
## Concrete Shader Walkthrough ## How Shaders Work Together
This is the complete shader for the rainbow triangle. Every line is explained below. A complete rendering shader is a two-stage program compiled into a single WGSL module. The **vertex shader** runs once per vertex in your draw call, transforming raw buffer data into GPU-ready outputs. The **fragment shader** runs once per pixel produced by the rasterizer, converting interpolated vertex data into the final color written to the [[framebuffer]](GLOSSARY.md#framebuffer). Both stages execute in parallel across thousands of invocations — the vertex shader processes all vertices simultaneously, then the fragment shader processes all fragments simultaneously.
```wgsl Data flows between the vertex and fragment stages through a shared struct. The struct's fields are tagged with WGSL attributes that tell the GPU how to route each value:
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) vertex_color: vec3<f32>,
};
@vertex - **`@location(n)`** marks values that bind to Rust vertex buffer attributes or flow between shader stages. The number `n` is a binding index: on the Rust side it appears as `shader_location: n` in a `VertexAttribute`, and in WGSL it appears as `@location(n)` on a parameter or struct field. If the numbers differ, the GPU reads from the wrong buffer offset and produces silent garbage. Between the vertex and fragment stages, `@location` values are automatically interpolated by the rasterizer using barycentric weights — the fragment shader receives a smooth blend without writing any interpolation code.
fn vs_main( - **`@builtin(position)`** is a reserved slot the vertex shader must output. It delivers the vertex's [[clip space]](GLOSSARY.md#clip-space) position as `vec4<f32>`, which the rasterizer uses for perspective division, viewport transform, and primitive assembly. The fragment shader receives its own independent `@builtin(position)` from the fragment pipeline stage — providing framebuffer pixel coordinates — not the vertex shader's output. The two builtins share a name but are completely separate values from different stages.
@location(0) position: vec3<f32>,
@location(1) color: vec3<f32>,
) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4<f32>(position, 1.0);
out.vertex_color = color;
return out;
}
@fragment The vertex shader produces a struct containing a `@builtin(position)` output plus any number of `@location` interpolants. The rasterizer takes these outputs, assembles [[primitives]](GLOSSARY.md#primitive), and for every pixel inside the triangle computes [[barycentric coordinates]](GLOSSARY.md#barycentric-coordinates) and blends all `@location` fields. The fragment shader receives the fully interpolated struct and outputs a `vec4<f32>` color at `@location(0)`, which maps to the [[pipeline]](GLOSSARY.md#pipeline)'s color attachment target.
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(input.vertex_color, 1.0);
}
```
### Line by line For a complete line-by-line walkthrough of our rainbow triangle shader, see [Section 4](01-rainbow-triangle.md#section-4-writing-the-shaders).
**`struct VertexOutput { ... }`** — The interface between vertex and fragment stages. This struct defines everything the vertex shader sends downstream. It is the contract the rasterizer enforces.
**`@builtin(position) clip_position: vec4<f32>`** — The mandatory clip-space position output. The `@builtin(position)` annotation tells the GPU this value goes to the primitive assembly / rasterizer pipeline, not to another shader stage. The GPU reads this to know where each vertex sits in 3D space.
**`@location(0) vertex_color: vec3<f32>`** — An interpolant flowing from vertex to fragment stage. The `@location(0)` annotation labels this value with binding index 0. Any `@location(0)` output here becomes the `@location(0)` input to the fragment shader.
**`@vertex fn vs_main(...)`** — The vertex shader entry point. The `@vertex` attribute marks this as the function the vertex pipeline stage calls.
**`@location(0) position: vec3<f32>`** — Vertex buffer input at location 0. In Rust, the vertex buffer's first attribute is declared with `shader_location: 0`. This is the first half of the location contract: the Rust buffer layout and WGSL input must agree.
**`@location(1) color: vec3<f32>`** — Vertex buffer input at location 1. The second vertex attribute in the buffer. Each vertex stores two values: a 3-component position and a 3-component color, contiguous in memory.
**`var out: VertexOutput;`** — Local variable holding the shader output. WGSL requires explicit variable declarations.
**`out.clip_position = vec4<f32>(position, 1.0);`** — Wraps the 3D position in a [[homogeneous coordinates]](GLOSSARY.md#homogeneous-coordinates) `vec4` by appending `w = 1.0`. See [[coordinate-systems.md]](coordinate-systems.md) for why `w = 1.0` is the identity for our triangle.
**`out.vertex_color = color;`** — Passes the vertex color through to the fragment shader. No transformation needed — the color is already the final per-vertex color. The rasterizer will blend across the triangle surface.
**`@fragment fn fs_main(input: VertexOutput) -> ...`** — The fragment shader entry point. It receives one input struct per fragment. This struct contains the rasterizer's pre-interpolated values.
**`input.vertex_color`** — The color value, already blended by the rasterizer. If the current fragment is 70% close to the red vertex, 20% close to green, 10% close to blue, this value is `(0.7*1.0 + 0.2*0.0 + 0.1*0.0, 0.7*0.0 + 0.2*1.0 + 0.1*0.0, 0.7*0.0 + 0.2*0.0 + 0.1*1.0)` = `(0.7, 0.2, 0.1)`. The interpolation was performed by hardware; the fragment shader does not compute it.
**`-> @location(0) vec4<f32>`** — The fragment shader output signature. `@location(0)` maps to the color attachment in the [[pipeline]](GLOSSARY.md#pipeline) render pass. It is the pixel color written to the framebuffer.
**`vec4<f32>(input.vertex_color, 1.0)`** — Wraps the interpolated RGB color in `vec4` by appending alpha = 1.0 (fully opaque). The framebuffer expects a 4-component color.
## WGSL Source Embedding ## WGSL Source Embedding