diff --git a/res/shaders/constants.wgsl b/res/shaders/constants.wgsl index c84912f..c77bb34 100644 --- a/res/shaders/constants.wgsl +++ b/res/shaders/constants.wgsl @@ -5,3 +5,7 @@ const INV_SQRT_3 = 0.57735026918962576451; // 1 / sqrt(3) // total = (2n + 1)^2 const SHADOW_SAMPLES = 2; const INV_SHADOW_SAMPLES = 1.0 / 25.0; +const FOG_MAX_STEPS = 20; +const FOG_MAX_DIST = 300.0; +const FOG_SCALE = 0.01; +const FOG_DENSITY = 1.0; diff --git a/res/shaders/fog.wgsl b/res/shaders/fog.wgsl index 303edd1..83379de 100644 --- a/res/shaders/fog.wgsl +++ b/res/shaders/fog.wgsl @@ -1,46 +1,72 @@ #include globals.wgsl +#include constants.wgsl +#include noise.wgsl + +struct FogVertexOutput { + @builtin(position) clip_position: vec4, + @location(0) world_position: vec3, + @location(1) light_world_position: vec3, +} @vertex fn vs_main( model: VertexInput, instance: InstanceInput, -) -> VertexOutput { +) -> FogVertexOutput { let model_matrix = mat4x4( instance.model_matrix_0, instance.model_matrix_1, instance.model_matrix_2, instance.model_matrix_3, ); - let normal_matrix = mat3x3( - instance.normal_matrix_0, - instance.normal_matrix_1, - instance.normal_matrix_2, - ); - - let world_normal = normalize(normal_matrix * model.normal); - let world_tangent = normalize(normal_matrix * model.tangent); - let world_bitangent = normalize(normal_matrix * model.bitangent); - let tangent_matrix = transpose(mat3x3( - world_tangent, - world_bitangent, - world_normal, - )); let world_position = model_matrix * vec4(model.position, 1.0); - var out: VertexOutput; + var out: FogVertexOutput; out.clip_position = camera.proj * camera.view * world_position; - out.tex_coords = model.tex_coords; - out.tangent_position = tangent_matrix * world_position.xyz; - out.tangent_light_position = tangent_matrix * light.position; - out.tangent_view_position = tangent_matrix * camera.position.xyz; - - out.world_position = world_position; + out.world_position = world_position.xyz / world_position.w; + out.light_world_position = light.position; return out; } -@fragment -fn fs_main(vert: VertexOutput) -> @location(0) vec4 { - return vec4(0.5, 0.5, 0.5, 0.1); +fn fog_noise(pos: vec3) -> f32 { + var p = pos * FOG_SCALE; + p.x += global_uniforms.time * 0.01; + p.y += global_uniforms.time * 0.1; + p.z += sin(global_uniforms.time * 0.1) * 0.1; + return fbm(p); +} + +fn ray_march(origin: vec3, direction: vec3, scene_depth: f32) -> f32 { + var density = 0.0; + var depth = 0.0; + for (var i = 0; i < FOG_MAX_STEPS; i++) + { + depth += FOG_MAX_DIST / f32(FOG_MAX_STEPS); + let p = origin + direction * depth; + density += fog_noise(p) * FOG_DENSITY / f32(FOG_MAX_STEPS); + if (density >= 1.0) + { + density = 1.0; + break; + } + if (depth >= scene_depth) + { + break; + } + } + return density; +} + +@fragment +fn fs_main(vert: FogVertexOutput) -> @location(0) vec4 { + var color = vec4(0.5, 0.5, 0.5, 1.0); + + let direction = normalize(vert.world_position.xyz - camera.position.xyz); + let scene_depth = FOG_MAX_DIST; // TODO: sample geometry pass depth buffer + let density = ray_march(vert.world_position.xyz, direction, scene_depth); + color.a *= density; + + return color; } diff --git a/res/shaders/globals.wgsl b/res/shaders/globals.wgsl index ed1bd96..af19006 100644 --- a/res/shaders/globals.wgsl +++ b/res/shaders/globals.wgsl @@ -3,6 +3,7 @@ struct CameraUniform { view: mat4x4, proj: mat4x4, + inv_view_proj: mat4x4, position: vec4, } @group(0) @binding(0) @@ -16,12 +17,11 @@ struct Light { @group(1) @binding(0) var light: Light; -// Needs to be 16 bytes in WebGL struct GlobalUniforms { + time: f32, light_matrix_index: u32, use_shadowmaps: u32, - _padding1: u32, - _padding2: u32, + _padding: u32, } @group(1) @binding(1) var global_uniforms: GlobalUniforms; diff --git a/res/shaders/noise.wgsl b/res/shaders/noise.wgsl new file mode 100644 index 0000000..00e9fc1 --- /dev/null +++ b/res/shaders/noise.wgsl @@ -0,0 +1,40 @@ +// Noise functions from 42yeah: +// https://blog.42yeah.is/rendering/2023/02/11/clouds.html +fn rand(p: vec3) -> f32 { + return fract(sin(dot(p, vec3(12.345, 67.89, 412.12))) * 42123.45) * 2.0 - 1.0; +} + +fn value_noise(p: vec3) -> f32 { + let u = floor(p); + let v = fract(p); + let s = smoothstep(vec3(0.0), vec3(1.0), v); + + let a = rand(u); + let b = rand(u + vec3(1.0, 0.0, 0.0)); + let c = rand(u + vec3(0.0, 1.0, 0.0)); + let d = rand(u + vec3(1.0, 1.0, 0.0)); + let e = rand(u + vec3(0.0, 0.0, 1.0)); + let f = rand(u + vec3(1.0, 0.0, 1.0)); + let g = rand(u + vec3(0.0, 1.0, 1.0)); + let h = rand(u + vec3(1.0, 1.0, 1.0)); + + return mix(mix(mix(a, b, s.x), mix(c, d, s.x), s.y), + mix(mix(e, f, s.x), mix(g, h, s.x), s.y), + s.z); +} + +fn fbm(p: vec3) -> f32 { + let num_octaves = 8; + var weight = 0.5; + var q = p; + var ret = 0.0; + + for (var i = 0; i < num_octaves; i++) + { + ret += weight * value_noise(q); + q *= 2.0; + weight *= 0.5; + } + + return saturate(ret); +} \ No newline at end of file diff --git a/src/core/camera.rs b/src/core/camera.rs index f912826..399c189 100644 --- a/src/core/camera.rs +++ b/src/core/camera.rs @@ -1,6 +1,7 @@ use std::time::Duration; use cgmath::num_traits::clamp; +use cgmath::SquareMatrix; use winit::{dpi::PhysicalPosition, event::*}; use winit::keyboard::{PhysicalKey, KeyCode}; @@ -105,22 +106,27 @@ impl Camera { pub struct CameraUniform { pub view: [[f32; 4]; 4], pub proj: [[f32; 4]; 4], + pub inv_view_proj: [[f32; 4]; 4], pub position: [f32; 4], } impl CameraUniform { pub fn new() -> Self { - use cgmath::SquareMatrix; Self { view: cgmath::Matrix4::identity().into(), proj: cgmath::Matrix4::identity().into(), + inv_view_proj: cgmath::Matrix4::identity().into(), position: [0.0; 4], } } pub fn update(&mut self, camera: &Camera) { - self.view = camera.get_view_matrix().into(); - self.proj = camera.projection.get_matrix().into(); + let view = camera.get_view_matrix(); + let proj = camera.projection.get_matrix(); + self.view = view.into(); + self.proj = proj.into(); + let inv_view_proj = (proj * view).invert().unwrap(); + self.inv_view_proj = inv_view_proj.into(); self.position = camera.position.to_homogeneous().into(); } } diff --git a/src/core/state.rs b/src/core/state.rs index a8d4f08..d1b2667 100644 --- a/src/core/state.rs +++ b/src/core/state.rs @@ -21,10 +21,10 @@ const SHADOW_MAP_LAYERS: u32 = 6; #[repr(C)] #[derive(Debug, Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct GlobalUniforms { + pub time: f32, pub light_matrix_index: u32, - // No DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED in WebGL pub use_shadowmaps: u32, - _padding: [u32; 2], + pub _padding: u32, } pub struct State { @@ -56,8 +56,8 @@ pub struct State { light_depth_bind_group: wgpu::BindGroup, light_depth_pass: RenderPass, light_depth_texture_target_views: [TextureView; SHADOW_MAP_LAYERS as usize], - light_matrix_uniform: GlobalUniforms, - light_matrix_buffer: wgpu::Buffer, + global_uniforms: GlobalUniforms, + global_uniforms_buffer: wgpu::Buffer, } impl State { @@ -198,10 +198,10 @@ impl State { usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); - let light_matrix_uniform = GlobalUniforms::default(); - let light_matrix_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + let global_uniforms = GlobalUniforms::default(); + let global_uniforms_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Light Matrix UB"), - contents: bytemuck::cast_slice(&[light_matrix_uniform]), + contents: bytemuck::cast_slice(&[global_uniforms]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); @@ -248,7 +248,7 @@ impl State { // matrix index wgpu::BindGroupEntry { binding: 1, - resource: light_matrix_buffer.as_entire_binding(), + resource: global_uniforms_buffer.as_entire_binding(), }, ], label: Some("Light Bind Group"), @@ -499,8 +499,8 @@ impl State { light_depth_bind_group, light_depth_pass, light_depth_texture_target_views, - light_matrix_uniform, - light_matrix_buffer, + global_uniforms, + global_uniforms_buffer, } } @@ -560,18 +560,21 @@ impl State { 0, bytemuck::cast_slice(&[self.light_uniform]), ); + + // Global uniforms + self.global_uniforms.time = time.as_secs_f32(); + self.global_uniforms.use_shadowmaps = if cfg!(target_arch = "wasm32") { 0u32 } else { 1u32 }; } pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { // render light to depth textures for i in 0..SHADOW_MAP_LAYERS as usize { - self.light_matrix_uniform.light_matrix_index = i as u32; - self.light_matrix_uniform.use_shadowmaps = if cfg!(target_arch = "wasm32") { 0u32 } else { 1u32 }; + self.global_uniforms.light_matrix_index = i as u32; self.queue.write_buffer( - &self.light_matrix_buffer, + &self.global_uniforms_buffer, 0, - bytemuck::cast_slice(&[self.light_matrix_uniform]), + bytemuck::cast_slice(&[self.global_uniforms]), ); let mut depth_encoder = self