Skip to content

Commit e82493a

Browse files
committed
[update] tutorial to have a better structure.
1 parent 88e9950 commit e82493a

1 file changed

Lines changed: 71 additions & 28 deletions

File tree

docs/tutorials/uniform-buffers.md

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,82 @@ title: "Uniform Buffers: Build a Spinning Triangle"
33
document_id: "uniform-buffers-tutorial-2025-10-17"
44
status: "draft"
55
created: "2025-10-17T00:00:00Z"
6-
last_updated: "2025-10-17T00:15:00Z"
7-
version: "0.2.0"
6+
last_updated: "2025-10-30T00:10:00Z"
7+
version: "0.4.0"
88
engine_workspace_version: "2023.1.30"
99
wgpu_version: "26.0.1"
1010
shader_backend_default: "naga"
1111
winit_version: "0.29.10"
12-
repo_commit: "00aababeb76370ebdeb67fc12ab4393aac5e4193"
12+
repo_commit: "88e99500def9a1c1a123c960ba46b5ba7bdc7bab"
1313
owners: ["lambda-sh"]
1414
reviewers: ["engine", "rendering"]
1515
tags: ["tutorial", "graphics", "uniform-buffers", "rust", "wgpu"]
1616
---
1717

18+
## Overview <a name="overview"></a>
1819
Uniform buffer objects (UBOs) are a standard mechanism to pass per‑frame or per‑draw constants to shaders. This document demonstrates a minimal 3D spinning triangle that uses a UBO to provide a model‑view‑projection matrix to the vertex shader.
1920

2021
Reference implementation: `crates/lambda-rs/examples/uniform_buffer_triangle.rs`.
2122

