summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-01 11:10:35 +0100
committerskal <pascal.massimino@gmail.com>2026-02-01 11:10:35 +0100
commitf307cde4ac1126e38c5595ce61a26d50cdd7ad4a (patch)
tree00b9ac5a74d61c1a58b4d2c7ffcf1eaf1ea55b84 /src
parent8bdc4754647c9c6691130fa91d51fee93c5fc88f (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')
-rw-r--r--src/3d/object.h3
-rw-r--r--src/3d/renderer.cc159
-rw-r--r--src/tests/test_3d_render.cc18
3 files changed, 150 insertions, 30 deletions
diff --git a/src/3d/object.h b/src/3d/object.h
index f4215aa..ccbb1e1 100644
--- a/src/3d/object.h
+++ b/src/3d/object.h
@@ -10,7 +10,8 @@ enum class ObjectType {
CUBE,
SPHERE,
PLANE,
- TORUS
+ TORUS,
+ BOX
// Add more SDF types here
};
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;
}
diff --git a/src/tests/test_3d_render.cc b/src/tests/test_3d_render.cc
index 41bffe6..4be7153 100644
--- a/src/tests/test_3d_render.cc
+++ b/src/tests/test_3d_render.cc
@@ -140,19 +140,27 @@ void init_wgpu() {
void setup_scene() {
g_scene.clear();
- // Center Red Cube
- Object3D center;
+ // Center Red Cube (Wireframe Proxy)
+ Object3D center(ObjectType::CUBE);
center.position = vec3(0, 0, 0);
center.color = vec4(1, 0, 0, 1);
g_scene.add_object(center);
- // Orbiting Green Cubes
+ // Orbiting Objects
for (int i = 0; i < 8; ++i) {
- Object3D obj;
+ ObjectType type = ObjectType::SPHERE;
+ if (i % 3 == 1) type = ObjectType::TORUS;
+ if (i % 3 == 2) type = ObjectType::BOX;
+
+ Object3D obj(type);
float angle = (i / 8.0f) * 6.28318f;
obj.position = vec3(std::cos(angle) * 4.0f, 0, std::sin(angle) * 4.0f);
obj.scale = vec3(0.5f, 0.5f, 0.5f);
- obj.color = vec4(0, 1, 0, 1);
+
+ if (type == ObjectType::SPHERE) obj.color = vec4(0, 1, 0, 1);
+ else if (type == ObjectType::TORUS) obj.color = vec4(0, 0.5, 1, 1);
+ else obj.color = vec4(1, 1, 0, 1);
+
g_scene.add_object(obj);
}
}