From f32afcbeffa0e8b947457c67c73566da4ebf33cc Mon Sep 17 00:00:00 2001 From: skal Date: Tue, 3 Feb 2026 08:18:13 +0100 Subject: refactor: Shader Asset Integration (Task #24) Extracted all hardcoded WGSL shaders into external assets. Updated AssetManager to handle shader snippets. Refactored Renderer3D, VisualDebug, and Effects to load shaders via the AssetManager, enabling better shader management and composition. --- assets/final/demo_assets.txt | 16 ++ assets/final/shaders/chroma_aberration.wgsl | 30 ++++ assets/final/shaders/common_uniforms.wgsl | 14 ++ assets/final/shaders/distort.wgsl | 27 ++++ assets/final/shaders/ellipse.wgsl | 57 +++++++ assets/final/shaders/gaussian_blur.wgsl | 33 ++++ assets/final/shaders/lighting.wgsl | 23 +++ assets/final/shaders/main_shader.wgsl | 29 ++++ assets/final/shaders/particle_compute.wgsl | 35 ++++ assets/final/shaders/particle_render.wgsl | 44 +++++ assets/final/shaders/particle_spray_compute.wgsl | 40 +++++ assets/final/shaders/passthrough.wgsl | 24 +++ assets/final/shaders/ray_box.wgsl | 16 ++ assets/final/shaders/renderer_3d.wgsl | 194 +++++++++++++++++++++++ assets/final/shaders/sdf_primitives.wgsl | 14 ++ assets/final/shaders/solarize.wgsl | 37 +++++ assets/final/shaders/visual_debug.wgsl | 27 ++++ assets/final/test_assets_list.txt | 6 + 18 files changed, 666 insertions(+) create mode 100644 assets/final/shaders/chroma_aberration.wgsl create mode 100644 assets/final/shaders/common_uniforms.wgsl create mode 100644 assets/final/shaders/distort.wgsl create mode 100644 assets/final/shaders/ellipse.wgsl create mode 100644 assets/final/shaders/gaussian_blur.wgsl create mode 100644 assets/final/shaders/lighting.wgsl create mode 100644 assets/final/shaders/main_shader.wgsl create mode 100644 assets/final/shaders/particle_compute.wgsl create mode 100644 assets/final/shaders/particle_render.wgsl create mode 100644 assets/final/shaders/particle_spray_compute.wgsl create mode 100644 assets/final/shaders/passthrough.wgsl create mode 100644 assets/final/shaders/ray_box.wgsl create mode 100644 assets/final/shaders/renderer_3d.wgsl create mode 100644 assets/final/shaders/sdf_primitives.wgsl create mode 100644 assets/final/shaders/solarize.wgsl create mode 100644 assets/final/shaders/visual_debug.wgsl (limited to 'assets/final') diff --git a/assets/final/demo_assets.txt b/assets/final/demo_assets.txt index a425e38..3099afe 100644 --- a/assets/final/demo_assets.txt +++ b/assets/final/demo_assets.txt @@ -3,3 +3,19 @@ KICK_1, NONE, kick1.spec, "A drum kick sample" SNARE_1, NONE, snare1.spec, "A snare drum sample" HIHAT_1, NONE, hihat1.spec, "A hi-hat sample" NOISE_TEX, PROC(gen_noise, 1234, 16), _, "Procedural noise texture for bump mapping" +SHADER_RENDERER_3D, NONE, shaders/renderer_3d.wgsl, "Hybrid 3D Renderer Shader" +SHADER_COMMON_UNIFORMS, NONE, shaders/common_uniforms.wgsl, "Common Uniforms Snippet" +SHADER_SDF_PRIMITIVES, NONE, shaders/sdf_primitives.wgsl, "SDF Primitives Snippet" +SHADER_LIGHTING, NONE, shaders/lighting.wgsl, "Lighting Snippet" +SHADER_RAY_BOX, NONE, shaders/ray_box.wgsl, "Ray-Box Intersection Snippet" +SHADER_MAIN, NONE, shaders/main_shader.wgsl, "Main Heptagon Shader" +SHADER_PARTICLE_COMPUTE, NONE, shaders/particle_compute.wgsl, "Particle Compute Shader" +SHADER_PARTICLE_RENDER, NONE, shaders/particle_render.wgsl, "Particle Render Shader" +SHADER_PASSTHROUGH, NONE, shaders/passthrough.wgsl, "Passthrough Shader" +SHADER_ELLIPSE, NONE, shaders/ellipse.wgsl, "Ellipse Shader" +SHADER_PARTICLE_SPRAY_COMPUTE, NONE, shaders/particle_spray_compute.wgsl, "Particle Spray Compute" +SHADER_GAUSSIAN_BLUR, NONE, shaders/gaussian_blur.wgsl, "Gaussian Blur Shader" +SHADER_SOLARIZE, NONE, shaders/solarize.wgsl, "Solarize Shader" +SHADER_DISTORT, NONE, shaders/distort.wgsl, "Distort Shader" +SHADER_CHROMA_ABERRATION, NONE, shaders/chroma_aberration.wgsl, "Chroma Aberration Shader" +SHADER_VISUAL_DEBUG, NONE, shaders/visual_debug.wgsl, "Visual Debug Shader" diff --git a/assets/final/shaders/chroma_aberration.wgsl b/assets/final/shaders/chroma_aberration.wgsl new file mode 100644 index 0000000..cd80afa --- /dev/null +++ b/assets/final/shaders/chroma_aberration.wgsl @@ -0,0 +1,30 @@ +@group(0) @binding(0) var smplr: sampler; +@group(0) @binding(1) var txt: texture_2d; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; + +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1, -1), + vec2(3, -1), + vec2(-1, 3) + ); + return vec4(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + let uv = p.xy / uniforms.resolution; + let off = 0.02 * uniforms.intensity; + let r = textureSample(txt, smplr, uv + vec2(off, 0.0)).r; + let g = textureSample(txt, smplr, uv).g; + let b = textureSample(txt, smplr, uv - vec2(off, 0.0)).b; + return vec4(r, g, b, 1.0); +} diff --git a/assets/final/shaders/common_uniforms.wgsl b/assets/final/shaders/common_uniforms.wgsl new file mode 100644 index 0000000..3c9e34b --- /dev/null +++ b/assets/final/shaders/common_uniforms.wgsl @@ -0,0 +1,14 @@ +struct GlobalUniforms { + view_proj: mat4x4, + camera_pos_time: vec4, + params: vec4, +}; +struct ObjectData { + model: mat4x4, + inv_model: mat4x4, + color: vec4, + params: vec4, +}; +struct ObjectsBuffer { + objects: array, +}; diff --git a/assets/final/shaders/distort.wgsl b/assets/final/shaders/distort.wgsl new file mode 100644 index 0000000..2d5f276 --- /dev/null +++ b/assets/final/shaders/distort.wgsl @@ -0,0 +1,27 @@ +@group(0) @binding(0) var smplr: sampler; +@group(0) @binding(1) var txt: texture_2d; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; + +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1, -1), + vec2(3, -1), + vec2(-1, 3) + ); + return vec4(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + let uv = p.xy / uniforms.resolution; + let dist = 0.1 * uniforms.intensity * sin(uv.y * 20.0 + uniforms.time * 5.0); + return textureSample(txt, smplr, uv + vec2(dist, 0.0)); +} diff --git a/assets/final/shaders/ellipse.wgsl b/assets/final/shaders/ellipse.wgsl new file mode 100644 index 0000000..be236c8 --- /dev/null +++ b/assets/final/shaders/ellipse.wgsl @@ -0,0 +1,57 @@ +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; + +@group(0) @binding(0) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1.0, -1.0), + vec2(3.0, -1.0), + vec2(-1.0, 3.0) + ); + return vec4(pos[i], 0.0, 1.0); +} + +fn sdEllipse(p: vec2, ab: vec2) -> f32 { + var p_abs = abs(p); + if (p_abs.x > p_abs.y) { + p_abs = vec2(p_abs.y, p_abs.x); + } + let l = ab.y * ab.y - ab.x * ab.x; + let m = ab.x * p_abs.x / l; + let n = ab.y * p_abs.y / l; + let m2 = m * m; + let n2 = n * n; + let c = (m2 + n2 - 1.0) / 3.0; + let c3 = c * c * c; + let d = c3 + m2 * n2; + let g = m + m * n2; + var co: f32; + if (d < 0.0) { + let h = acos((c3 + m2 * n2 * 2.0) / c3) / 3.0; + let s = cos(h); + let t = sin(h) * sqrt(3.0); + co = (sqrt(-c * (s + t * 2.0) + m2) + sign(l) * sqrt(-c * (s - t * 2.0) + m2) + abs(g) / (sqrt(-c * (s + t * 2.0) + m2) * sqrt(-c * (s - t * 2.0) + m2)) - m) / 2.0; + } else { + let h = 2.0 * m * n * sqrt(d); + let s = sign(c3 + m2 * n2 + h) * pow(abs(c3 + m2 * n2 + h), 1.0 / 3.0); + let u = sign(c3 + m2 * n2 - h) * pow(abs(c3 + m2 * n2 - h), 1.0 / 3.0); + let rx = -s - u + m2 * 2.0; + let ry = (s - u) * sqrt(3.0); + co = (ry / sqrt(sqrt(rx * rx + ry * ry) - rx) + 2.0 * g / sqrt(rx * rx + ry * ry) - m) / 2.0; + } + let si = sqrt(max(0.0, 1.0 - co * co)); + return length(p_abs - vec2(ab.x * co, ab.y * si)) * sign(p_abs.y * ab.x * co - p_abs.x * ab.y * si); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + let uv = (p.xy / uniforms.resolution - 0.5) * 2.0; + let movement = vec2(sin(uniforms.time * 0.7), cos(uniforms.time * 0.5)); + let d = sdEllipse((uv * vec2(uniforms.aspect_ratio, 1.0)) - movement, vec2(0.5, 0.3) * (1.0 + uniforms.beat * 0.2)); + return mix(vec4(0.2, 0.8, 0.4, 1.0), vec4(0.0), smoothstep(0.0, 0.01, d)); +} diff --git a/assets/final/shaders/gaussian_blur.wgsl b/assets/final/shaders/gaussian_blur.wgsl new file mode 100644 index 0000000..fd083ff --- /dev/null +++ b/assets/final/shaders/gaussian_blur.wgsl @@ -0,0 +1,33 @@ +@group(0) @binding(0) var smplr: sampler; +@group(0) @binding(1) var txt: texture_2d; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; + +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1, -1), + vec2(3, -1), + vec2(-1, 3) + ); + return vec4(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + let uv = p.xy / uniforms.resolution; + var res = vec4(0.0); + let size = 5.0 * uniforms.intensity; + for (var x: f32 = -2.0; x <= 2.0; x += 1.0) { + for (var y: f32 = -2.0; y <= 2.0; y += 1.0) { + res += textureSample(txt, smplr, uv + vec2(x, y) * size / uniforms.resolution.x); + } + } + return res / 25.0; +} diff --git a/assets/final/shaders/lighting.wgsl b/assets/final/shaders/lighting.wgsl new file mode 100644 index 0000000..277909b --- /dev/null +++ b/assets/final/shaders/lighting.wgsl @@ -0,0 +1,23 @@ +fn get_normal_basic(p: vec3, obj_type: f32) -> vec3 { + if (obj_type == 1.0) { return normalize(p); } + let e = vec2(0.001, 0.0); + return normalize(vec3( + get_dist(p + e.xyy, obj_type) - get_dist(p - e.xyy, obj_type), + get_dist(p + e.yxy, obj_type) - get_dist(p - e.yxy, obj_type), + get_dist(p + e.yyx, obj_type) - get_dist(p - e.yyx, obj_type) + )); +} + +fn calc_shadow(ro: vec3, rd: vec3, tmin: f32, tmax: f32, skip_idx: u32) -> f32 { + var res = 1.0; + var t = tmin; + if (t < 0.05) { t = 0.05; } + for (var i = 0; i < 32; i = i + 1) { + let h = map_scene(ro + rd * t, skip_idx); + if (h < 0.001) { return 0.0; } + res = min(res, 16.0 * h / t); + t = t + clamp(h, 0.02, 0.4); + if (t > tmax) { break; } + } + return clamp(res, 0.0, 1.0); +} diff --git a/assets/final/shaders/main_shader.wgsl b/assets/final/shaders/main_shader.wgsl new file mode 100644 index 0000000..7011159 --- /dev/null +++ b/assets/final/shaders/main_shader.wgsl @@ -0,0 +1,29 @@ +struct Uniforms { + audio_peak: f32, + aspect_ratio: f32, + time: f32, +}; + +@group(0) @binding(0) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + let PI = 3.14159265; + let num_sides = 7.0; + let scale = 0.5 + 0.3 * uniforms.audio_peak; + let tri_idx = f32(i / 3u); + let sub_idx = i % 3u; + if (sub_idx == 0u) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + let angle = (tri_idx + f32(sub_idx - 1u)) * 2.0 * PI / num_sides + uniforms.time * 0.5; + return vec4(scale * cos(angle) / uniforms.aspect_ratio, scale * sin(angle), 0.0, 1.0); +} + +@fragment fn fs_main() -> @location(0) vec4 { + let h = uniforms.time * 2.0 + uniforms.audio_peak * 3.0; + let r = sin(h) * 0.5 + 0.5; + let g = sin(h + 2.0) * 0.9 + 0.3; + let b = sin(h + 4.0) * 0.5 + 0.5; + let boost = uniforms.audio_peak * 0.5; + return vec4(r + boost, g + boost, b + boost, 1.0); +} diff --git a/assets/final/shaders/particle_compute.wgsl b/assets/final/shaders/particle_compute.wgsl new file mode 100644 index 0000000..a6c96db --- /dev/null +++ b/assets/final/shaders/particle_compute.wgsl @@ -0,0 +1,35 @@ +struct Particle { + pos: vec4, + vel: vec4, + rot: vec4, + color: vec4, +}; + +struct Uniforms { + audio_peak: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var particles: array; +@group(0) @binding(1) var uniforms: Uniforms; + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) id: vec3) { + let i = id.x; + if (i >= arrayLength(&particles)) { + return; + } + var p = particles[i]; + let new_pos = p.pos.xyz + p.vel.xyz * 0.016; + p.pos = vec4(new_pos, p.pos.w); + p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_peak * 5.0); + p.rot.x = p.rot.x + p.rot.y * 0.016; + if (p.pos.y < -1.5) { + p.pos.y = 1.5; + p.pos.x = (f32(i % 100u) / 50.0) - 1.0 + (uniforms.audio_peak * 0.5); + p.vel.y = 0.0; + } + particles[i] = p; +} diff --git a/assets/final/shaders/particle_render.wgsl b/assets/final/shaders/particle_render.wgsl new file mode 100644 index 0000000..6f115ec --- /dev/null +++ b/assets/final/shaders/particle_render.wgsl @@ -0,0 +1,44 @@ +struct Particle { + pos: vec4, + vel: vec4, + rot: vec4, + color: vec4, +}; + +struct Uniforms { + audio_peak: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var particles: array; +@group(0) @binding(1) var uniforms: Uniforms; + +struct VSOut { + @builtin(position) pos: vec4, + @location(0) color: vec4, +}; + +@vertex fn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut { + let p = particles[ii]; + let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_peak * 0.02; + var offsets = array, 6>( + vec2(-1, -1), + vec2(1, -1), + vec2(-1, 1), + vec2(-1, 1), + vec2(1, -1), + vec2(1, 1) + ); + let offset = offsets[vi]; + let c = cos(p.rot.x); + let s = sin(p.rot.x); + let rotated_offset = vec2(offset.x * c - offset.y * s, offset.x * s + offset.y * c); + let pos = vec2(p.pos.x + rotated_offset.x * size / uniforms.aspect_ratio, p.pos.y + rotated_offset.y * size); + return VSOut(vec4(pos, 0.0, 1.0), p.color * (0.5 + 0.5 * uniforms.audio_peak)); +} + +@fragment fn fs_main(@location(0) color: vec4) -> @location(0) vec4 { + return color; +} diff --git a/assets/final/shaders/particle_spray_compute.wgsl b/assets/final/shaders/particle_spray_compute.wgsl new file mode 100644 index 0000000..55fa8e9 --- /dev/null +++ b/assets/final/shaders/particle_spray_compute.wgsl @@ -0,0 +1,40 @@ +struct Particle { + pos: vec4, + vel: vec4, + rot: vec4, + color: vec4, +}; + +struct Uniforms { + intensity: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var particles: array; +@group(0) @binding(1) var uniforms: Uniforms; + +fn hash(p: f32) -> f32 { + return fract(sin(p) * 43758.5453); +} + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) id: vec3) { + let i = id.x; + if (i >= arrayLength(&particles)) { + return; + } + var p = particles[i]; + if (p.pos.w <= 0.0) { + let r = hash(f32(i) + uniforms.time); + let angle = r * 6.28318; + p.pos = vec4(0.0, 0.0, 0.0, 1.0); + p.vel = vec4(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.intensity * 2.0); + p.color = vec4(hash(r + 0.1), hash(r + 0.2), 1.0, 1.0); + } + let new_pos = p.pos.xyz + p.vel.xyz * 0.016; + p.pos = vec4(new_pos, p.pos.w - 0.01 * (1.0 + uniforms.beat)); + p.vel.y = p.vel.y - 0.01; + particles[i] = p; +} diff --git a/assets/final/shaders/passthrough.wgsl b/assets/final/shaders/passthrough.wgsl new file mode 100644 index 0000000..aa4de1c --- /dev/null +++ b/assets/final/shaders/passthrough.wgsl @@ -0,0 +1,24 @@ +@group(0) @binding(0) var smplr: sampler; +@group(0) @binding(1) var txt: texture_2d; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1, -1), + vec2(3, -1), + vec2(-1, 3) + ); + return vec4(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + return textureSample(txt, smplr, p.xy / uniforms.resolution); +} diff --git a/assets/final/shaders/ray_box.wgsl b/assets/final/shaders/ray_box.wgsl new file mode 100644 index 0000000..d56ea1b --- /dev/null +++ b/assets/final/shaders/ray_box.wgsl @@ -0,0 +1,16 @@ +struct RayBounds { + t_entry: f32, + t_exit: f32, + hit: bool, +}; + +fn ray_box_intersection(ro: vec3, rd: vec3, extent: vec3) -> RayBounds { + let inv_rd = 1.0 / rd; + let t0 = (-extent - ro) * inv_rd; + let t1 = (extent - ro) * inv_rd; + let tmin_vec = min(t0, t1); + let tmax_vec = max(t0, t1); + let t_entry = max(0.0, max(tmin_vec.x, max(tmin_vec.y, tmin_vec.z))); + let t_exit = min(tmax_vec.x, min(tmax_vec.y, tmax_vec.z)); + return RayBounds(t_entry, t_exit, t_entry <= t_exit); +} diff --git a/assets/final/shaders/renderer_3d.wgsl b/assets/final/shaders/renderer_3d.wgsl new file mode 100644 index 0000000..0b2c38b --- /dev/null +++ b/assets/final/shaders/renderer_3d.wgsl @@ -0,0 +1,194 @@ +@group(0) @binding(0) var globals: GlobalUniforms; +@group(0) @binding(1) var object_data: ObjectsBuffer; +@group(0) @binding(2) var noise_tex: texture_2d; +@group(0) @binding(3) var noise_sampler: sampler; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) local_pos: vec3, + @location(1) color: vec4, + @location(2) @interpolate(flat) instance_index: u32, + @location(3) world_pos: vec3, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32, + @builtin(instance_index) instance_index: u32) -> VertexOutput { + + var pos = array, 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), + 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), + 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), + 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), + 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), + 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) + ); + + var p = pos[vertex_index]; + let obj = object_data.objects[instance_index]; + let obj_type = obj.params.x; + + // Tight fit for Torus proxy hull (major radius 1.0, minor 0.4) + if (obj_type == 3.0) { + p.x = p.x * 1.5; + p.z = p.z * 1.5; + p.y = p.y * 0.5; + } + + let world_pos = obj.model * vec4(p, 1.0); + let clip_pos = globals.view_proj * world_pos; + + var out: VertexOutput; + out.position = clip_pos; + out.local_pos = p; + out.color = obj.color; + out.instance_index = instance_index; + out.world_pos = world_pos.xyz; + return out; +} + +fn get_dist(p: vec3, obj_type: f32) -> f32 { + if (obj_type == 1.0) { return length(p) - 1.0; } // Unit Sphere + if (obj_type == 2.0) { return sdBox(p, vec3(1.0)); } // Unit Box + if (obj_type == 3.0) { return sdTorus(p, vec2(1.0, 0.4)); } // Unit Torus + if (obj_type == 4.0) { return sdPlane(p, vec3(0.0, 1.0, 0.0), 0.0); } + return 100.0; +} + +fn map_scene(p: vec3, skip_idx: u32) -> f32 { + var d = 1000.0; + let count = u32(globals.params.x); + + for (var i = 0u; i < count; i = i + 1u) { + if (i == skip_idx) { continue; } + let obj = object_data.objects[i]; + let obj_type = obj.params.x; + // Skip rasterized objects (like the floor) in the SDF map + if (obj_type <= 0.0) { continue; } + + let q = (obj.inv_model * vec4(p, 1.0)).xyz; + + let scale_x = length(obj.model[0].xyz); + let scale_y = length(obj.model[1].xyz); + let scale_z = length(obj.model[2].xyz); + // Use conservative minimum scale to avoid overstepping the distance field + let s = min(scale_x, min(scale_y, scale_z)); + + d = min(d, get_dist(q, obj_type) * s); + } + return d; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let obj = object_data.objects[in.instance_index]; + let obj_type = obj.params.x; + + var p: vec3; + var normal: vec3; + var base_color = in.color.rgb; + let light_dir = normalize(vec3(1.0, 1.0, 1.0)); + + if (obj_type <= 0.0) { // Raster path + p = in.world_pos; + let local_normal = normalize(cross(dpdx(in.local_pos), dpdy(in.local_pos))); + let normal_matrix = mat3x3(obj.inv_model[0].xyz, obj.inv_model[1].xyz, obj.inv_model[2].xyz); + normal = normalize(transpose(normal_matrix) * local_normal); + + // Apply grid pattern to floor + let uv = p.xz * 0.5; + let grid = 0.5 + 0.5 * sin(uv.x * 3.14) * sin(uv.y * 3.14); + let grid_val = smoothstep(0.45, 0.55, grid); + base_color = base_color * (0.5 + 0.5 * grid_val); + } else { // SDF path + let ro_world = globals.camera_pos_time.xyz; + let rd_world = normalize(in.world_pos - ro_world); + + // Ray-Box Intersection in local space to find tight bounds + let ro_local = (obj.inv_model * vec4(ro_world, 1.0)).xyz; + let rd_local = normalize((obj.inv_model * vec4(rd_world, 0.0)).xyz); + + // Proxy box extent (matches vs_main) + var extent = vec3(1.0); + if (obj_type == 3.0) { extent = vec3(1.5, 0.5, 1.5); } + + let bounds = ray_box_intersection(ro_local, rd_local, extent); + + if (!bounds.hit) { discard; } + + var t = bounds.t_entry; + var hit = false; + for (var i = 0; i < 64; i = i + 1) { + let q = ro_local + rd_local * t; + let d_local = get_dist(q, obj_type); + if (d_local < 0.0005) { hit = true; break; } + t = t + d_local; + if (t > bounds.t_exit) { break; } + } + if (!hit) { discard; } + + let q_hit = ro_local + rd_local * t; + p = (obj.model * vec4(q_hit, 1.0)).xyz; // Correct world position + + // Calculate normal with bump mapping + let e = vec2(0.005, 0.0); + let disp_strength = 0.05; + + let q_x1 = q_hit + e.xyy; + let uv_x1 = vec2(atan2(q_x1.x, q_x1.z) / 6.28 + 0.5, acos(clamp(q_x1.y / length(q_x1), -1.0, 1.0)) / 3.14); + let h_x1 = textureSample(noise_tex, noise_sampler, uv_x1).r; + let d_x1 = get_dist(q_x1, obj_type) - disp_strength * h_x1; + + let q_x2 = q_hit - e.xyy; + let uv_x2 = vec2(atan2(q_x2.x, q_x2.z) / 6.28 + 0.5, acos(clamp(q_x2.y / length(q_x2), -1.0, 1.0)) / 3.14); + let h_x2 = textureSample(noise_tex, noise_sampler, uv_x2).r; + let d_x2 = get_dist(q_x2, obj_type) - disp_strength * h_x2; + + let q_y1 = q_hit + e.yxy; + let uv_y1 = vec2(atan2(q_y1.x, q_y1.z) / 6.28 + 0.5, acos(clamp(q_y1.y / length(q_y1), -1.0, 1.0)) / 3.14); + let h_y1 = textureSample(noise_tex, noise_sampler, uv_y1).r; + let d_y1 = get_dist(q_y1, obj_type) - disp_strength * h_y1; + + let q_y2 = q_hit - e.yxy; + let uv_y2 = vec2(atan2(q_y2.x, q_y2.z) / 6.28 + 0.5, acos(clamp(q_y2.y / length(q_y2), -1.0, 1.0)) / 3.14); + let h_y2 = textureSample(noise_tex, noise_sampler, uv_y2).r; + let d_y2 = get_dist(q_y2, obj_type) - disp_strength * h_y2; + + let q_z1 = q_hit + e.yyx; + let uv_z1 = vec2(atan2(q_z1.x, q_z1.z) / 6.28 + 0.5, acos(clamp(q_z1.y / length(q_z1), -1.0, 1.0)) / 3.14); + let h_z1 = textureSample(noise_tex, noise_sampler, uv_z1).r; + let d_z1 = get_dist(q_z1, obj_type) - disp_strength * h_z1; + + let q_z2 = q_hit - e.yyx; + let uv_z2 = vec2(atan2(q_z2.x, q_z2.z) / 6.28 + 0.5, acos(clamp(q_z2.y / length(q_z2), -1.0, 1.0)) / 3.14); + let h_z2 = textureSample(noise_tex, noise_sampler, uv_z2).r; + let d_z2 = get_dist(q_z2, obj_type) - disp_strength * h_z2; + + let n_local = normalize(vec3(d_x1 - d_x2, d_y1 - d_y2, d_z1 - d_z2)); + let normal_matrix = mat3x3(obj.inv_model[0].xyz, obj.inv_model[1].xyz, obj.inv_model[2].xyz); + normal = normalize(transpose(normal_matrix) * n_local); + + // Apply texture to SDF color + if (in.instance_index == 0u || obj_type == 4.0) { // Floor (index 0) or PLANE + let uv_grid = p.xz * 0.5; + let grid = 0.5 + 0.5 * sin(uv_grid.x * 3.14) * sin(uv_grid.y * 3.14); + let grid_val = smoothstep(0.45, 0.55, grid); + base_color = base_color * (0.5 + 0.5 * grid_val); + } else { + let uv_hit = vec2(atan2(q_hit.x, q_hit.z) / 6.28 + 0.5, acos(clamp(q_hit.y / length(q_hit), -1.0, 1.0)) / 3.14); + let tex_val = textureSample(noise_tex, noise_sampler, uv_hit).r; + base_color = base_color * (0.7 + 0.3 * tex_val); + } + } + + let shadow = calc_shadow(p, light_dir, 0.05, 20.0, in.instance_index); + let diffuse = max(dot(normal, light_dir), 0.0); + let lighting = diffuse * (0.1 + 0.9 * shadow) + 0.1; // Ambient + Shadowed Diffuse + return vec4(base_color * lighting, 1.0); +} \ No newline at end of file diff --git a/assets/final/shaders/sdf_primitives.wgsl b/assets/final/shaders/sdf_primitives.wgsl new file mode 100644 index 0000000..31bbe2d --- /dev/null +++ b/assets/final/shaders/sdf_primitives.wgsl @@ -0,0 +1,14 @@ +fn sdSphere(p: vec3, r: f32) -> f32 { + return length(p) - r; +} +fn sdBox(p: vec3, b: vec3) -> f32 { + let q = abs(p) - b; + return length(max(q, vec3(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0); +} +fn sdTorus(p: vec3, t: vec2) -> f32 { + let q = vec2(length(p.xz) - t.x, p.y); + return length(q) - t.y; +} +fn sdPlane(p: vec3, n: vec3, h: f32) -> f32 { + return dot(p, n) + h; +} diff --git a/assets/final/shaders/solarize.wgsl b/assets/final/shaders/solarize.wgsl new file mode 100644 index 0000000..9bd0212 --- /dev/null +++ b/assets/final/shaders/solarize.wgsl @@ -0,0 +1,37 @@ +@group(0) @binding(0) var smplr: sampler; +@group(0) @binding(1) var txt: texture_2d; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2, +}; + +@group(0) @binding(2) var uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(-1, -1), + vec2(3, -1), + vec2(-1, 3) + ); + return vec4(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { + let uv = p.xy / uniforms.resolution; + var col = textureSample(txt, smplr, uv); + let thr = 0.5 + 0.3 * sin(uniforms.time); + if (col.r < thr) { + col.r = 1.0 - col.r; + } + if (col.g < thr) { + col.g = 1.0 - col.g; + } + if (col.b < thr) { + col.b = 1.0 - col.b; + } + return col; +} diff --git a/assets/final/shaders/visual_debug.wgsl b/assets/final/shaders/visual_debug.wgsl new file mode 100644 index 0000000..e91c1a9 --- /dev/null +++ b/assets/final/shaders/visual_debug.wgsl @@ -0,0 +1,27 @@ +struct Uniforms { + viewProj : mat4x4, +} +@group(0) @binding(0) var uniforms : Uniforms; + +struct VertexInput { + @location(0) position : vec3, + @location(1) color : vec3, +} + +struct VertexOutput { + @builtin(position) position : vec4, + @location(0) color : vec3, +} + +@vertex +fn vs_main(in : VertexInput) -> VertexOutput { + var out : VertexOutput; + out.position = uniforms.viewProj * vec4(in.position, 1.0); + out.color = in.color; + return out; +} + +@fragment +fn fs_main(in : VertexOutput) -> @location(0) vec4 { + return vec4(in.color, 1.0); +} diff --git a/assets/final/test_assets_list.txt b/assets/final/test_assets_list.txt index f0c2275..727d4bc 100644 --- a/assets/final/test_assets_list.txt +++ b/assets/final/test_assets_list.txt @@ -1,3 +1,9 @@ # Asset Name, Compression Type, Filename/Placeholder, Description TEST_ASSET, NONE, test_asset.txt, "A static test asset" PROC_NOISE_256, PROC(gen_noise,256,256), _, "A 256x256 procedural noise texture" +SHADER_RENDERER_3D, NONE, shaders/renderer_3d.wgsl, "Hybrid 3D Renderer Shader" +SHADER_COMMON_UNIFORMS, NONE, shaders/common_uniforms.wgsl, "Common Uniforms Snippet" +SHADER_SDF_PRIMITIVES, NONE, shaders/sdf_primitives.wgsl, "SDF Primitives Snippet" +SHADER_LIGHTING, NONE, shaders/lighting.wgsl, "Lighting Snippet" +SHADER_RAY_BOX, NONE, shaders/ray_box.wgsl, "Ray-Box Intersection Snippet" +SHADER_VISUAL_DEBUG, NONE, shaders/visual_debug.wgsl, "Visual Debug Shader" -- cgit v1.2.3