22-
Goals
23+
## Table of Contents
24+
- [Overview](#overview)
25+
- [Goals](#goals)
26+
- [Prerequisites](#prerequisites)
27+
- [Requirements and Constraints](#requirements-and-constraints)
28+
- [Data Flow](#data-flow)
29+
- [Implementation Steps](#implementation-steps)
30+
- [Step 1 — Runtime and Component Skeleton](#step-1)
31+
- [Step 2 — Vertex and Fragment Shaders](#step-2)
32+
- [Step 3 — Mesh Data and Vertex Layout](#step-3)
33+
- [Step 4 — Uniform Data Layout in Rust](#step-4)
34+
- [Step 5 — Bind Group Layout at Set 0](#step-5)
35+
- [Step 6 — Create the Uniform Buffer and Bind Group](#step-6)
36+
- [Step 7 — Build the Render Pipeline](#step-7)
37+
- [Step 8 — Per‑Frame Update and Write](#step-8)
38+
- [Step 9 — Issue Draw Commands](#step-9)
39+
- [Step 10 — Handle Window Resize](#step-10)
40+
- [Validation](#validation)
41+
- [Notes](#notes)
42+
- [Exercises](#exercises)
43+
- [Changelog](#changelog)
44+
45+
## Goals <a name="goals"></a>
46+
2347
- Build a spinning triangle that reads a model‑view‑projection matrix from a uniform buffer.
2448
- Learn how to define a uniform block in shaders and mirror it in Rust.
2549
- Learn how to create a bind group layout, allocate a uniform buffer, and write per‑frame data.
2650
- Learn how to construct a render pipeline and issue draw commands using Lambda’s builders.
2751

28-
Prerequisites
52+
## Prerequisites <a name="prerequisites"></a>
2953
- Rust toolchain installed and the workspace builds: `cargo build --workspace`.
3054
- Familiarity with basic Rust and the repository’s example layout.
3155
- Ability to run examples: `cargo run --example minimal` verifies setup.
3256

33-
Requirements and constraints
57+
## Requirements and Constraints <a name="requirements-and-constraints"></a>
3458
- The uniform block layout in the shader and the Rust structure MUST match in size, alignment, and field order.
3559
- The bind group layout in Rust MUST match the shader `set` and `binding` indices.
3660
- Matrices MUST be provided in the order expected by the shader (column‑major in this example). Rationale: prevents implicit driver conversions and avoids incorrect transforms.
3761
- Acronyms MUST be defined on first use (e.g., uniform buffer object (UBO)).
3862

39-
Data flow
63+
## Data Flow <a name="data-flow"></a>
4064
- CPU writes → UBO → bind group (set 0) → pipeline layout → vertex shader.
4165
- A single UBO MAY be reused across multiple draws and pipelines.
4266

43-
Implementation Steps
67+
ASCII diagram
68+
69+
```
70+
CPU (matrix calc)
71+
│ write_value
72+
73+
Uniform Buffer (UBO)
74+
│ binding 0, set 0
75+
76+
Bind Group ──▶ Pipeline Layout ──▶ Render Pipeline ──▶ Vertex Shader
77+
```
78+
79+
## Implementation Steps <a name="implementation-steps"></a>
4480

45-
1) Runtime and component skeleton
81+
### Step 1 — Runtime and Component Skeleton <a name="step-1"></a>
4682
Before rendering, create a minimal application entry point and a `Component` that receives lifecycle callbacks. The engine routes initialization, input, updates, and rendering through the component interface, which provides the context needed to create GPU resources and submit commands.
4783

4884
```rust
@@ -83,7 +119,7 @@ fn main() {
83119
}
84120
```
85121

86-
2) Vertex and fragment shaders
122+
### Step 2 — Vertex and Fragment Shaders <a name="step-2"></a>
87123
Define shader stages next. The vertex shader declares three vertex attributes and a uniform block at set 0, binding 0. It multiplies the incoming position by the matrix stored in the UBO. The fragment shader returns the interpolated color. Declaring the uniform block now establishes the contract that the Rust side will satisfy via a matching bind group layout and buffer.
88124

89125
```glsl
@@ -138,7 +174,7 @@ let vertex_shader: Shader = shader_builder.build(vertex_virtual);
138174
let fragment_shader: Shader = shader_builder.build(fragment_virtual);
139175
```
140176

141-
3) Mesh data and vertex layout
177+
### Step 3 — Mesh Data and Vertex Layout <a name="step-3"></a>
142178
Provide vertex data for a single triangle and describe how the pipeline reads it. Each vertex stores position, normal, and color as three `f32` values. The attribute descriptors specify locations and byte offsets so the pipeline can interpret the packed buffer consistently across platforms.
143179

144180
```rust
@@ -175,7 +211,7 @@ let mesh: Mesh = mesh_builder
175211
.build();
176212
```
177213

178-
4) Uniform data layout in Rust
214+
### Step 4 — Uniform Data Layout in Rust <a name="step-4"></a>
179215
Mirror the shader’s uniform block with a Rust structure. Use `#[repr(C)]` so the memory layout is predictable. A `mat4` in the shader corresponds to a 4×4 `f32` array here. Many GPU interfaces expect column‑major matrices; transpose before upload if the local math library is row‑major. This avoids implicit driver conversions and prevents incorrect transforms.
180216

181217
```rust
@@ -186,7 +222,7 @@ pub struct GlobalsUniform {
186222
}
187223
```
188224

189-
5) Bind group layout at set 0
225+
### Step 5 — Bind Group Layout at Set 0 <a name="step-5"></a>
190226
Create a bind group layout that matches the shader declaration. This layout says: at set 0, binding 0 there is a uniform buffer visible to the vertex stage. The pipeline layout will incorporate this, ensuring the shader and the bound resources agree at draw time.
191227

192228
```rust
@@ -197,7 +233,7 @@ let layout = BindGroupLayoutBuilder::new()
197233
.build(render_context);
198234
```
199235

200-
6) Create the uniform buffer and bind group
236+
### Step 6 — Create the Uniform Buffer and Bind Group <a name="step-6"></a>
201237
Allocate the uniform buffer, seed it with an initial matrix, and create a bind group using the layout. Mark the buffer usage as `UNIFORM` and properties as `CPU_VISIBLE` to permit direct per‑frame writes from the CPU. This is the simplest path for frequently updated data.
202238

203239
```rust
@@ -221,7 +257,7 @@ let bind_group = BindGroupBuilder::new()
221257
.build(render_context);
222258
```
223259

224-
7) Build the render pipeline
260+
### Step 7 — Build the Render Pipeline <a name="step-7"></a>
225261
Construct the render pipeline, supplying the bind group layouts, vertex buffer, and the shader pair. Disable face culling for simplicity so both sides of the triangle remain visible regardless of winding during early experimentation.
226262

227263
```rust
@@ -242,7 +278,7 @@ let pipeline = RenderPipelineBuilder::new()
242278
.build(render_context, &render_pass, &vertex_shader, Some(&fragment_shader));
243279
```
244280

245-
8) Per‑frame update and write
281+
### Step 8 — Per‑Frame Update and Write <a name="step-8"></a>
246282
Animate by recomputing the model‑view‑projection matrix each frame and writing it into the uniform buffer. The helper `compute_model_view_projection_matrix_about_pivot` maintains a correct aspect ratio using the current window dimensions and rotates the model around a chosen pivot.
247283

248284
```rust
@@ -269,7 +305,7 @@ fn update_uniform_each_frame(
269305
&camera,
270306
width.max(1),
271307
height.max(1),
272-
[0.0, -1.0 / 3.0, 0.0], // pivot
308+
[0.0, -1.0 / 3.0, 0.0], // pivot at triangle centroid (geometric center)
273309
[0.0, 1.0, 0.0], // axis
274310
angle_in_turns,
275311
0.5, // scale
@@ -281,7 +317,7 @@ fn update_uniform_each_frame(
281317
}
282318
```
283319

284-
9) Issue draw commands
320+
### Step 9 — Issue Draw Commands <a name="step-9"></a>
285321
Record commands in the order the GPU expects: begin the render pass, set the pipeline, configure viewport and scissors, bind the vertex buffer and the uniform bind group, draw the vertices, then end the pass. This sequence describes the full state required for a single draw.
286322

287323
```rust
@@ -304,7 +340,7 @@ let commands = vec![
304340
];
305341
```
306342

307-
10) Handle window resize
343+
### Step 10 — Handle Window Resize <a name="step-10"></a>
308344
Track window dimensions and update the per‑frame matrix using the new aspect ratio. Forwarding resize events into stored `width` and `height` maintains consistent camera projection across resizes.
309345

310346
```rust
@@ -321,18 +357,18 @@ fn on_event(&mut self, event: Events) -> Result<ComponentResult, String> {
321357
}
322358
```
323359

324-
Validation
360+
## Validation <a name="validation"></a>
325361
- Build the workspace: `cargo build --workspace`
326362
- Run the example: `cargo run --example uniform_buffer_triangle`
327363

328-
Notes
364+
## Notes <a name="notes"></a>
329365
- Layout matching: The Rust `GlobalsUniform` MUST match the shader block layout. Keep `#[repr(C)]` and follow alignment rules.
330366
- Matrix order: The shader expects column‑major matrices, so the uploaded matrix MUST be transposed if the local math library uses row‑major.
331367
- Binding indices: The Rust bind group layout and `.with_uniform(0, ...)`, plus the shader `set = 0, binding = 0`, MUST be consistent.
332368
- Update strategy: `CPU_VISIBLE` buffers SHOULD be used for per‑frame updates; device‑local memory MAY be preferred for static data.
333369
- Pipeline layout: All bind group layouts used by the pipeline MUST be included via `.with_layouts(...)`.
334370

335-
Exercises
371+
## Exercises <a name="exercises"></a>
336372

337373
- Exercise 1: Time‑based fragment color
338374
- Implement a second UBO at set 0, binding 1 with a `float time_seconds`.
@@ -344,22 +380,29 @@ Exercises
344380
- Add input to adjust orbit speed.
345381

346382
- Exercise 3: Two objects with dynamic offsets
347-
- Pack two `GlobalsUniform` matrices into one UBO and issue two draws with different dynamic offsets.
383+
- Pack two `GlobalsUniform` matrices into one UBO and issue two draws with
384+
different dynamic offsets.
348385
- Use `dynamic_offsets` in `RenderCommand::SetBindGroup`.
349386

350387
- Exercise 4: Basic Lambert lighting
351388
- Extend shaders to compute diffuse lighting.
352389
- Provide a lighting UBO at binding 2 with light position and color.
353390

354391
- Exercise 5: Push constants comparison
355-
- Port to push constants (see `crates/lambda-rs/examples/push_constants.rs`) and compare trade‑offs.
392+
- Port to push constants (see `crates/lambda-rs/examples/push_constants.rs`)
393+
and compare trade‑offs.
356394

357395
- Exercise 6: Per‑material uniforms
358-
- Split per‑frame and per‑material data; use a shared frame UBO and a per‑material UBO (e.g., tint color).
396+
- Split per‑frame and per‑material data; use a shared frame UBO and a
397+
per‑material UBO (e.g., tint color).
359398

360399
- Exercise 7: Shader hot‑reload (stretch)
361-
- Rebuild shaders on file changes and re‑create the pipeline while preserving UBOs and bind groups.
400+
- Rebuild shaders on file changes and re‑create the pipeline while preserving
401+
UBOs and bind groups.
402+
403+
## Changelog <a name="changelog"></a>
362404

363-
Changelog
364-
- 0.2.0 (2025‑10‑17): Added goals and book‑style step explanations; expanded rationale before code blocks; refined validation and notes.
405+
- 0.4.0 (2025‑10‑30): Added table of contents with links; converted sections to anchored headings; added ASCII data flow diagram; metadata updated.
406+
- 0.2.0 (2025‑10‑17): Added goals and book‑style step explanations; expanded
407+
rationale before code blocks; refined validation and notes.
365408
- 0.1.0 (2025‑10‑17): Initial draft aligned with `crates/lambda-rs/examples/uniform_buffer_triangle.rs`.

0 commit comments

Comments
 (0)