From d1ed3662bc984ded339207b34351061c1bdf8831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=81NCZOS=20Vilmos=20Zsombor?= Date: Sat, 31 May 2025 20:18:50 +0200 Subject: [PATCH] mandelbulb --- assets/shaders/material.wgsl | 77 +++++++++++++++++++++++++----------- src/main.rs | 16 ++++++-- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/assets/shaders/material.wgsl b/assets/shaders/material.wgsl index 091b12a..834b8ae 100644 --- a/assets/shaders/material.wgsl +++ b/assets/shaders/material.wgsl @@ -9,13 +9,51 @@ struct VoluMaterial { mesh_translation: vec3, sphere_radius: f32, color: vec4, + model_inverse: mat4x4, + power: f32, + iterations: i32, } @group(2) @binding(100) var volu_material: VoluMaterial; -fn sphere_sdf(p: vec3, center: vec3, radius: f32) -> f32 { - return length(p - center) - radius; +fn mandelbulb_sdf(p: vec3) -> f32 { + var z = p; + var dr = 1.0; + var r = 0.0; + let power = volu_material.power; + let iterations = volu_material.iterations; + + for (var i = 0; i < iterations; i++) { + r = length(z); + if r > 2.0 { + break; + } + + let theta = acos(clamp(z.z / r, -1.0, 1.0)); + let phi = atan2(z.y, z.x); + dr = pow(r, power - 1.0) * power * dr + 1.0; + + let zr = pow(r, power); + let new_theta = theta * power; + let new_phi = phi * power; + + z = zr * vec3( + sin(new_theta) * cos(new_phi), + sin(new_theta) * sin(new_phi), + cos(new_theta) + ) + p; + } + + return 0.5 * log(max(r, 0.001)) * r / dr; +} + +fn mandelbulb_normal(p: vec3) -> vec3 { + let e = 0.001; + let dx = mandelbulb_sdf(p + vec3(e, 0.0, 0.0)) - mandelbulb_sdf(p - vec3(e, 0.0, 0.0)); + let dy = mandelbulb_sdf(p + vec3(0.0, e, 0.0)) - mandelbulb_sdf(p - vec3(0.0, e, 0.0)); + let dz = mandelbulb_sdf(p + vec3(0.0, 0.0, e)) - mandelbulb_sdf(p - vec3(0.0, 0.0, e)); + return normalize(vec3(dx, dy, dz)); } fn raymarch(ray_origin: vec3, ray_direction: vec3) -> f32 { @@ -26,7 +64,7 @@ fn raymarch(ray_origin: vec3, ray_direction: vec3) -> f32 { for (var i = 0; i < max_steps; i++) { let current_pos = ray_origin + t * ray_direction; - let distance = sphere_sdf(current_pos, volu_material.mesh_translation, volu_material.sphere_radius); + let distance = mandelbulb_sdf(current_pos); if distance < epsilon { return t; @@ -42,20 +80,6 @@ fn raymarch(ray_origin: vec3, ray_direction: vec3) -> f32 { return -1.0; } -fn sphere_normal(p: vec3) -> vec3 { - let epsilon = 0.001; - let center = volu_material.mesh_translation; - let radius = volu_material.sphere_radius; - - let gradient = vec3( - sphere_sdf(p + vec3(epsilon, 0.0, 0.0), center, radius) - sphere_sdf(p - vec3(epsilon, 0.0, 0.0), center, radius), - sphere_sdf(p + vec3(0.0, epsilon, 0.0), center, radius) - sphere_sdf(p - vec3(0.0, epsilon, 0.0), center, radius), - sphere_sdf(p + vec3(0.0, 0.0, epsilon), center, radius) - sphere_sdf(p - vec3(0.0, 0.0, epsilon), center, radius) - ); - - return normalize(gradient); -} - @fragment fn fragment( vertex_output: VertexOutput, @@ -66,20 +90,27 @@ fn fragment( let ray_origin = view.world_position; let ray_direction = normalize(in.world_position.xyz - view.world_position); - let t = raymarch(ray_origin, ray_direction); + let local_origin = (volu_material.model_inverse * vec4(ray_origin, 1.0)).xyz; + let local_direction = normalize((volu_material.model_inverse * vec4(ray_direction, 0.0)).xyz); + + let t = raymarch(local_origin, local_direction); var out: FragmentOutput; if t > 0.0 { - let hit_point = ray_origin + t * ray_direction; - let normal = sphere_normal(hit_point); + let local_hit_point = ray_origin + t * ray_direction; + let local_normal = mandelbulb_normal(local_hit_point); - in.world_position = vec4(hit_point, 1.0); - in.world_normal = normal; + let world_hit_point = (transpose(volu_material.model_inverse) * vec4(local_hit_point, 1.0)).xyz; + let world_normal = normalize((transpose(volu_material.model_inverse) * vec4(local_normal, 0.0)).xyz); + + in.world_position = vec4(world_hit_point, 1.0); + in.world_normal = world_normal; var pbr_input = pbr_input_from_standard_material(in, is_front); - pbr_input.material.base_color = alpha_discard(pbr_input.material, pbr_input.material.base_color); + let n = world_normal * 0.5 + 0.5; + pbr_input.material.base_color = alpha_discard(pbr_input.material, vec4(n, 1.0)); out.color = apply_pbr_lighting(pbr_input); diff --git a/src/main.rs b/src/main.rs index cac51fc..4f08275 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,9 @@ fn setup( mesh_translation: transform.translation, sphere_radius: 1.0, color: Vec4::default(), + model_inverse: transform.compute_matrix().inverse(), + power: 8.0, + iterations: 32, })); commands.spawn(( @@ -49,6 +52,7 @@ fn setup( commands.spawn(( DirectionalLight::default(), Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y), + Rotate, )); commands.spawn(( @@ -67,14 +71,12 @@ fn rotate_things(mut q: Query<&mut Transform, With>, time: Res