diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-01 11:10:35 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-01 11:10:35 +0100 |
| commit | f307cde4ac1126e38c5595ce61a26d50cdd7ad4a (patch) | |
| tree | 00b9ac5a74d61c1a58b4d2c7ffcf1eaf1ea55b84 /src/3d/renderer.cc | |
| parent | 8bdc4754647c9c6691130fa91d51fee93c5fc88f (diff) | |
feat: Implement hybrid rendering with SDF primitives
- Added SDF logic for Sphere, Box, and Torus in WGSL.
- Implemented hybrid normal calculation (analytical for sphere, numerical fallback).
- Updated Renderer3D to dispatch object types to shader.
- Updated test_3d_render to display mixed SDF shapes (Sphere, Torus, Box).
- Added BOX to ObjectType enum.
Diffstat (limited to 'src/3d/renderer.cc')
| -rw-r--r-- | src/3d/renderer.cc | 159 |
1 files changed, 135 insertions, 24 deletions
diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc index 1745a97..2e08b4e 100644 --- a/src/3d/renderer.cc +++ b/src/3d/renderer.cc @@ -76,19 +76,14 @@ struct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) local_pos: vec3<f32>, @location(1) color: vec4<f32>, + @location(2) @interpolate(flat) instance_index: u32, + @location(3) world_pos: vec3<f32>, }; @vertex fn vs_main(@builtin(vertex_index) vertex_index: u32, @builtin(instance_index) instance_index: u32) -> VertexOutput { - // Hardcoded cube vertices (similar to C++ array but in shader for simplicity if desired, - // but here we might assume a vertex buffer or just generate logic. - // For this demo, let's use the buffer-less approach for vertices if we want to save space, - // but we have a C++ array. Let's just generate a cube on the fly from index?) - // Actually, let's map the C++ kCubeVertices to a vertex buffer or use a hardcoded array here. - // For 64k size, hardcoded in shader is good. - var pos = array<vec3<f32>, 36>( vec3(-1.0, -1.0, 1.0), vec3( 1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), vec3(-1.0, -1.0, 1.0), vec3( 1.0, 1.0, 1.0), vec3(-1.0, 1.0, 1.0), @@ -113,31 +108,140 @@ fn vs_main(@builtin(vertex_index) vertex_index: u32, var out: VertexOutput; out.position = clip_pos; - out.local_pos = p; + out.local_pos = p; // Proxy geometry local coords (-1 to 1) out.color = obj.color; + out.instance_index = instance_index; + out.world_pos = world_pos.xyz; return out; } +// --- SDF Primitives --- +// All primitives are centered at 0,0,0 + +fn sdSphere(p: vec3<f32>, r: f32) -> f32 { + return length(p) - r; +} + +fn sdBox(p: vec3<f32>, b: vec3<f32>) -> f32 { + let q = abs(p) - b; + return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0); +} + +fn sdTorus(p: vec3<f32>, t: vec2<f32>) -> f32 { + let q = vec2<f32>(length(p.xz) - t.x, p.y); + return length(q) - t.y; +} + +// --- Dispatchers --- + +// Type IDs: 0=Cube(Wireframe proxy), 1=Sphere, 2=Box, 3=Torus +fn get_dist(p: vec3<f32>, type: f32) -> f32 { + if (type == 1.0) { return sdSphere(p, 0.9); } + if (type == 2.0) { return sdBox(p, vec3<f32>(0.7)); } + if (type == 3.0) { return sdTorus(p, vec2<f32>(0.6, 0.25)); } + return 100.0; +} + +// Analytical normals where possible, fallback to Numerical +fn get_normal(p: vec3<f32>, type: f32) -> vec3<f32> { + if (type == 1.0) { // Sphere + return normalize(p); // Center is 0,0,0 + } + + // Finite Difference for others + let e = vec2<f32>(0.001, 0.0); + return normalize(vec3<f32>( + get_dist(p + e.xyy, type) - get_dist(p - e.xyy, type), + get_dist(p + e.yxy, type) - get_dist(p - e.yxy, type), + get_dist(p + e.yyx, type) - get_dist(p - e.yyx, type) + )); +} + @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { - // Simple wireframe-ish effect using barycentric coords logic? - // Or just check proximity to edge of local cube? - let d = abs(in.local_pos); - let edge_dist = max(max(d.x, d.y), d.z); + let obj = object_data.objects[in.instance_index]; + let type = obj.params.x; + + // Case 0: The central cube (Wireframe/Solid Box logic) - Proxy only + if (type == 0.0) { + let d = abs(in.local_pos); + let edge_dist = max(max(d.x, d.y), d.z); + + var col = in.color.rgb; + if (edge_dist > 0.95) { + col = vec3<f32>(1.0, 1.0, 1.0); // White edges + } else { + // Simple face shading + let normal = normalize(cross(dpdx(in.local_pos), dpdy(in.local_pos))); + let light = normalize(vec3<f32>(0.5, 1.0, 0.5)); + let diff = max(dot(normal, light), 0.2); + col = col * diff; + } + return vec4<f32>(col, 1.0); + } + + // Case 1+: Raymarching inside the proxy box + let center = vec3<f32>(obj.model[3].x, obj.model[3].y, obj.model[3].z); + + // Scale: Assume uniform scale from model matrix + let scale = length(vec3<f32>(obj.model[0].x, obj.model[0].y, obj.model[0].z)); + + let ro = globals.camera_pos; + let rd = normalize(in.world_pos - globals.camera_pos); - // Mix object color with edge highlight - var col = in.color.rgb; - if (edge_dist > 0.95) { - col = vec3<f32>(1.0, 1.0, 1.0); // White edges - } else { - // Simple shading - let normal = normalize(cross(dpdx(in.local_pos), dpdy(in.local_pos))); - let light = normalize(vec3<f32>(0.5, 1.0, 0.5)); - let diff = max(dot(normal, light), 0.2); - col = col * diff; + // Start marching at proxy surface + var t = length(in.world_pos - ro); + var p = ro + rd * t; + + // Extract rotation (Normalized columns of model matrix) + let mat3 = mat3x3<f32>( + obj.model[0].xyz / scale, + obj.model[1].xyz / scale, + obj.model[2].xyz / scale + ); + + var hit = false; + // Raymarch Loop + for (var i = 0; i < 40; i++) { + // Transform p to local unscaled space for SDF eval + // q = inv(R) * (p - center) / scale + let q = transpose(mat3) * (p - center) / scale; + + let d_local = get_dist(q, type); + let d_world = d_local * scale; + + if (d_world < 0.001) { + hit = true; + break; + } + if (d_world > 3.0 * scale) { + break; + } + p = p + rd * d_world; } - return vec4<f32>(col, 1.0); + if (!hit) { + discard; + } + + // Shading + // Recompute local pos at hit + let q_hit = transpose(mat3) * (p - center) / scale; + + // Normal calculation: + // Calculate normal in local space, then rotate to world. + let n_local = get_normal(q_hit, type); + let n_world = mat3 * n_local; + + let normal = normalize(n_world); + let light_dir = normalize(vec3<f32>(1.0, 1.0, 1.0)); + + let diff = max(dot(normal, light_dir), 0.0); + let amb = 0.1; + + let lighting = diff + amb; + + return vec4<f32>(in.color.rgb * lighting, 1.0); } )"; @@ -328,7 +432,14 @@ void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera, float ObjectData data; data.model = obj.get_model_matrix(); data.color = obj.color; - // data.params = ... + // Map ObjectType enum to float ID + float type_id = 0.0f; + if (obj.type == ObjectType::SPHERE) type_id = 1.0f; + else if (obj.type == ObjectType::CUBE) type_id = 0.0f; + else if (obj.type == ObjectType::TORUS) type_id = 3.0f; + else if (obj.type == ObjectType::BOX) type_id = 2.0f; + + data.params = vec4(type_id, 0, 0, 0); obj_data.push_back(data); if (obj_data.size() >= kMaxObjects) break; } |
