Loading WebGPU example...
More info
Back to the demo ↑

WebGPU notes   /   01

WebGPU Triangle in Rust: wgpu and WGSL

This WebGPU triangle example uses Rust, wgpu, and WGSL to draw three colored vertices. Follow the vertex buffer, index buffer, shaders, and draw call from source code to pixels in the live demo above.

Vertices
3
Draw calls / frame
1
Color textures
0

Why start with a triangle?

A triangle is the graphics equivalent of “Hello, world.” The result is simple, but getting it onto the screen connects the essential parts of a renderer: data buffers, shaders, a render pipeline, and a draw call.

My aim with this example is to keep that path easy to follow. It is a static triangle on a black background, with blue at the top, green at the bottom left, and red at the bottom right. There is no camera to move or animation to control. The interesting part is how those three colors fill the shape.

The application is written in Rust, using wgpu through the shared sib::render framework. In the browser, the Rust application runs as WebAssembly and uses WebGPU to render; the GPU runs the WGSL shaders.

A WebGPU triangle with a blue top, green bottom left, red bottom right, and smoothly interpolated colors on a black background.
The rendered triangle: blue, green, and red vertex colors blend across its surface through interpolation. This screenshot is also available when the live WebGPU demo cannot run.

Vertex and index buffers in Rust

Each vertex holds a position and an RGB color. In the Rust source, those are two arrays of three floating-point values:

struct Vertex {
    position: [f32; 3],
    color: [f32; 3],
}

The vertex buffer layout connects position to shader location 0 and color to location 1. The vertices sit at (-0.5, -0.5, 0), (0.5, -0.5, 0), and (0, 0.75, 0). These are coordinates passed directly to clip space with w = 1, not pixel positions.

A separate index buffer contains [0, 1, 2]: use the first, second, and third vertices to form one triangle. Indexing does not save any vertices here, but it introduces the same approach used to share vertices across larger meshes.

WGSL shaders and color interpolation

The WGSL shader has two entry points. The vertex shader passes through the position and color. The fragment shader returns the color it receives, with an alpha of 1.0:

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;
    output.clip_position = vec4<f32>(input.position, 1.0);
    output.color = input.color;
    return output;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    return vec4<f32>(input.color, 1.0);
}

Between those stages, the rasterizer determines which parts of the screen the triangle covers and interpolates the vertex colors. Fragments near the blue vertex receive more blue; fragments between vertices receive a mixture. That smooth gradient needs no color texture, lighting calculation, or hand-written gradient in the fragment shader. The WGSL interpolation rules describe how values pass between these stages.

From the render pipeline to pixels

The pipeline ties the vertex layout and shader entry points to the render target. Each frame, the example clears the background to black, binds the pipeline and buffers, and issues one indexed draw:

// Three indices, starting at vertex 0, one instance.
render_pass.draw_indexed(0..3, 0, 0..1);

A depth attachment is also cleared and used with depth writes and a LessEqual comparison. It makes little visual difference for one flat triangle, but shows where depth testing belongs when more geometry is added. The depth texture is recreated when the canvas resizes.

If you want to get familiar with the GPU pipeline, you can watch this GPU pipeline video.

Run and modify the example

Start by giving all three vertices the same color, then move a vertex. Those two edits make the relationship between the buffer data and the result easy to see. Next, add a fourth vertex and indices for a second triangle to explore vertex reuse.

From a local checkout with Rust installed, run the native example:

cargo run --example triangle

For the browser build, install the WebAssembly target and the wasm-bindgen CLI version matching Cargo.lock, then run:

scripts/build-wasm.sh --release triangle
cargo run --bin serve

Open http://127.0.0.1:8080/triangle/. The live demo needs a browser and GPU configuration that support WebGPU; these notes are still readable without it. See MDN’s WebGPU compatibility table for current support.