Skip to content

Commit

Permalink
Support Sunlight (diffuse and ambient, in all shaders)
Browse files Browse the repository at this point in the history
  • Loading branch information
MeFisto94 committed Feb 14, 2025
1 parent c5de0a5 commit 06857bb
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 88 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ Things that still need to be implemented (loosely sorted by priority):
- Instanced Rendering of M2s (UnitMaterial is currently created in-place, even if the same texture has already been
used.) It remains to be seen if that is enough for rend3 to auto instance, though.
- TODO: Does Rend3 even have instancing?
- Debug Shader Reloading
- Fix Doodad Collisions
- Debug Shader Reloading. NOTE: This requires extensive rend3 changes because we need to reset the ShaderPreProcessor
and rebuild the base_graph that is usually only build in async_start, once.
- Lights Improvement (especially the SkyLight: Implement a game time that ticks, support ambient color)
- Move WMOs to "UnitsMaterial" (and probably rename/refactor it too.) This requires Unit to support more things (
transparency, albedo colors)
Expand Down
92 changes: 87 additions & 5 deletions shaders/src/terrain-opaque.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{{include "rend3-routine/math/brdf.wgsl"}}
{{include "rend3-routine/math/color.wgsl"}}
{{include "rend3-routine/math/matrix.wgsl"}}

{{include "rend3-routine/shadow/pcf.wgsl"}}

// TODO: Why don't we required the 16 byte padding here?
struct GpuTerrainData {
Expand All @@ -18,6 +18,16 @@ struct GpuTerrainData {
var primary_sampler: sampler;
@group(0) @binding(1)
var nearest_sampler: sampler;
@group(0) @binding(2)
var comparison_sampler: sampler_comparison;
@group(0) @binding(3)
var<uniform> uniforms: UniformData;
@group(0) @binding(4)
var<storage> directional_lights: DirectionalLightData;
@group(0) @binding(5)
var<storage> point_lights: PointLightData;
@group(0) @binding(6)
var shadows: texture_depth_2d;

// per material bind group
@group(1) @binding(0)
Expand All @@ -43,8 +53,10 @@ var textures: binding_array<texture_2d<f32>>;
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) vertex_relative: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(1) world_space_normal: vec3<f32>,
@location(2) @interpolate(flat) material: u32,
@location(3) view_position: vec4<f32>,
@location(4) normal: vec3<f32>,
}

@vertex
Expand Down Expand Up @@ -72,8 +84,10 @@ fn vs_main(@builtin(instance_index) instance_index: u32, @builtin(vertex_index)
vs_out.vertex_relative = vec3(xy.x, z, xy.y);

vs_out.material = data.material_index;
vs_out.normal = normalize(vs_in.normal);
vs_out.world_space_normal = normalize(vs_in.normal);
vs_out.normal = normalize(mv_mat3 * (inv_scale_sq * vs_in.normal));
vs_out.position = model_view_proj * position_vec4;
vs_out.view_position = model_view * position_vec4;
return vs_out;
}

