diff --git a/assets/shaders/mesh.glsl b/assets/shaders/mesh.glsl index a3e46ba..7204d23 100644 --- a/assets/shaders/mesh.glsl +++ b/assets/shaders/mesh.glsl @@ -43,7 +43,7 @@ int getCSMSplitCount(int lightIdx) { int getCSMSplit(int lightIdx, float depth) { int totalSplits = getCSMSplitCount(lightIdx); - for (int i = 1; i < totalSplits; i++) { + for (int i = 0; i < totalSplits; i++) { if (depth > lights[lightIdx].csm_split_points[i]) { return i; } @@ -180,11 +180,21 @@ vec2 poissonDisk[4] = vec2[]( vec2( 0.34495938, 0.29387760 ) ); +const vec3 csm_split_colors[4] = vec3[]( + vec3(0f, 1f, 0.17f), + vec3(1f, 0.2f, 0.6f), + vec3(0.17f, 0.78f, 1f), + vec3(0.91f, 0.93f, 0.64f) +); + float map(float value, float min1, float max1, float min2, float max2) { return min2 + (value - min1) * (max2 - min2) / (max1 - min1); } vec3 microfacetModel(Material mat, int light_idx, Light light, vec3 P, vec3 N) { + int csm_split_idx = getCSMSplit(light_idx, P.z); + mat.albedo = mix(mat.albedo, csm_split_colors[csm_split_idx], 0.8); + vec3 diffuseBrdf = vec3(0); // metallic if (!mat.metallic) { diffuseBrdf = mat.albedo; @@ -251,8 +261,6 @@ vec3 microfacetModel(Material mat, int light_idx, Light light, vec3 P, vec3 N) { // Directional shadow vec2 shadow_map_texel_size = 1.0 / vec2(textureSize(shadow_maps, 0)); shadow_offset *= shadow_map_texel_size.x; - float view_depth = (light.view_mat * vec4(VertexOut.wPos, 1.0)).z; - int csm_split_idx = getCSMSplit(light_idx, view_depth); vec4 shadow_pos = light.view_proj_mats[csm_split_idx] * vec4(VertexOut.wPos, 1.0); shadow_pos.xy = (light.view_proj_mats[csm_split_idx] * (vec4(VertexOut.wPos, 1.0) + shadow_offset)).xy; shadow_pos /= shadow_pos.w; diff --git a/src/Render.zig b/src/Render.zig index dcccc3b..05c3091 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -19,7 +19,12 @@ pub const MAX_FRAMES_QUEUED = 3; pub const MAX_LIGHTS = 8; pub const MAX_DRAW_COMMANDS = 4096; pub const MAX_LIGHT_COMMANDS = 2048; -pub const CSM_SPLITS = 2; +pub const CSM_SPLITS = 4; +// affects how cascades are split +// 0 - exponential +// 1 - uniform +// 0.5 - mix between the two +pub const CSM_EXPO_UNIFORM_FACTOR = 0.8; pub const Render = @This(); @@ -467,6 +472,9 @@ pub fn finish(self: *Render) void { // Light shadow maps { + gl.enable(gl.DEPTH_CLAMP); + defer gl.disable(gl.DEPTH_CLAMP); + gl.bindVertexArray(self.shadow_vao); gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, self.shadow_framebuffer); @@ -500,10 +508,21 @@ pub fn finish(self: *Render) void { light.params.shadow_map_idx = shadow_map_idx; light.params.csm_split_count = @floatFromInt(CSM_SPLITS); - const csm_split_z_diff = (self.camera.far - self.camera.near) / @as(f32, @floatFromInt(CSM_SPLITS)); + var splits: [CSM_SPLITS + 1]f32 = undefined; + + const splits_count_f: f32 = @floatFromInt(CSM_SPLITS); + for (0..CSM_SPLITS + 1) |split_idx| { + const split_idx_f: f32 = @floatFromInt(split_idx); + const split_i_over_n = split_idx_f / splits_count_f; + const expo_split = self.camera.near * std.math.pow(f32, self.camera.far / self.camera.near, split_i_over_n); + const uniform_split = self.camera.near + split_i_over_n * (self.camera.far - self.camera.near); + const split = CSM_EXPO_UNIFORM_FACTOR * expo_split + (1.0 - CSM_EXPO_UNIFORM_FACTOR) * uniform_split; + splits[split_idx] = split; + } for (0..CSM_SPLITS) |split_idx| { - var split_far: f32 = 0; + const split_near = splits[split_idx]; + const split_far = splits[split_idx + 1]; gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.shadow_texture_array, 0, @intCast(shadow_map_idx * CSM_SPLITS + split_idx)); const check_fbo_status = gl.checkNamedFramebufferStatus(self.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); @@ -516,9 +535,8 @@ pub fn finish(self: *Render) void { { if (self.update_view_frustum) { var camera = self.camera.*; - camera.near = camera.near + csm_split_z_diff * @as(f32, @floatFromInt(split_idx)); - camera.far = camera.near + csm_split_z_diff; - split_far = camera.far; + camera.near = split_near; + camera.far = split_far; const inv_csm_proj = camera.projection().mul(camera.view_mat).inv(); for (math.ndc_box_corners, 0..) |corner, corner_idx| { @@ -939,7 +957,7 @@ fn renderShadow(self: *Render, frustum: *const math.Frustum) void { const mesh = self.assetman.resolveMesh(cmd.mesh); const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max); - if (!frustum.intersectAABB(aabb.transform(cmd.transform))) { + if (!frustum.intersectAABBSkipNear(aabb.transform(cmd.transform))) { continue; } diff --git a/src/math.zig b/src/math.zig index 027b7b0..5aede3e 100644 --- a/src/math.zig +++ b/src/math.zig @@ -159,12 +159,12 @@ pub const Frustum = struct { return !(self.top.isUnder(point) or self.right.isUnder(point) or self.bottom.isUnder(point) or self.left.isUnder(point) or self.near.isUnder(point) or self.far.isUnder(point)); } - pub fn intersectAABB(self: *const Frustum, aabb: AABB) bool { + fn intersectAABBInternal(self: *const Frustum, aabb: AABB, comptime skip_near: bool) bool { var outside_top_plane = true; var outside_bottom_plane = true; var outside_left_plane = true; var outside_right_plane = true; - var outside_near_plane = true; + var outside_near_plane = !skip_near; var outside_far_plane = true; inline for (box_corners) |corner| { const p = aabb.origin.add(aabb.extents.mul(corner)); @@ -173,12 +173,22 @@ pub const Frustum = struct { outside_bottom_plane = outside_bottom_plane and self.bottom.isUnder(p); outside_left_plane = outside_left_plane and self.left.isUnder(p); outside_right_plane = outside_right_plane and self.right.isUnder(p); - outside_near_plane = outside_near_plane and self.near.isUnder(p); + if (!skip_near) { + outside_near_plane = outside_near_plane and self.near.isUnder(p); + } outside_far_plane = outside_far_plane and self.far.isUnder(p); } return !(outside_left_plane or outside_right_plane or outside_bottom_plane or outside_top_plane or outside_near_plane or outside_far_plane); } + + pub fn intersectAABB(self: *const Frustum, aabb: AABB) bool { + return self.intersectAABBInternal(aabb, false); + } + + pub fn intersectAABBSkipNear(self: *const Frustum, aabb: AABB) bool { + return self.intersectAABBInternal(aabb, true); + } }; pub fn checkAABBIntersectionNDC(aabb: *const AABB, mvp: *const Mat4) bool {