|
8 | 8 | - [x] Fix `ray_color` in `cam.rs` to accumulate emission with throughput |
9 | 9 | - [x] Create test scene with emissive quad (Cornell box) |
10 | 10 | 2. [x] Quads (axis-aligned and arbitrary) |
11 | | -3. [x] Transforms (translate, rotate, scale) - see design notes at bottom of file |
| 11 | +3. [x] Transforms (translate, rotate, scale) |
12 | 12 | 4. [ ] Image textures |
13 | 13 | 5. [ ] Noise textures (Perlin noise) |
14 | 14 | 6. [ ] Bounding Volume Hierarchy (BVH) |
|
35 | 35 | 5. [x] Grid composition for `test` command |
36 | 36 | - [x] Render each scene at 720p |
37 | 37 | - [x] Stitch into 4x4 grid image |
38 | | -6. [ ] Unit tests for ray-material interactions |
39 | | - - [ ] Shoot specific rays at specific materials, verify resulting scattered ray |
40 | | - - [ ] Test cases: Lambertian diffuse, Metal reflection, Dielectric refraction (entering/exiting) |
41 | | - - [ ] Helps catch regressions like the normal-flip bug from instance transforms |
| 38 | +6. [x] Unit tests for ray-material interactions |
| 39 | + - [x] Shoot specific rays at specific materials, verify resulting scattered ray |
| 40 | + - [x] Test cases: Lambertian diffuse, Metal reflection, Dielectric refraction (entering/exiting) |
| 41 | + - [x] Helps catch regressions like the normal-flip bug from instance transforms |
42 | 42 |
|
43 | 43 | ## Technical Debt |
44 | 44 |
|
45 | 45 | 1. [ ] `Array<T, N>` currently requires `T: Copy + Default` because we use `[Default::default(); N]` for initialization. This is overly restrictive. Ideally, `T` would only need to be "zeroable" (all zero bytes is a valid default). This would allow types like `Sphere` that aren't `Copy` but can be safely zero-initialized. The goal is something like `core::array::from_fn(|_| Default::default())` but in a form rust-gpu accepts. |
46 | | - |
47 | | ---- |
48 | | - |
49 | | -## Design: Instanced Transforms |
50 | | - |
51 | | -### Current Architecture |
52 | | - |
53 | | -The ray tracer currently uses `List` which contains arrays of world-space primitives: |
54 | | - |
55 | | -```rust |
56 | | -// rtx-obj/src/list.rs (current) |
57 | | -struct List { |
58 | | - spheres: Array<Sphere, N>, |
59 | | - quads: Array<Quad, M>, |
60 | | -} |
61 | | -``` |
62 | | - |
63 | | -Each `Sphere` stores its center, radius, material, and precomputed bounding box. |
64 | | -Each `Quad` stores its corner, edge vectors, normal, material, and bounding box. |
65 | | -The `hit()` method iterates both arrays, testing each primitive in world space. |
66 | | - |
67 | | -### New Architecture |
68 | | - |
69 | | -Instead of storing concrete primitives with world-space coordinates, we use |
70 | | -canonical unit primitives (implicit/hardcoded) and transform them via instances. |
71 | | - |
72 | | -**Canonical primitives (not stored, just hardcoded in hit functions):** |
73 | | -- Unit sphere: radius 1, centered at origin |
74 | | -- Unit quad: 1x1, at origin (corners at (0,0,0) and (1,1,0), normal +Z) |
75 | | - |
76 | | -**New types:** |
77 | | - |
78 | | -```rust |
79 | | -enum PrimitiveKind { Sphere, Quad } |
80 | | - |
81 | | -struct Instance { |
82 | | - kind: PrimitiveKind, |
83 | | - transform: Mat4, // object space -> world space |
84 | | - inv_transform: Mat4, // world space -> object space |
85 | | - material: MaterialInfo, |
86 | | -} |
87 | | - |
88 | | -struct Scene { |
89 | | - instances: Array<Instance, N>, |
90 | | -} |
91 | | -``` |
92 | | - |
93 | | -**Hit logic:** |
94 | | -1. For each instance, transform ray to object space via `inv_transform` |
95 | | -2. Test against hardcoded unit sphere or unit quad |
96 | | -3. Transform hit point/normal back to world space via `transform` |
97 | | - |
98 | | -**Mat4 capabilities:** |
99 | | -- Translation: move objects in space |
100 | | -- Rotation: rotate around any axis (start with rotate_y) |
101 | | -- Non-uniform scale: `scale(4, 1, 1)` stretches only on X axis |
102 | | -- Compose as: `translate * rotate * scale` (apply right to left) |
103 | | - |
104 | | -**Mat4 structure (column-major):** |
105 | | -``` |
106 | | -[ Rx Ry Rz | Tx ] R = rotation/scale (upper-left 3x3) |
107 | | -[ Rx Ry Rz | Ty ] T = translation (fourth column) |
108 | | -[ Rx Ry Rz | Tz ] |
109 | | -[ 0 0 0 | 1 ] |
110 | | -``` |
111 | | - |
112 | | -**Transforming Vec3 with Mat4:** |
113 | | -glam's `Mat4` multiplies with `Vec4`, not `Vec3`. Use the `w` component to control behavior: |
114 | | -- **Points** (positions): use `w=1`, affected by translation |
115 | | - `transformed_point = (mat * vec4(point, 1.0)).xyz` |
116 | | -- **Directions** (ray dir, not normalized): use `w=0`, NOT affected by translation |
117 | | - `transformed_dir = (mat * vec4(dir, 0.0)).xyz` |
118 | | -- **Normals**: use transpose of inverse matrix, or since we store `inv_transform`: |
119 | | - `world_normal = (inv_transform.transpose() * vec4(local_normal, 0.0)).xyz` |
120 | | - (then normalize) |
121 | | - |
122 | | -### Implementation Plan |
123 | | - |
124 | | -Phase 1: Add new types (non-breaking, ray tracer still works) |
125 | | -- [x] Re-export `Mat4` from `spirv_std::glam` in `rtx-prim/src/types.rs` |
126 | | - (glam already provides identity, from_translation, from_rotation_y, from_scale, |
127 | | - inverse, and all the Mul impls we need) |
128 | | -- [x] Create `PrimitiveKind` enum in `rtx-obj` |
129 | | -- [x] Create `Instance` struct in `rtx-obj` |
130 | | -- [x] Implement hardcoded `hit_unit_sphere()` and `hit_unit_quad()` functions |
131 | | - |
132 | | -Phase 2: Refactor ray tracer to use instances |
133 | | -- [x] Create new `Scene` type that holds `Array<Instance, N>` |
134 | | -- [x] Update `hit()` logic to iterate instances, transform rays, dispatch to unit primitives |
135 | | -- [x] Migrate existing scenes to use `Instance` with appropriate transforms |
136 | | -- [x] Remove old `Sphere`, `Quad`, and `List` types once migration is complete |
137 | | - |
138 | | -**Note:** The `t` parameter may need adjustment for non-uniform scale. When the ray |
139 | | -is transformed to object space, direction length changes, so the same `t` value |
140 | | -maps to different world-space distances. May need to recompute `t` in world space |
141 | | -after hit, or be careful about comparing `t` values across differently-scaled instances. |
0 commit comments