From cb5b559bbd9304bdcf587666cc410bb854b29e76 Mon Sep 17 00:00:00 2001 From: marceline-cramer Date: Wed, 16 Feb 2022 21:32:38 -0700 Subject: [PATCH] PBR --- src/camera.rs | 5 +++ src/model.rs | 14 ++++++-- src/pool.rs | 29 ++++++++++++---- src/renderer.rs | 5 ++- src/shader.wgsl | 92 ++++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 1057e2c..95794ef 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -3,6 +3,7 @@ use std::time::Instant; use winit::event::{ElementState, VirtualKeyCode}; pub trait Camera { + fn get_eye(&self) -> [f32; 4]; fn get_vp(&self) -> [[f32; 4]; 4]; } @@ -148,6 +149,10 @@ impl Flycam { } impl Camera for Flycam { + fn get_eye(&self) -> [f32; 4] { + self.position.extend(0.0).to_array() + } + fn get_vp(&self) -> [[f32; 4]; 4] { let orientation = Quat::from_euler(glam::EulerRot::XYZ, self.tilt, self.pan, 0.0); let rotation = Mat4::from_quat(orientation); diff --git a/src/model.rs b/src/model.rs index 635c97d..34a1ab4 100644 --- a/src/model.rs +++ b/src/model.rs @@ -93,8 +93,10 @@ impl ObjModel { let albedo = on_load.load_texture(&albedo_data); - let material_data = MaterialData { albedo }; - let material_handle = on_load.load_material(&material_data); + let material_handle = on_load.load_material(&MaterialData { + albedo, + metallic_roughness: albedo, // TODO better placeholder MR texture + }); let mesh = BasicMesh { mesh_handle, @@ -201,7 +203,13 @@ impl<'a, T: OnLoad> GltfLoader<'a, T> { let base_color = pbr.base_color_texture().unwrap().texture(); let albedo = self.load_texture(base_color); - self.on_load.load_material(&MaterialData { albedo }) + let metallic_roughness = pbr.metallic_roughness_texture().unwrap().texture(); + let metallic_roughness = self.load_texture(metallic_roughness); + + self.on_load.load_material(&MaterialData { + albedo, + metallic_roughness, + }) } pub fn load_texture(&mut self, texture: gltf::Texture) -> TextureHandle { diff --git a/src/pool.rs b/src/pool.rs index eb05ef6..89e5eff 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -145,6 +145,7 @@ impl TexturePool { pub struct MaterialData { pub albedo: TextureHandle, + pub metallic_roughness: TextureHandle, } pub struct Material { @@ -158,6 +159,17 @@ pub struct MaterialPool { impl MaterialPool { pub fn new(device: &wgpu::Device) -> Self { + let texture_entry = wgpu::BindGroupLayoutEntry { + binding: u32::MAX, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + multisampled: false, + view_dimension: wgpu::TextureViewDimension::D2, + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + }, + count: None, + }; + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { @@ -168,13 +180,11 @@ impl MaterialPool { }, wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - }, - count: None, + ..texture_entry + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + ..texture_entry }, ], label: Some("Texture Bind Group Layout"), @@ -193,6 +203,7 @@ impl MaterialPool { data: &MaterialData, ) -> MaterialHandle { let albedo_view = &texture_pool.textures[data.albedo.id].texture_view; + let mr_view = &texture_pool.textures[data.metallic_roughness.id].texture_view; let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.bind_group_layout, @@ -205,6 +216,10 @@ impl MaterialPool { binding: 1, resource: wgpu::BindingResource::TextureView(albedo_view), }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::TextureView(mr_view), + }, ], label: None, }); diff --git a/src/renderer.rs b/src/renderer.rs index 19dd213..db6559b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -61,7 +61,7 @@ impl Renderer { entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStages::VERTEX, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, @@ -378,17 +378,20 @@ impl OnLoad for &mut Renderer { #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct CameraUniform { + eye: [f32; 4], vp: [[f32; 4]; 4], } impl CameraUniform { pub fn new() -> Self { Self { + eye: [0.0; 4], vp: glam::Mat4::IDENTITY.to_cols_array_2d(), } } pub fn update(&mut self, camera: &impl Camera) { + self.eye = camera.get_eye(); self.vp = camera.get_vp(); } } diff --git a/src/shader.wgsl b/src/shader.wgsl index 78fb980..5a055bf 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,4 +1,5 @@ struct CameraUniform { + eye: vec4; vp: mat4x4; }; @@ -44,6 +45,76 @@ var meshes: MeshData; [[group(2), binding(0)]] var m_sampler: sampler; [[group(2), binding(1)]] var m_albedo: texture_2d; +[[group(2), binding(2)]] var m_metallic_roughness: texture_2d; + +let PI: f32 = 3.141592; + +fn D_GGX(NoH: f32, roughness: f32) -> f32 { + let a = roughness * roughness; + let a2 = a * a; + let NoH2 = NoH * NoH; + let f = NoH * (a2 - 1.0) + 1.0; + return a2 / (PI * f * f); +} + +fn g1(NoV: f32, roughness: f32, k: f32) -> f32 { + let denom = NoV * (1.0 - k) + k; + return NoV / denom; +} + +fn G_SmithGGXCorrelated(NoV: f32, NoL: f32, roughness: f32) -> f32 { + let r = roughness + 1.0; + let k = (r * r) / 8.0; + + let g1l = g1(NoV, roughness, k); + let g1v = g1(NoL, roughness, k); + + return g1l * g1v; +} + +fn F_Schlick(u: f32, f0: vec3) -> vec3 { + let f = pow(1.0 - u, 5.0); + return f + f0 * (1.0 - f); +} + +fn BRDF( + l: vec3, // normalized light direction + n: vec3, // normalized surface normal + v: vec3, // normalized view direction + albedo: vec3, // surface albedo + metallic: f32, // surface metallic + roughness: f32, // surface roughness +) -> vec3 { + let h = normalize(v + l); + let NoL = max(dot(n, l), 0.0); + let NoV = max(dot(n, v), 0.0); + let NoH = max(dot(n, h), 0.0); + let LoH = max(dot(l, h), 0.0); + + // calculate reflectance at surface incidence + let f0 = mix(vec3(0.04), albedo, metallic); + + // specular BRDF + let D = D_GGX(NoH, roughness); + let G = G_SmithGGXCorrelated(NoV, NoL, roughness); + let F = F_Schlick(LoH, f0); + + let numerator = (D * G) * F; + let denominator = 4.0 * NoV * NoL; + + let Fr = numerator / max(denominator, 0.01); + + // diffuse BRDF + let diffuse_fresnel = + (vec3(1.0) - F_Schlick(NoL, f0)) * + (vec3(1.0) - F_Schlick(NoV, f0)); + + let lambertian = albedo / PI; + let Fd = diffuse_fresnel * lambertian; + + // TODO multiple scattering + return (Fr + Fd) * NoL; +} [[stage(vertex)]] fn vs_main( @@ -68,14 +139,27 @@ fn fs_main( ) -> [[location(0)]] vec4 { let albedo = textureSample(m_albedo, m_sampler, frag.tex_coords).rgb; let normal = normalize(frag.normal); + let view = normalize(camera.eye.xyz - frag.position); + + let metallic_roughness = textureSample(m_metallic_roughness, m_sampler, frag.tex_coords).bg; + let metallic = metallic_roughness.x; + let roughness = metallic_roughness.y; var lum = vec3(0.0); for(var i = 0; i < 4; i = i + 1) { let light = point_lights.lights[i]; - let light_relative = light.center.xyz - frag.position; - let received = dot(normal, normalize(light_relative)); - let diffuse = (albedo * received) / dot(light_relative, light_relative); - lum = lum + (diffuse * light.intensity.rgb); + let light_position = light.center.xyz - frag.position; + let light_intensity = light.intensity.rgb; + let light_direction = normalize(light_position); + + let radiance = light_intensity / dot(light_position, light_position); + + let reflected = BRDF( + light_direction, normal, view, + albedo, metallic, roughness + ); + + lum = lum + (radiance * reflected); } return vec4(lum, 1.0);