Expand All @@ -86,7 +100,7 @@ fn fs_main(vs_out: VertexOutput) -> @location(0) vec4<f32> {

let tex_scale = -9.3333333;
let blend_sharpness = 25.0;
var blend_weights = pow(abs(vs_out.normal), vec3(blend_sharpness));
var blend_weights = pow(abs(vs_out.world_space_normal), vec3(blend_sharpness));
blend_weights /= blend_weights.x + blend_weights.y + blend_weights.z;
// blend_weights = vec3(0.0, 1.0, 0.0); // TODO: Use this to disable triplanar mapping, I have yet to encounter a mountain where it improves visuals...

Expand Down Expand Up @@ -121,5 +135,73 @@ fn fs_main(vs_out: VertexOutput) -> @location(0) vec4<f32> {
albedo_sum = mix(albedo_sum, albedo, alpha);
}

return vec4(albedo_sum.xyz, 1.0);
let combined_albedo = vec4(albedo_sum.xyz, 1.0);

// Diffuse color / albedo has been determined, now it's time to apply directional light and shadows.
var color = vec3(0.0);

// View vector
let v = -normalize(vs_out.view_position.xyz);
// Transform vectors into view space
let view_mat3 = mat3x3<f32>(uniforms.view[0].xyz, uniforms.view[1].xyz, uniforms.view[2].xyz);
for (var i = 0; i < i32(directional_lights.count); i += 1) {
let light = directional_lights.data[i];

// Get the shadow ndc coordinates, then convert to texture sample coordinates
let shadow_ndc = (light.view_proj * uniforms.inv_view * vs_out.view_position).xyz;
let shadow_flipped = (shadow_ndc.xy * 0.5) + 0.5;
let shadow_local_coords = vec2<f32>(shadow_flipped.x, 1.0 - shadow_flipped.y);

// Texture sample coordinates of
var top_left = light.offset;
var top_right = top_left + light.size;
let shadow_coords = mix(top_left, top_right, shadow_local_coords);

// The shadow is stored in an atlas, so we need to make sure we don't linear blend
// across atlasses. We move our conditional borders in a half a pixel for standard
// linear blending (so we're hitting texel centers on the edge). We move it an additional
// pixel in so that our pcf5 offsets don't move off the edge of the atlasses.
let shadow_border = light.inv_resolution * 1.5;
top_left += shadow_border;
top_right -= shadow_border;

var shadow_value = 1.0;
if (
any(shadow_flipped >= top_left) && // XY lower
any(shadow_flipped <= top_right) && // XY upper
shadow_ndc.z >= 0.0 && // Z lower
shadow_ndc.z <= 1.0 // Z upper
) {
shadow_value = shadow_sample_pcf5(shadows, comparison_sampler, shadow_coords, shadow_ndc.z);
}

// Calculate light source vector
let l = normalize(view_mat3 * -light.direction);
color += surface_shading(l, light.color, v, shadow_value, combined_albedo.xyz, vs_out.normal);
}

return clamp_ambient_color(combined_albedo, color);
}

fn clamp_ambient_color(albedo: vec4<f32>, shaded_color: vec3<f32>) -> vec4<f32> {
let ambient = uniforms.ambient * albedo;
let shaded = vec4<f32>(shaded_color, albedo.a); // TODO: Understand why the shaded color isn't already alphaed
return max(ambient, shaded);
}

// We don't use PBR here
fn surface_shading(inv_light_dir: vec3<f32>, intensity: vec3<f32>, view_pos: vec3<f32>, occlusion: f32, diffuse_color: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
let nol = saturate(dot(normal, inv_light_dir));

// specular
let fr = vec3(0.0);

// diffuse
let fd = diffuse_color * brdf_fd_lambert();

let energy_comp = 1.0;
let color = fd + fr * energy_comp;

let light_attenuation = 1.0; // directional lights -> no
return (color * intensity) * (light_attenuation * nol * occlusion /* shadows */);
}
84 changes: 83 additions & 1 deletion shaders/src/units-opaque.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{{include "rend3-routine/math/brdf.wgsl"}}
{{include "rend3-routine/math/color.wgsl"}}
{{include "rend3-routine/math/matrix.wgsl"}}
{{include "rend3-routine/shadow/pcf.wgsl"}}

// TODO: Why don't we required the 16 byte padding here?
struct GpuUnitsData{
Expand All @@ -16,6 +17,16 @@ struct GpuUnitsData{
var primary_sampler: sampler;
@group(0) @binding(1)
var nearest_sampler: sampler;
@group(0) @binding(2)
var comparison_sampler: sampler_comparison;
@group(0) @binding(3)
var<uniform> uniforms: UniformData;
@group(0) @binding(4)
var<storage> directional_lights: DirectionalLightData;
@group(0) @binding(5)
var<storage> point_lights: PointLightData;
@group(0) @binding(6)
var shadows: texture_depth_2d;

// per material bind group
@group(1) @binding(0)
Expand All @@ -35,6 +46,7 @@ var textures: binding_array<texture_2d<f32>>;
vertex_fetch
object_buffer
position
normal
texture_coords_0
}}

