diff options
21 files changed, 714 insertions, 754 deletions
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<f32>; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2<f32>, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), + vec2<f32>(3, -1), + vec2<f32>(-1, 3) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / uniforms.resolution; + let off = 0.02 * uniforms.intensity; + let r = textureSample(txt, smplr, uv + vec2<f32>(off, 0.0)).r; + let g = textureSample(txt, smplr, uv).g; + let b = textureSample(txt, smplr, uv - vec2<f32>(off, 0.0)).b; + return vec4<f32>(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<f32>, + camera_pos_time: vec4<f32>, + params: vec4<f32>, +}; +struct ObjectData { + model: mat4x4<f32>, + inv_model: mat4x4<f32>, + color: vec4<f32>, + params: vec4<f32>, +}; +struct ObjectsBuffer { + objects: array<ObjectData>, +}; 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<f32>; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2<f32>, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), + vec2<f32>(3, -1), + vec2<f32>(-1, 3) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + 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<f32>(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<f32>, +}; + +@group(0) @binding(0) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1.0, -1.0), + vec2<f32>(3.0, -1.0), + vec2<f32>(-1.0, 3.0) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +fn sdEllipse(p: vec2<f32>, ab: vec2<f32>) -> f32 { + var p_abs = abs(p); + if (p_abs.x > p_abs.y) { + p_abs = vec2<f32>(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<f32>(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<f32>) -> @location(0) vec4<f32> { + let uv = (p.xy / uniforms.resolution - 0.5) * 2.0; + let movement = vec2<f32>(sin(uniforms.time * 0.7), cos(uniforms.time * 0.5)); + let d = sdEllipse((uv * vec2<f32>(uniforms.aspect_ratio, 1.0)) - movement, vec2<f32>(0.5, 0.3) * (1.0 + uniforms.beat * 0.2)); + return mix(vec4<f32>(0.2, 0.8, 0.4, 1.0), vec4<f32>(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<f32>; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2<f32>, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), + vec2<f32>(3, -1), + vec2<f32>(-1, 3) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let uv = p.xy / uniforms.resolution; + var res = vec4<f32>(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<f32>(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<f32>, obj_type: f32) -> vec3<f32> { + if (obj_type == 1.0) { return normalize(p); } + let e = vec2<f32>(0.001, 0.0); + return normalize(vec3<f32>( + 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<f32>, rd: vec3<f32>, 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<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + 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<f32>(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<f32>(scale * cos(angle) / uniforms.aspect_ratio, scale * sin(angle), 0.0, 1.0); +} + +@fragment fn fs_main() -> @location(0) vec4<f32> { + 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<f32>(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<f32>, + vel: vec4<f32>, + rot: vec4<f32>, + color: vec4<f32>, +}; + +struct Uniforms { + audio_peak: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var<storage, read_write> particles: array<Particle>; +@group(0) @binding(1) var<uniform> uniforms: Uniforms; + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) id: vec3<u32>) { + 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<f32>(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<f32>, + vel: vec4<f32>, + rot: vec4<f32>, + color: vec4<f32>, +}; + +struct Uniforms { + audio_peak: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var<storage, read> particles: array<Particle>; +@group(0) @binding(1) var<uniform> uniforms: Uniforms; + +struct VSOut { + @builtin(position) pos: vec4<f32>, + @location(0) color: vec4<f32>, +}; + +@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<vec2<f32>, 6>( + vec2<f32>(-1, -1), + vec2<f32>(1, -1), + vec2<f32>(-1, 1), + vec2<f32>(-1, 1), + vec2<f32>(1, -1), + vec2<f32>(1, 1) + ); + let offset = offsets[vi]; + let c = cos(p.rot.x); + let s = sin(p.rot.x); + let rotated_offset = vec2<f32>(offset.x * c - offset.y * s, offset.x * s + offset.y * c); + let pos = vec2<f32>(p.pos.x + rotated_offset.x * size / uniforms.aspect_ratio, p.pos.y + rotated_offset.y * size); + return VSOut(vec4<f32>(pos, 0.0, 1.0), p.color * (0.5 + 0.5 * uniforms.audio_peak)); +} + +@fragment fn fs_main(@location(0) color: vec4<f32>) -> @location(0) vec4<f32> { + 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<f32>, + vel: vec4<f32>, + rot: vec4<f32>, + color: vec4<f32>, +}; + +struct Uniforms { + intensity: f32, + aspect_ratio: f32, + time: f32, + beat: f32, +}; + +@group(0) @binding(0) var<storage, read_write> particles: array<Particle>; +@group(0) @binding(1) var<uniform> 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<u32>) { + 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<f32>(0.0, 0.0, 0.0, 1.0); + p.vel = vec4<f32>(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.intensity * 2.0); + p.color = vec4<f32>(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<f32>(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<f32>; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2<f32>, +}; +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), + vec2<f32>(3, -1), + vec2<f32>(-1, 3) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + 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<f32>, rd: vec3<f32>, extent: vec3<f32>) -> 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<uniform> globals: GlobalUniforms; +@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer; +@group(0) @binding(2) var noise_tex: texture_2d<f32>; +@group(0) @binding(3) var noise_sampler: sampler; + +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 { + + 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), + 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<f32>(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<f32>, 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<f32>(1.0)); } // Unit Box + if (obj_type == 3.0) { return sdTorus(p, vec2<f32>(1.0, 0.4)); } // Unit Torus + if (obj_type == 4.0) { return sdPlane(p, vec3<f32>(0.0, 1.0, 0.0), 0.0); } + return 100.0; +} + +fn map_scene(p: vec3<f32>, 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<f32>(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<f32> { + let obj = object_data.objects[in.instance_index]; + let obj_type = obj.params.x; + + var p: vec3<f32>; + var normal: vec3<f32>; + var base_color = in.color.rgb; + let light_dir = normalize(vec3<f32>(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<f32>(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<f32>(ro_world, 1.0)).xyz; + let rd_local = normalize((obj.inv_model * vec4<f32>(rd_world, 0.0)).xyz); + + // Proxy box extent (matches vs_main) + var extent = vec3<f32>(1.0); + if (obj_type == 3.0) { extent = vec3<f32>(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<f32>(q_hit, 1.0)).xyz; // Correct world position + + // Calculate normal with bump mapping + let e = vec2<f32>(0.005, 0.0); + let disp_strength = 0.05; + + let q_x1 = q_hit + e.xyy; + let uv_x1 = vec2<f32>(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<f32>(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<f32>(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<f32>(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<f32>(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<f32>(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<f32>(d_x1 - d_x2, d_y1 - d_y2, d_z1 - d_z2)); + let normal_matrix = mat3x3<f32>(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<f32>(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<f32>(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<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; +} +fn sdPlane(p: vec3<f32>, n: vec3<f32>, 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<f32>; + +struct Uniforms { + time: f32, + beat: f32, + intensity: f32, + aspect_ratio: f32, + resolution: vec2<f32>, +}; + +@group(0) @binding(2) var<uniform> uniforms: Uniforms; + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1, -1), + vec2<f32>(3, -1), + vec2<f32>(-1, 3) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + 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<f32>, +} +@group(0) @binding(0) var<uniform> uniforms : Uniforms; + +struct VertexInput { + @location(0) position : vec3<f32>, + @location(1) color : vec3<f32>, +} + +struct VertexOutput { + @builtin(position) position : vec4<f32>, + @location(0) color : vec3<f32>, +} + +@vertex +fn vs_main(in : VertexInput) -> VertexOutput { + var out : VertexOutput; + out.position = uniforms.viewProj * vec4<f32>(in.position, 1.0); + out.color = in.color; + return out; +} + +@fragment +fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> { + return vec4<f32>(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" diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc index abd98b6..cbc3cfa 100644 --- a/src/3d/renderer.cc +++ b/src/3d/renderer.cc @@ -2,6 +2,9 @@ // It implements the Renderer3D class. #include "3d/renderer.h" +#include "generated/assets.h" +#include "gpu/effects/shader_composer.h" +#include "util/asset_manager.h" #include <algorithm> #include <cassert> #include <cstring> @@ -11,269 +14,6 @@ bool Renderer3D::s_debug_enabled_ = false; #endif -static const char* kShaderCode = R"( -struct GlobalUniforms { - view_proj: mat4x4<f32>, - camera_pos_time: vec4<f32>, - params: vec4<f32>, -}; - -struct ObjectData { - model: mat4x4<f32>, - inv_model: mat4x4<f32>, - color: vec4<f32>, - params: vec4<f32>, -}; - -struct ObjectsBuffer { - objects: array<ObjectData>, -}; - -@group(0) @binding(0) var<uniform> globals: GlobalUniforms; -@group(0) @binding(1) var<storage, read> object_data: ObjectsBuffer; -@group(0) @binding(2) var noise_tex: texture_2d<f32>; -@group(0) @binding(3) var noise_sampler: sampler; - -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 { - - 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), - 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<f32>(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 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; -} - -fn sdPlane(p: vec3<f32>, n: vec3<f32>, h: f32) -> f32 { - return dot(p, n) + h; -} - -fn get_dist(p: vec3<f32>, 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<f32>(1.0)); } // Unit Box - if (obj_type == 3.0) { return sdTorus(p, vec2<f32>(1.0, 0.4)); } // Unit Torus - if (obj_type == 4.0) { return sdPlane(p, vec3<f32>(0.0, 1.0, 0.0), 0.0); } - return 100.0; -} - -fn map_scene(p: vec3<f32>, 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<f32>(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; -} - -fn calc_shadow(ro: vec3<f32>, rd: vec3<f32>, 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); // Standard k=16 - t = t + clamp(h, 0.02, 0.4); - if (t > tmax) { break; } - } - return clamp(res, 0.0, 1.0); -} - -fn get_normal(p: vec3<f32>, obj_type: f32) -> vec3<f32> { - if (obj_type == 1.0) { return normalize(p); } - let e = vec2<f32>(0.001, 0.0); - return normalize(vec3<f32>( - 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) - )); -} - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { - let obj = object_data.objects[in.instance_index]; - let obj_type = obj.params.x; - - var p: vec3<f32>; - var normal: vec3<f32>; - var base_color = in.color.rgb; - let light_dir = normalize(vec3<f32>(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<f32>(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<f32>(ro_world, 1.0)).xyz; - let rd_local = normalize((obj.inv_model * vec4<f32>(rd_world, 0.0)).xyz); - - // Proxy box extent (matches vs_main) - var extent = vec3<f32>(1.0); - if (obj_type == 3.0) { extent = vec3<f32>(1.5, 0.5, 1.5); } - - let inv_rd = 1.0 / rd_local; - let t0 = (-extent - ro_local) * inv_rd; - let t1 = (extent - ro_local) * 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)); - - if (t_entry > t_exit) { discard; } - - var t = 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 > t_exit) { break; } - } - if (!hit) { discard; } - - let q_hit = ro_local + rd_local * t; - p = (obj.model * vec4<f32>(q_hit, 1.0)).xyz; // Correct world position - - // Calculate normal with bump mapping - let e = vec2<f32>(0.005, 0.0); - let disp_strength = 0.05; - - let q_x1 = q_hit + e.xyy; - let uv_x1 = vec2<f32>(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<f32>(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<f32>(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<f32>(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<f32>(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<f32>(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<f32>(d_x1 - d_x2, d_y1 - d_y2, d_z1 - d_z2)); - let normal_matrix = mat3x3<f32>(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<f32>(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<f32>(base_color * lighting, 1.0); -} - -)"; - void Renderer3D::init(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format) { device_ = device; @@ -398,16 +138,21 @@ void Renderer3D::create_pipeline() { WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); + std::string main_code = + (const char*)GetAsset(AssetId::ASSET_SHADER_RENDERER_3D); + std::string shader_source = ShaderComposer::Get().Compose( + {"common_uniforms", "sdf_primitives", "lighting", "ray_box"}, main_code); + #if defined(DEMO_CROSS_COMPILE_WIN32) WGPUShaderModuleWGSLDescriptor wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; - wgsl_desc.code = kShaderCode; + wgsl_desc.code = shader_source.c_str(); WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; #else WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_desc.code = {kShaderCode, strlen(kShaderCode)}; + wgsl_desc.code = {shader_source.c_str(), shader_source.length()}; WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; #endif @@ -621,4 +366,4 @@ void Renderer3D::render(const Scene& scene, const Camera& camera, float time, wgpuCommandBufferRelease(commands); wgpuCommandEncoderRelease(encoder); -}
\ No newline at end of file +} diff --git a/src/3d/visual_debug.cc b/src/3d/visual_debug.cc index 51ccc39..f8d9bed 100644 --- a/src/3d/visual_debug.cc +++ b/src/3d/visual_debug.cc @@ -5,40 +5,11 @@ #if !defined(STRIP_ALL) +#include "generated/assets.h" +#include "util/asset_manager.h" #include <cstdio> #include <cstring> -// Simple shader for drawing colored lines -static const char* kDebugShaderCode = R"( -struct Uniforms { - viewProj : mat4x4<f32>, -} -@group(0) @binding(0) var<uniform> uniforms : Uniforms; - -struct VertexInput { - @location(0) position : vec3<f32>, - @location(1) color : vec3<f32>, -} - -struct VertexOutput { - @builtin(position) position : vec4<f32>, - @location(0) color : vec3<f32>, -} - -@vertex -fn vs_main(in : VertexInput) -> VertexOutput { - var out : VertexOutput; - out.position = uniforms.viewProj * vec4<f32>(in.position, 1.0); - out.color = in.color; - return out; -} - -@fragment -fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> { - return vec4<f32>(in.color, 1.0); -} -)"; - void VisualDebug::init(WGPUDevice device, WGPUTextureFormat format) { device_ = device; create_pipeline(format); @@ -92,16 +63,20 @@ void VisualDebug::create_pipeline(WGPUTextureFormat format) { wgpuDeviceCreatePipelineLayout(device_, &pl_desc); // Shader + size_t shader_len = 0; + const char* shader_code = + (const char*)GetAsset(AssetId::ASSET_SHADER_VISUAL_DEBUG, &shader_len); + #if defined(DEMO_CROSS_COMPILE_WIN32) WGPUShaderModuleWGSLDescriptor wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor; - wgsl_desc.code = kDebugShaderCode; + wgsl_desc.code = shader_code; WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; #else WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_desc.code = {kDebugShaderCode, strlen(kDebugShaderCode)}; + wgsl_desc.code = {shader_code, shader_len}; WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain; #endif diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc index 579160c..6b37869 100644 --- a/src/gpu/effects/shaders.cc +++ b/src/gpu/effects/shaders.cc @@ -2,465 +2,39 @@ // It defines WGSL shader code for various effects. #include "../demo_effects.h" - +#include "generated/assets.h" #include "gpu/effects/shader_composer.h" +#include "util/asset_manager.h" void InitShaderComposer() { auto& sc = ShaderComposer::Get(); - sc.RegisterSnippet("common_uniforms", R"( -struct GlobalUniforms { - view_proj: mat4x4<f32>, - camera_pos_time: vec4<f32>, - params: vec4<f32>, -}; -struct ObjectData { - model: mat4x4<f32>, - inv_model: mat4x4<f32>, - color: vec4<f32>, - params: vec4<f32>, -}; -struct ObjectsBuffer { - objects: array<ObjectData>, -}; -)"); - - sc.RegisterSnippet("sdf_primitives", R"( -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; -} -fn sdPlane(p: vec3<f32>, n: vec3<f32>, h: f32) -> f32 { - return dot(p, n) + h; -} -)"); - - sc.RegisterSnippet("lighting", R"( -fn get_normal_basic(p: vec3<f32>, obj_type: f32) -> vec3<f32> { - if (obj_type == 1.0) { return normalize(p); } - let e = vec2<f32>(0.001, 0.0); - return normalize(vec3<f32>( - 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<f32>, rd: vec3<f32>, 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); -} -)"); - - sc.RegisterSnippet("ray_box", R"( -struct RayBounds { - t_entry: f32, - t_exit: f32, - hit: bool, -}; - -fn ray_box_intersection(ro: vec3<f32>, rd: vec3<f32>, extent: vec3<f32>) -> 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); -} -)"); -} - -const char* main_shader_wgsl = R"( -struct Uniforms { - audio_peak: f32, - aspect_ratio: f32, - time: f32, -}; - -@group(0) @binding(0) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - 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<f32>(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<f32>(scale * cos(angle) / uniforms.aspect_ratio, scale * sin(angle), 0.0, 1.0); -} - -@fragment fn fs_main() -> @location(0) vec4<f32> { - 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<f32>(r + boost, g + boost, b + boost, 1.0); -})"; - -const char* particle_compute_wgsl = R"( -struct Particle { - pos: vec4<f32>, - vel: vec4<f32>, - rot: vec4<f32>, - color: vec4<f32>, -}; - -struct Uniforms { - audio_peak: f32, - aspect_ratio: f32, - time: f32, - beat: f32, -}; - -@group(0) @binding(0) var<storage, read_write> particles: array<Particle>; -@group(0) @binding(1) var<uniform> uniforms: Uniforms; - -@compute @workgroup_size(64) -fn main(@builtin(global_invocation_id) id: vec3<u32>) { - 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<f32>(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; -})"; - -const char* particle_render_wgsl = R"( -struct Particle { - pos: vec4<f32>, - vel: vec4<f32>, - rot: vec4<f32>, - color: vec4<f32>, -}; - -struct Uniforms { - audio_peak: f32, - aspect_ratio: f32, - time: f32, - beat: f32, -}; - -@group(0) @binding(0) var<storage, read> particles: array<Particle>; -@group(0) @binding(1) var<uniform> uniforms: Uniforms; - -struct VSOut { - @builtin(position) pos: vec4<f32>, - @location(0) color: vec4<f32>, -}; - -@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<vec2<f32>, 6>( - vec2<f32>(-1, -1), - vec2<f32>(1, -1), - vec2<f32>(-1, 1), - vec2<f32>(-1, 1), - vec2<f32>(1, -1), - vec2<f32>(1, 1) - ); - let offset = offsets[vi]; - let c = cos(p.rot.x); - let s = sin(p.rot.x); - let rotated_offset = vec2<f32>(offset.x * c - offset.y * s, offset.x * s + offset.y * c); - let pos = vec2<f32>(p.pos.x + rotated_offset.x * size / uniforms.aspect_ratio, p.pos.y + rotated_offset.y * size); - return VSOut(vec4<f32>(pos, 0.0, 1.0), p.color * (0.5 + 0.5 * uniforms.audio_peak)); -} - -@fragment fn fs_main(@location(0) color: vec4<f32>) -> @location(0) vec4<f32> { - return color; -} -)"; - -const char* passthrough_shader_wgsl = R"( -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d<f32>; - -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; -@group(0) @binding(2) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1, -1), - vec2<f32>(3, -1), - vec2<f32>(-1, 3) - ); - return vec4<f32>(pos[i], 0.0, 1.0); -} - -@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { - return textureSample(txt, smplr, p.xy / uniforms.resolution); -})"; - -const char* ellipse_shader_wgsl = R"( -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; - -@group(0) @binding(0) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1.0, -1.0), - vec2<f32>(3.0, -1.0), - vec2<f32>(-1.0, 3.0) - ); - return vec4<f32>(pos[i], 0.0, 1.0); -} - -fn sdEllipse(p: vec2<f32>, ab: vec2<f32>) -> f32 { - var p_abs = abs(p); - if (p_abs.x > p_abs.y) { - p_abs = vec2<f32>(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<f32>(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<f32>) -> @location(0) vec4<f32> { - let uv = (p.xy / uniforms.resolution - 0.5) * 2.0; - let movement = vec2<f32>(sin(uniforms.time * 0.7), cos(uniforms.time * 0.5)); - let d = sdEllipse((uv * vec2<f32>(uniforms.aspect_ratio, 1.0)) - movement, vec2<f32>(0.5, 0.3) * (1.0 + uniforms.beat * 0.2)); - return mix(vec4<f32>(0.2, 0.8, 0.4, 1.0), vec4<f32>(0.0), smoothstep(0.0, 0.01, d)); -})"; - -const char* particle_spray_compute_wgsl = R"( -struct Particle { - pos: vec4<f32>, - vel: vec4<f32>, - rot: vec4<f32>, - color: vec4<f32>, -}; - -struct Uniforms { - intensity: f32, - aspect_ratio: f32, - time: f32, - beat: f32, -}; - -@group(0) @binding(0) var<storage, read_write> particles: array<Particle>; -@group(0) @binding(1) var<uniform> 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<u32>) { - 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<f32>(0.0, 0.0, 0.0, 1.0); - p.vel = vec4<f32>(cos(angle), sin(angle), 0.0, 0.0) * (0.5 + hash(r) * 0.5) * (1.0 + uniforms.intensity * 2.0); - p.color = vec4<f32>(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<f32>(new_pos, p.pos.w - 0.01 * (1.0 + uniforms.beat)); - p.vel.y = p.vel.y - 0.01; - particles[i] = p; -})"; - -const char* gaussian_blur_shader_wgsl = R"( -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d<f32>; - -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; - -@group(0) @binding(2) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1, -1), - vec2<f32>(3, -1), - vec2<f32>(-1, 3) - ); - return vec4<f32>(pos[i], 0.0, 1.0); -} - -@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { - let uv = p.xy / uniforms.resolution; - var res = vec4<f32>(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<f32>(x, y) * size / uniforms.resolution.x); - } - } - return res / 25.0; -})"; - -const char* solarize_shader_wgsl = R"( -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d<f32>; - -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; - -@group(0) @binding(2) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1, -1), - vec2<f32>(3, -1), - vec2<f32>(-1, 3) - ); - return vec4<f32>(pos[i], 0.0, 1.0); -} - -@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { - 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; -})"; - -const char* distort_shader_wgsl = R"( -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d<f32>; - -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; - -@group(0) @binding(2) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1, -1), - vec2<f32>(3, -1), - vec2<f32>(-1, 3) - ); - return vec4<f32>(pos[i], 0.0, 1.0); -} - -@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { - 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<f32>(dist, 0.0)); -})"; - -const char* chroma_aberration_shader_wgsl = R"( -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d<f32>; - -struct Uniforms { - time: f32, - beat: f32, - intensity: f32, - aspect_ratio: f32, - resolution: vec2<f32>, -}; - -@group(0) @binding(2) var<uniform> uniforms: Uniforms; - -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { - var pos = array<vec2<f32>, 3>( - vec2<f32>(-1, -1), - vec2<f32>(3, -1), - vec2<f32>(-1, 3) - ); - return vec4<f32>(pos[i], 0.0, 1.0); + sc.RegisterSnippet("common_uniforms", + (const char*)GetAsset(AssetId::ASSET_SHADER_COMMON_UNIFORMS)); + sc.RegisterSnippet("sdf_primitives", + (const char*)GetAsset(AssetId::ASSET_SHADER_SDF_PRIMITIVES)); + sc.RegisterSnippet("lighting", + (const char*)GetAsset(AssetId::ASSET_SHADER_LIGHTING)); + sc.RegisterSnippet("ray_box", + (const char*)GetAsset(AssetId::ASSET_SHADER_RAY_BOX)); } -@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { - let uv = p.xy / uniforms.resolution; - let off = 0.02 * uniforms.intensity; - let r = textureSample(txt, smplr, uv + vec2<f32>(off, 0.0)).r; - let g = textureSample(txt, smplr, uv).g; - let b = textureSample(txt, smplr, uv - vec2<f32>(off, 0.0)).b; - return vec4<f32>(r, g, b, 1.0); -})"; +const char* main_shader_wgsl = (const char*)GetAsset(AssetId::ASSET_SHADER_MAIN); +const char* particle_compute_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_PARTICLE_COMPUTE); +const char* particle_render_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_PARTICLE_RENDER); +const char* passthrough_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_PASSTHROUGH); +const char* ellipse_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_ELLIPSE); +const char* particle_spray_compute_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_PARTICLE_SPRAY_COMPUTE); +const char* gaussian_blur_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_GAUSSIAN_BLUR); +const char* solarize_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_SOLARIZE); +const char* distort_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_DISTORT); +const char* chroma_aberration_shader_wgsl = + (const char*)GetAsset(AssetId::ASSET_SHADER_CHROMA_ABERRATION);
\ No newline at end of file |
