diff --git a/dotrix_pbr/src/lib.rs b/dotrix_pbr/src/lib.rs index ffe42c91..1f0d6e53 100644 --- a/dotrix_pbr/src/lib.rs +++ b/dotrix_pbr/src/lib.rs @@ -26,3 +26,11 @@ pub fn extension(app: &mut Application) { solid::extension(app); skeletal::extension(app); } + +pub fn add_pbr_to_shader(source: &str, bind_group: usize, binding: usize) -> String { + let pbr_code = include_str!("shaders/pbr.inc.wgsl"); + + let pbr_lighted_code = Lights::add_to_shader(pbr_code, bind_group, binding); + + source.replace("{{ include(light) }}", &pbr_lighted_code) +} diff --git a/dotrix_pbr/src/shaders/light.inc.wgsl b/dotrix_pbr/src/shaders/light.inc.wgsl index c270bc26..665d90d7 100644 --- a/dotrix_pbr/src/shaders/light.inc.wgsl +++ b/dotrix_pbr/src/shaders/light.inc.wgsl @@ -1,6 +1,4 @@ -// Most of this comes from https://learnopengl.com/PBR/Lighting let MAX_LIGHTS_COUNT: u32 = {{ max_lights_count }}; -let PI: f32 = 3.14159; struct LightCalcOutput { light_direction: vec3; @@ -51,7 +49,6 @@ var u_light: Light; fn calculate_directional( light: DirectionalLight, - normal: vec3, ) -> LightCalcOutput { let light_direction: vec3 = normalize(-light.direction.xyz); @@ -65,7 +62,6 @@ fn calculate_directional( fn calculate_point( light: PointLight, position: vec3, - normal: vec3, ) -> LightCalcOutput { let light_direction: vec3 = normalize(light.position.xyz - position); @@ -85,7 +81,6 @@ fn calculate_point( fn calculate_simple( light: SimpleLight, position: vec3, - normal: vec3, ) -> LightCalcOutput { let light_direction: vec3 = normalize(light.position.xyz - position.xyz); @@ -99,7 +94,6 @@ fn calculate_simple( fn calculate_spot( light: SpotLight, position: vec3, - normal: vec3, ) -> LightCalcOutput { let light_direction: vec3 = normalize(light.position.xyz - position.xyz); let theta: f32 = dot(light_direction, normalize(-light.direction.xyz)); @@ -113,173 +107,55 @@ fn calculate_spot( return out; } -fn distribution_ggx(normal: vec3, halfway: vec3, roughness: f32) -> f32 -{ - let a: f32 = roughness*roughness; - let a2: f32 = a*a; - let n_dot_h: f32 = max(dot(normal, halfway), 0.0); - let n_dot_h_2: f32 = n_dot_h*n_dot_h; - - let num: f32 = a2; - var denom: f32 = (n_dot_h_2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - - return num / denom; -} - -fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 -{ - let r: f32 = (roughness + 1.0); - let k: f32 = (r*r) / 8.0; - - let num: f32 = n_dot_v; - let denom: f32 = n_dot_v * (1.0 - k) + k; - - return num / denom; -} -fn geometry_smith(normal: vec3, camera_direction: vec3, light_direction: vec3, roughness: f32) -> f32 -{ - let n_dot_v: f32 = max(dot(normal, camera_direction), 0.0); - let n_dot_l: f32 = max(dot(normal, light_direction), 0.0); - let ggx2: f32 = geometry_schlick_ggx(n_dot_v, roughness); - let ggx1: f32 = geometry_schlick_ggx(n_dot_l, roughness); - - return ggx1 * ggx2; +// This will get the direction direction and intensity of +// the nth light towards a position +// If used in conjectuion with `get_light_count` +// It allows for more consistent iter code by providing +// A standard single data `LightCalcOutput` for any light +// regardless of type +fn calculate_nth_light_ray( + in_camera_index: u32, + position: vec3, +) -> LightCalcOutput { + var camera_index: u32 = in_camera_index; + // directional + let dir_count = min(u32(u_light.count.x), MAX_LIGHTS_COUNT); + if (camera_index < dir_count) { + var light: DirectionalLight = u_light.directional[camera_index]; + return calculate_directional(light); + } + camera_index = camera_index - dir_count; + // point + let point_count = min(u32(u_light.count.y), MAX_LIGHTS_COUNT); + if (camera_index < point_count) { + var light: PointLight = u_light.point[camera_index]; + return calculate_point(light, position); + } + camera_index = camera_index - point_count; + // simple + let simple_count = min(u32(u_light.count.z), MAX_LIGHTS_COUNT); + if (camera_index < simple_count) { + var light: SimpleLight = u_light.simple[camera_index]; + return calculate_simple(light, position); + } + camera_index = camera_index - simple_count; + // spot + let spot_count = min(u32(u_light.count.w), MAX_LIGHTS_COUNT); + if (camera_index < spot_count) { + var light: SpotLight = u_light.spot[camera_index]; + return calculate_spot(light, position); + } + // Trying to access a non existant light + var oob: LightCalcOutput; + oob.light_direction = vec3(0.); + oob.radiance = vec3(0.); + return oob; } -// Calulates the amount of light that refects (specular) and that which scatters (diffuse) -fn calculate_fresnel_schlick(cos_theta: f32, fresnel_schlick_0: vec3) -> vec3 -{ - return fresnel_schlick_0 + (vec3(1.0) - fresnel_schlick_0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0); +fn get_light_count() -> u32 { + return u_light.count.x + u_light.count.y + u_light.count.z + u_light.count.w; } -fn pbr( - light_out: LightCalcOutput, - camera_direction: vec3, // Camera direction - normal: vec3, // normal - fresnel_schlick_0: vec3, // surface reflection at zero incidence - albedo: vec3, // scatter color in linear space - metallic: f32, // Metallic (reflectance) - roughness: f32 // Roughness (random scatter) -) -> vec3 { - let light_direction: vec3 = light_out.light_direction; - let halfway: vec3 = normalize(camera_direction + light_direction); - - // cook-torrance brdf - let normal_distribution_function: f32 = distribution_ggx(normal, halfway, roughness); - let geometry_function: f32 = geometry_smith(normal, camera_direction, light_direction, roughness); - let fresnel_schlick: vec3 = calculate_fresnel_schlick(max(dot(halfway, camera_direction), 0.0), fresnel_schlick_0); - - let reflection_specular_fraction: vec3 = fresnel_schlick; - var refraction_diffuse_fraction: vec3 = vec3(1.0) - reflection_specular_fraction; // refraction/diffuse fraction - refraction_diffuse_fraction = refraction_diffuse_fraction * (1.0 - metallic); - - let numerator: vec3 = normal_distribution_function * geometry_function * fresnel_schlick; - let denominator: f32 = 4.0 * max(dot(normal, camera_direction), 0.0) * max(dot(normal, light_direction), 0.0) + 0.0001; - let specular: vec3 = numerator / denominator; - - // get the outgoing radiance - let n_dot_l: f32 = max(dot(normal, light_direction), 0.0); - return (refraction_diffuse_fraction * albedo / PI + specular) * light_out.radiance * n_dot_l; -} - -fn calculate_lighting( - position: vec3, - normal_in: vec3, - albedo: vec3, - roughness: f32, - metallic: f32, - ao: f32, -) -> vec4 { - let camera_position: vec3 = u_light.camera_position.xyz; - var light_color: vec3 = vec3(0.); - - let normal: vec3 = normalize(normal_in ); - let camera_direction: vec3 = normalize(camera_position - position); - - var fresnel_schlick_0: vec3 = vec3(0.04); - fresnel_schlick_0 = mix(fresnel_schlick_0, albedo, vec3(metallic)); - - // Directions light - var i: u32 = 0u; - var count: u32 = min(u32(u_light.count.x), MAX_LIGHTS_COUNT); - for (i = 0u; i< count; i = i + 1u) { - let light_result = calculate_directional( - u_light.directional[i], - normal - ); - light_color = light_color + pbr( - light_result, - camera_direction, - normal, - fresnel_schlick_0, - albedo, - metallic, - roughness - ); - } - // Point light - count = min(u32(u_light.count.y), MAX_LIGHTS_COUNT); - for (i = 0u; i< count; i = i + 1u) { - let light_result = calculate_point( - u_light.point[i], - position, - normal - ); - light_color = light_color + pbr( - light_result, - camera_direction, - normal, - fresnel_schlick_0, - albedo, - metallic, - roughness - ); - } - // Simple light - count = min(u32(u_light.count.z), MAX_LIGHTS_COUNT); - for (i = 0u; i< count; i = i + 1u) { - let light_result = calculate_simple( - u_light.simple[i], - position, - normal - ); - light_color = light_color + pbr( - light_result, - camera_direction, - normal, - fresnel_schlick_0, - albedo, - metallic, - roughness - ); - } - // Spot light - count = min(u32(u_light.count.w), MAX_LIGHTS_COUNT); - for (i = 0u; i< count; i = i + 1u) { - let light_result = calculate_spot( - u_light.spot[i], - position, - normal - ); - light_color = light_color + pbr( - light_result, - camera_direction, - normal, - fresnel_schlick_0, - albedo, - metallic, - roughness - ); - } - - // Ambient - let ambient = u_light.ambient.xyz * albedo * ao; - light_color = light_color + ambient; - - // Gamma correct - light_color = light_color / (light_color + vec3(1.0)); - light_color = pow(light_color, vec3(1.0/2.2)); - - return vec4(light_color, 1.0); +fn get_ambient() -> vec3 { + return u_light.ambient.xyz; } diff --git a/dotrix_pbr/src/shaders/pbr.inc.wgsl b/dotrix_pbr/src/shaders/pbr.inc.wgsl new file mode 100644 index 00000000..1eb07d0b --- /dev/null +++ b/dotrix_pbr/src/shaders/pbr.inc.wgsl @@ -0,0 +1,118 @@ +// Most of this comes from https://learnopengl.com/PBR/Lighting +let PI: f32 = 3.14159; + +{{ include(light) }} + +fn distribution_ggx(normal: vec3, halfway: vec3, roughness: f32) -> f32 +{ + let a: f32 = roughness*roughness; + let a2: f32 = a*a; + let n_dot_h: f32 = max(dot(normal, halfway), 0.0); + let n_dot_h_2: f32 = n_dot_h*n_dot_h; + + let num: f32 = a2; + var denom: f32 = (n_dot_h_2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; + + return num / denom; +} + +fn geometry_schlick_ggx(n_dot_v: f32, roughness: f32) -> f32 +{ + let r: f32 = (roughness + 1.0); + let k: f32 = (r*r) / 8.0; + + let num: f32 = n_dot_v; + let denom: f32 = n_dot_v * (1.0 - k) + k; + + return num / denom; +} +fn geometry_smith(normal: vec3, camera_direction: vec3, light_direction: vec3, roughness: f32) -> f32 +{ + let n_dot_v: f32 = max(dot(normal, camera_direction), 0.0); + let n_dot_l: f32 = max(dot(normal, light_direction), 0.0); + let ggx2: f32 = geometry_schlick_ggx(n_dot_v, roughness); + let ggx1: f32 = geometry_schlick_ggx(n_dot_l, roughness); + + return ggx1 * ggx2; +} + +// Calulates the amount of light that refects (specular) and that which scatters (diffuse) +fn calculate_fresnel_schlick(cos_theta: f32, fresnel_schlick_0: vec3) -> vec3 +{ + return fresnel_schlick_0 + (vec3(1.0) - fresnel_schlick_0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0); +} + +fn pbr( + light_out: LightCalcOutput, + camera_direction: vec3, // Camera direction + normal: vec3, // normal + fresnel_schlick_0: vec3, // surface reflection at zero incidence + albedo: vec3, // scatter color in linear space + metallic: f32, // Metallic (reflectance) + roughness: f32 // Roughness (random scatter) +) -> vec3 { + let light_direction: vec3 = light_out.light_direction; + let halfway: vec3 = normalize(camera_direction + light_direction); + + // cook-torrance brdf + let normal_distribution_function: f32 = distribution_ggx(normal, halfway, roughness); + let geometry_function: f32 = geometry_smith(normal, camera_direction, light_direction, roughness); + let fresnel_schlick: vec3 = calculate_fresnel_schlick(max(dot(halfway, camera_direction), 0.0), fresnel_schlick_0); + + let reflection_specular_fraction: vec3 = fresnel_schlick; + var refraction_diffuse_fraction: vec3 = vec3(1.0) - reflection_specular_fraction; // refraction/diffuse fraction + refraction_diffuse_fraction = refraction_diffuse_fraction * (1.0 - metallic); + + let numerator: vec3 = normal_distribution_function * geometry_function * fresnel_schlick; + let denominator: f32 = 4.0 * max(dot(normal, camera_direction), 0.0) * max(dot(normal, light_direction), 0.0) + 0.0001; + let specular: vec3 = numerator / denominator; + + // get the outgoing radiance + let n_dot_l: f32 = max(dot(normal, light_direction), 0.0); + return (refraction_diffuse_fraction * albedo / PI + specular) * light_out.radiance * n_dot_l; +} + +fn calculate_lighting( + position: vec3, + normal_in: vec3, + albedo: vec3, + roughness: f32, + metallic: f32, + ao: f32, +) -> vec4 { + let camera_position: vec3 = u_light.camera_position.xyz; + var light_color: vec3 = vec3(0.); + + let normal: vec3 = normalize(normal_in ); + let camera_direction: vec3 = normalize(camera_position - position); + + var fresnel_schlick_0: vec3 = vec3(0.04); + fresnel_schlick_0 = mix(fresnel_schlick_0, albedo, vec3(metallic)); + + // Directions light + var i: u32 = 0u; + var count: u32 = get_light_count(); + for (i = 0u; i< count; i = i + 1u) { + let light_result = calculate_nth_light_ray(i, position); + light_color = light_color + pbr( + light_result, + camera_direction, + normal, + fresnel_schlick_0, + albedo, + metallic, + roughness + ); + } + + // Ambient + let ambient = get_ambient() * albedo * ao; + light_color = light_color + ambient; + + // Gamma correct + light_color = light_color / (light_color + vec3(1.0)); + light_color = pow(light_color, vec3(1.0/2.2)); + + return vec4(light_color, 1.0); +} diff --git a/dotrix_pbr/src/skeletal.rs b/dotrix_pbr/src/skeletal.rs index a5c95475..fabd99e4 100644 --- a/dotrix_pbr/src/skeletal.rs +++ b/dotrix_pbr/src/skeletal.rs @@ -8,7 +8,7 @@ use dotrix_core::{Application, Assets, Color, Globals, Id, Pose, Renderer, Trans use dotrix_math::{Quat, Rad, Rotation3, Vec3}; -use crate::{Lights, Material, Model}; +use crate::{add_pbr_to_shader, Lights, Material, Model}; pub const PIPELINE_LABEL: &str = "pbr::skeletal"; @@ -223,7 +223,7 @@ pub fn startup(mut assets: Mut) { assets.store_as( Shader { name: String::from(PIPELINE_LABEL), - code: Lights::add_to_shader(shader, 0, 2), + code: add_pbr_to_shader(shader, 0, 2), ..Default::default() }, PIPELINE_LABEL, diff --git a/dotrix_pbr/src/solid.rs b/dotrix_pbr/src/solid.rs index bc3cdf89..583b1699 100644 --- a/dotrix_pbr/src/solid.rs +++ b/dotrix_pbr/src/solid.rs @@ -8,7 +8,7 @@ use dotrix_core::{Application, Assets, Color, Globals, Id, Renderer, Transform, use dotrix_math::{Quat, Rad, Rotation3, Vec3}; -use crate::{Lights, Material, Model}; +use crate::{add_pbr_to_shader, Lights, Material, Model}; pub const PIPELINE_LABEL: &str = "pbr::solid"; @@ -214,7 +214,7 @@ pub fn startup(mut assets: Mut) { assets.store_as( Shader { name: String::from(PIPELINE_LABEL), - code: Lights::add_to_shader(shader, 0, 2), + code: add_pbr_to_shader(shader, 0, 2), ..Default::default() }, PIPELINE_LABEL,