Expand All @@ -43,6 +55,8 @@ struct VertexOutput {
@location(0) vertex_relative: vec3<f32>,
@location(1) @interpolate(flat) material: u32,
@location(2) coords0: vec2<f32>,
@location(3) view_position: vec4<f32>,
@location(4) normal: vec3<f32>,
}

@vertex
Expand All @@ -63,6 +77,8 @@ fn vs_main(@builtin(instance_index) instance_index: u32, @builtin(vertex_index)
vs_out.coords0 = vs_in.texture_coords_0;
vs_out.material = data.material_index;
vs_out.position = model_view_proj * position_vec4;
vs_out.view_position = model_view * position_vec4;
vs_out.normal = normalize(mv_mat3 * (inv_scale_sq * vs_in.normal));
return vs_out;
}

Expand Down Expand Up @@ -94,5 +110,71 @@ fn fs_main(vs_out: VertexOutput) -> @location(0) vec4<f32> {
discard;
}

return albedo_sum;
// Diffuse color / albedo has been determined, now it's time to apply directional light and shadows.
var color = vec3(0.0);

// View vector
let v = -normalize(vs_out.view_position.xyz);
// Transform vectors into view space
let view_mat3 = mat3x3<f32>(uniforms.view[0].xyz, uniforms.view[1].xyz, uniforms.view[2].xyz);
for (var i = 0; i < i32(directional_lights.count); i += 1) {
let light = directional_lights.data[i];

// Get the shadow ndc coordinates, then convert to texture sample coordinates
let shadow_ndc = (light.view_proj * uniforms.inv_view * vs_out.view_position).xyz;
let shadow_flipped = (shadow_ndc.xy * 0.5) + 0.5;
let shadow_local_coords = vec2<f32>(shadow_flipped.x, 1.0 - shadow_flipped.y);

// Texture sample coordinates of
var top_left = light.offset;
var top_right = top_left + light.size;
let shadow_coords = mix(top_left, top_right, shadow_local_coords);

// The shadow is stored in an atlas, so we need to make sure we don't linear blend
// across atlasses. We move our conditional borders in a half a pixel for standard
// linear blending (so we're hitting texel centers on the edge). We move it an additional
// pixel in so that our pcf5 offsets don't move off the edge of the atlasses.
let shadow_border = light.inv_resolution * 1.5;
top_left += shadow_border;
top_right -= shadow_border;

var shadow_value = 1.0;
if (
any(shadow_flipped >= top_left) && // XY lower
any(shadow_flipped <= top_right) && // XY upper
shadow_ndc.z >= 0.0 && // Z lower
shadow_ndc.z <= 1.0 // Z upper
) {
shadow_value = shadow_sample_pcf5(shadows, comparison_sampler, shadow_coords, shadow_ndc.z);
}

// Calculate light source vector
let l = normalize(view_mat3 * -light.direction);
color += surface_shading(l, light.color, v, shadow_value, albedo_sum.xyz, -normalize(vs_out.normal));
}

return clamp_ambient_color(albedo_sum, color);
}

fn clamp_ambient_color(albedo: vec4<f32>, shaded_color: vec3<f32>) -> vec4<f32> {
let ambient = uniforms.ambient * albedo;
let shaded = vec4<f32>(shaded_color, albedo.a); // TODO: Understand why the shaded color isn't already alphaed
return max(ambient, shaded);
}

// We don't use PBR here
fn surface_shading(inv_light_dir: vec3<f32>, intensity: vec3<f32>, view_pos: vec3<f32>, occlusion: f32, diffuse_color: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
let nol = saturate(dot(normal, inv_light_dir));

// specular
let fr = vec3(0.0);

// diffuse
let fd = diffuse_color * brdf_fd_lambert();

let energy_comp = 1.0;
let color = fd + fr * energy_comp;

let light_attenuation = 1.0; // directional lights -> no
return (color * intensity) * (light_attenuation * nol * occlusion /* shadows */);
}
Loading

0 comments on commit 06857bb

Please sign in to comment.