diff --git a/assets/shaders/mesh.glsl b/assets/shaders/mesh.glsl index bba4954..b8cf45e 100644 --- a/assets/shaders/mesh.glsl +++ b/assets/shaders/mesh.glsl @@ -10,8 +10,8 @@ #define PI 3.1415926535897932384626433832795 #define CSM_SPLITS 4 -layout(location = 16, bindless_sampler) uniform sampler2DArrayShadow shadow_maps; -layout(location = 17, bindless_sampler) uniform samplerCubeArrayShadow cube_shadow_maps; +layout(location = 16, bindless_sampler) uniform sampler2DArray shadow_maps; +layout(location = 17, bindless_sampler) uniform samplerCubeArray cube_shadow_maps; layout(location = 22, bindless_sampler) uniform sampler2D brdfLut; @@ -104,7 +104,7 @@ int getCSMSplit(int lightIdx, float depth) { // int totalSplits = getCSMSplitCount(lightIdx); for (int i = 0; i < CSM_SPLITS; i++) { - if (depth > lights[lightIdx].csm_split_points[i]) { + if (depth >= lights[lightIdx].csm_split_points[i]) { return i; } } @@ -170,6 +170,41 @@ const vec3 csm_split_colors[4] = vec3[]( float map(float value, float min1, float max1, float min2, float max2) { return min2 + (value - min1) * (max2 - min2) / (max1 - min1); } +float linestep(float min, float max, float v) { + return clamp((v - min) / (max - min), 0, 1); +} +float ReduceLightBleeding(float p_max, float amount) { + return linestep(amount, 1, p_max); +} +float g_min_variance = 0.00001; +float ChebyshevUpperBound(vec2 moments, float t) { + // // One-tailed inequality valid if t > Moments.x + float p = t <= moments.x ? 1.0 : 0.0; + // // Compute variance. + float variance = moments.y - (moments.x * moments.x); + // // Compute probabilistic upper bound. + variance = max(variance, g_min_variance); + float d = t - moments.x; + float p_max = variance / (variance + d * d); + return max(p, ReduceLightBleeding(p_max, 0.6)); +} + +float weight(float t, float log2radius, float gamma) { + return exp(-gamma*pow(log2radius-t,2.0f)); +} + +vec2 sampleDirectShadowBlurred(vec3 uv, float radius, float gamma) { + vec2 pix = vec2(0); + float norm = 0; + // weighted integration over mipmap levels + for(float i = 0; i < 10; i += 0.5) { + float k = weight(i, log2(radius), gamma); + pix += k*textureLod(shadow_maps, uv, i).xy; + norm += k; + } + // nomalize, and a bit of brigtness hacking + return pix/norm; +} vec3 microfacetModel(int matIdx, int light_idx, vec3 P, vec3 N) { // Light light = lights[light_idx]; @@ -222,7 +257,7 @@ vec3 microfacetModel(int matIdx, int light_idx, vec3 P, vec3 N) { float sum = 0; - sum += texture(cube_shadow_maps, vec4(normalize(texcoord.xyz), texcoord.w), mapped_depth - constant_bias); + // sum += texture(cube_shadow_maps, vec4(normalize(texcoord.xyz), texcoord.w), mapped_depth - constant_bias); // for (float y = -1.5; y <= 1.5; y += 1) { // for (float x = -1.5; x <= 1.5; x += 1) { // // sum += texture(cube_shadow_maps, vec4(normalize(texcoord.xyz + vec3(x, y, z) * shadow_map_texel_size.x), texcoord.w), mapped_depth - constant_bias); @@ -241,21 +276,39 @@ vec3 microfacetModel(int matIdx, int light_idx, vec3 P, vec3 N) { // shadow_pos.xy = (lights[light_idx].view_proj_mats[csm_split_idx] * (vec4(VertexOut.wPos, 1.0) + shadow_offset)).xy; shadow_pos /= shadow_pos.w; shadow_pos.xy = shadow_pos.xy * 0.5 + 0.5; // [-1, 1] to [0, 1] - shadow_pos.z = min(shadow_pos.z, 1); - shadow_pos.z -= constant_bias; + // shadow_pos.z = min(shadow_pos.z, 1); + // shadow_pos.z -= constant_bias; vec4 texcoord; texcoord.xyw = shadow_pos.xyz; // sampler2DArrayShadow strange texcoord mapping texcoord.z = shadow_map_idx + csm_split_idx; + + // vec4 xs = textureGather(shadow_maps, texcoord.xyz, 0); + // vec4 ys = textureGather(shadow_maps, texcoord.xyz, 1); + + // vec2 moments = vec2( + // xs.x + xs.y + xs.z + xs.w, + // ys.x + ys.y + ys.z + ys.w + // ); + + // for (int i = 0; i < 4; i++) { + // moments += texture(shadow_maps, vec3(texcoord.xy + poissonDisk[i] * shadow_map_texel_size, texcoord.z)).xy; + // } + // moments /= 4.0; + float sum = 0; + + // moments = sampleDirectShadowBlurred(texcoord.xyz, shadow_pos.z * 10, 0.5); + for (float y = -1.5; y <= 1.5; y += 1) { for (float x = -1.5; x <= 1.5; x += 1) { - sum += texture(shadow_maps, vec4(texcoord.xy + vec2(x, y) * shadow_map_texel_size, texcoord.zw)); + vec2 moments = texture(shadow_maps, vec3(texcoord.xy + vec2(x, y) * shadow_map_texel_size, texcoord.z)).xy; + sum += ChebyshevUpperBound(moments, shadow_pos.z); } } - shadow_mult = sum / 16.0; + shadow_mult = sum / 16.0;; } shadow_mult = clamp(shadow_mult, 0.0, 1.0); diff --git a/assets/shaders/shadow.glsl b/assets/shaders/shadow.glsl index 55c0f1b..085e35b 100644 --- a/assets/shaders/shadow.glsl +++ b/assets/shaders/shadow.glsl @@ -20,8 +20,18 @@ void main() { #if FRAGMENT_SHADER +out vec4 FragColor; void main() { + // Store moments for VSM + float clamped_depth = clamp(gl_FragCoord.z, 0.0, 1.0); + float dx = dFdx(clamped_depth); + float dy = dFdy(clamped_depth); + vec2 moments; + moments.x = clamped_depth; + moments.y = clamped_depth * clamped_depth + 0.25 * (dx * dx + dy * dy); + moments.y = clamp(moments.y, 0.0, 1.0); + FragColor = vec4(moments.x, moments.y, 0.0, 1.0); } diff --git a/assets/shaders/z_prepass.glsl b/assets/shaders/z_prepass.glsl index 29f260a..b690c0f 100644 --- a/assets/shaders/z_prepass.glsl +++ b/assets/shaders/z_prepass.glsl @@ -33,10 +33,13 @@ void main() { out vec4 FragColor; void main() { +#if BLEND_MODE == 1 + // Alpha Mask material int matIdx = draw_data[DrawID].materialIdx; if (getAlbedo(matIdx).a < 0.5) { discard; } +#endif } #endif diff --git a/src/AssetManager.zig b/src/AssetManager.zig index 29a0057..bbc3578 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -1343,7 +1343,7 @@ test "ShaderTokenizer" { var token = tokenizer.next(); while (token.type != .End) : (token = tokenizer.next()) { - try std.io.getStdErr().writer().print("{} \"{s}\"\n", .{ token.type, token.text }); + // try std.io.getStdErr().writer().print("{} \"{s}\"\n", .{ token.type, token.text }); try testing.expect(token.type != .Unknown); } } diff --git a/src/Render.zig b/src/Render.zig index 93c2885..83af20e 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -28,8 +28,8 @@ pub const DIRECTIONAL_SHADOW_MAP_SIZE = 4096; // affects how cascades are split // 0 - uniform // 1 - exponential -// 0.5 - mix between the two -pub const CSM_EXPO_UNIFORM_FACTOR = 0.8; +// 0.5 - lerp between the two +pub const CSM_UNIFORM_EXPO_SPLIT_WEIGHT = 1; pub const Render = @This(); @@ -39,6 +39,7 @@ allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetman: *AssetManager, camera: *Camera = &default_camera, +frozen_camera: Camera = .{}, mesh_vao: gl.GLuint = 0, z_prepass_vao: gl.GLuint = 0, tripple_buffer_index: usize = MAX_FRAMES_QUEUED - 1, @@ -58,11 +59,13 @@ command_count: usize = 0, ubo_align: usize = 0, ssbo_align: usize = 0, shadow_vao: gl.GLuint = 0, +direct_shadow_depth_buffer_texture: gl.GLuint = 0, shadow_texture_array: gl.GLuint = 0, shadow_texture_handle: gl.GLuint64 = 0, shadow_framebuffer: gl.GLuint = 0, shadow_matrices_buffer: gl.GLuint = 0, shadow_matrices: CameraMatrices = .{}, +cube_shadow_depth_buffer_texture: gl.GLuint = 0, cube_shadow_texture_array: gl.GLuint = 0, cube_shadow_texture_handle: gl.GLuint64 = 0, cube_shadow_framebuffer: gl.GLuint = 0, @@ -209,17 +212,26 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm } { + // Depth texture for directional shadow maps, reused for all CSM splits + { + gl.createTextures(gl.TEXTURE_2D, 1, &render.direct_shadow_depth_buffer_texture); + checkGLError(); + std.debug.assert(render.direct_shadow_depth_buffer_texture != 0); + + gl.textureStorage2D(render.direct_shadow_depth_buffer_texture, 1, gl.DEPTH_COMPONENT16, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE); + } // 2D Shadow texture array { gl.createTextures(gl.TEXTURE_2D_ARRAY, 1, &render.shadow_texture_array); checkGLError(); std.debug.assert(render.shadow_texture_array != 0); - gl.textureStorage3D(render.shadow_texture_array, 1, gl.DEPTH_COMPONENT16, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE, CSM_SPLITS); + const mip_count = calculateMipCount(DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE); + gl.textureStorage3D(render.shadow_texture_array, @intCast(mip_count), gl.RG16, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE, CSM_SPLITS); checkGLError(); - gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE); - gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS); + // gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE); + // gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS); var border = [_]f32{1} ** 4; gl.textureParameterfv(render.shadow_texture_array, gl.TEXTURE_BORDER_COLOR, &border); @@ -227,8 +239,10 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER); gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER); - gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.textureParameteri(render.shadow_texture_array, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + // GL_TEXTURE_MAX_ANISOTROPY_EXT + gl.textureParameterf(render.shadow_texture_array, 0x84FE, 4.0); } // First shadow texture handle @@ -240,18 +254,26 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm checkGLError(); } + // Depth texture for directional shadow maps, reused for all CSM splits + { + gl.createTextures(gl.TEXTURE_2D, 1, &render.cube_shadow_depth_buffer_texture); + checkGLError(); + std.debug.assert(render.cube_shadow_depth_buffer_texture != 0); + + gl.textureStorage2D(render.cube_shadow_depth_buffer_texture, 1, gl.DEPTH_COMPONENT16, 512, 512); + } // Cube Shadow texture array { gl.createTextures(gl.TEXTURE_CUBE_MAP_ARRAY, 1, &render.cube_shadow_texture_array); checkGLError(); std.debug.assert(render.cube_shadow_texture_array != 0); - gl.textureStorage3D(render.cube_shadow_texture_array, 1, gl.DEPTH_COMPONENT16, 512, 512, MAX_LIGHTS * 6); + gl.textureStorage3D(render.cube_shadow_texture_array, 1, gl.RG16, 512, 512, MAX_LIGHTS * 6); checkGLError(); - gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE); - gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS); - gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + // gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE); + // gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_FUNC, gl.LESS); + gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); @@ -272,14 +294,28 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm gl.createFramebuffers(1, &render.shadow_framebuffer); checkGLError(); std.debug.assert(render.shadow_framebuffer != 0); - gl.namedFramebufferDrawBuffer(render.shadow_framebuffer, gl.NONE); + gl.namedFramebufferDrawBuffer(render.shadow_framebuffer, gl.FRONT_LEFT); gl.namedFramebufferReadBuffer(render.shadow_framebuffer, gl.NONE); } - gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.shadow_texture_array, 0, 0); - const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); - if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { - std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); + // Verify directional shadow framebuffer setup + { + gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.shadow_texture_array, 0, 0); + gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0); + const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); + if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { + std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); + } + } + + // Verify cube shadow framebuffer setup + { + gl.namedFramebufferTextureLayer(render.shadow_framebuffer, gl.COLOR_ATTACHMENT0, render.cube_shadow_texture_array, 0, 0); + gl.namedFramebufferTexture(render.shadow_framebuffer, gl.DEPTH_ATTACHMENT, render.direct_shadow_depth_buffer_texture, 0); + const check_fbo_status = gl.checkNamedFramebufferStatus(render.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); + if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { + std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); + } } gl.createBuffers(1, &render.shadow_matrices_buffer); @@ -381,11 +417,15 @@ fn getMipSize(width: i32, height: i32, mip_level: usize) Vec2_i32 { return Vec2_i32.new(mip_width, mip_height); } -fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void { - const mip_count = 1 + @as( +fn calculateMipCount(width: c_int, height: c_int) usize { + return 1 + @as( u32, @intFromFloat(@log2(@as(f32, @floatFromInt(@max(width, height))))), ); +} + +fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void { + const mip_count = calculateMipCount(width, height); gl.bindTexture(gl.TEXTURE_2D, self.screen_color_texture); for (0..mip_count) |mip_level| { @@ -494,6 +534,33 @@ const DebugDrawer = struct { return Self{ .allocator = allocator }; } + pub fn drawNDCBox(self: *Self, transform: Mat4) void { + var new_lines: [12][2][3]f32 = undefined; + + var corners: [8][3]f32 = undefined; + for (math.ndc_box_corners, 0..) |corner, corner_idx| { + const p = transform.mulByVec4(corner.toVec4(1)); + corners[corner_idx][0] = p.x() / p.w(); + corners[corner_idx][1] = p.y() / p.w(); + corners[corner_idx][2] = p.z() / p.w(); + } + + new_lines[0] = .{ corners[0], corners[1] }; + new_lines[1] = .{ corners[2], corners[3] }; + new_lines[2] = .{ corners[0], corners[2] }; + new_lines[3] = .{ corners[1], corners[3] }; + new_lines[4] = .{ corners[4], corners[5] }; + new_lines[5] = .{ corners[6], corners[7] }; + new_lines[6] = .{ corners[4], corners[6] }; + new_lines[7] = .{ corners[5], corners[7] }; + new_lines[8] = .{ corners[0], corners[4] }; + new_lines[9] = .{ corners[2], corners[6] }; + new_lines[10] = .{ corners[1], corners[5] }; + new_lines[11] = .{ corners[3], corners[7] }; + + self.line_list.appendSlice(self.allocator, &new_lines) catch {}; + } + pub fn drawAABB(self: *Self, in_aabb: AABB) void { var aabb = in_aabb; var new_lines: [12][2][3]f32 = undefined; @@ -533,7 +600,9 @@ pub fn draw(self: *Render, cmd: DrawCommand) void { const mesh = self.assetman.resolveMesh(cmd.mesh); const aabb = mesh.aabb.transformBy(cmd.transform); - self.debug_drawer.drawAABB(aabb); + if (false) { + self.debug_drawer.drawAABB(aabb); + } const material: Material = if (cmd.material_override) |mat| mat else mesh.material; const view_origin = self.camera.view_mat.extractTranslation(); const distance = view_origin.distance(aabb.min.add(aabb.max).scale(0.5)); @@ -542,13 +611,14 @@ pub fn draw(self: *Render, cmd: DrawCommand) void { const max_value = @as(f32, @floatFromInt(std.math.maxInt(u30))); const quantized_dist: u30 = @intFromFloat(alpha * max_value); const inv_quantized_dist: u30 = @intFromFloat((1.0 - alpha) * max_value); + _ = inv_quantized_dist; // autofix const key = DrawCommandKey{ .blend = switch (material.blend_mode) { .Opaque => 0, .AlphaMask => 1, .AlphaBlend => 2, }, - .distance = if (material.blend_mode == .AlphaBlend) inv_quantized_dist else quantized_dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front + .distance = if (material.blend_mode == .AlphaBlend) quantized_dist else quantized_dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front // .mesh = @intCast(cmd.mesh.id % std.math.maxInt(u16)), }; self.command_buffer[self.command_count].key = key; @@ -556,9 +626,10 @@ pub fn draw(self: *Render, cmd: DrawCommand) void { } // Multipass radix sort for u32 -fn sortCommands(self: *Render, in_cmds: []DrawCommand) void { +fn sortCommands(temp_allocator: std.mem.Allocator, in_cmds: []DrawCommand) void { var cmds = in_cmds; - var aux = self.frame_arena.alloc(DrawCommand, cmds.len) catch @panic("OOM"); + var aux = temp_allocator.alloc(DrawCommand, cmds.len) catch @panic("OOM"); + defer temp_allocator.free(aux); var cnt1: [256]usize = std.mem.zeroes([256]usize); var cnt2: [256]usize = std.mem.zeroes([256]usize); @@ -566,7 +637,7 @@ fn sortCommands(self: *Render, in_cmds: []DrawCommand) void { var cnt4: [256]usize = std.mem.zeroes([256]usize); // Find counts - for (cmds) |*cmd| { + for (cmds) |cmd| { const key: u32 = @bitCast(cmd.key); cnt1[(key >> 0) & 0xFF] += 1; @@ -633,6 +704,26 @@ fn sortCommands(self: *Render, in_cmds: []DrawCommand) void { std.mem.swap([]DrawCommand, &cmds, &aux); } +test "sortCommands" { + var rand = std.rand.DefaultPrng.init(@intCast(std.time.milliTimestamp())); + var cmds = try std.testing.allocator.alloc(DrawCommand, 1024 * 128); + defer std.testing.allocator.free(cmds); + + for (0..cmds.len) |i| { + cmds[i].key = .{ .blend = std.rand.int(rand.random(), u2), .distance = std.rand.int(rand.random(), u30) }; + } + + sortCommands(std.testing.allocator, cmds); + + var cur_blend: u2 = 0; + for (cmds) |cmd| { + if (cmd.key.blend != cur_blend) { + try std.testing.expect(cmd.key.blend > cur_blend); + } + cur_blend = cmd.key.blend; + } +} + pub fn finish(self: *Render) void { const zone = tracy.initZone(@src(), .{ .name = "Render.finish" }); defer zone.deinit(); @@ -645,18 +736,19 @@ pub fn finish(self: *Render) void { const zoneSort = tracy.initZone(@src(), .{ .name = "Render.finish_sortDraws" }); defer zoneSort.deinit(); - self.sortCommands(self.command_buffer[0..self.command_count]); + sortCommands(self.frame_arena, self.command_buffer[0..self.command_count]); // Sorting validation - if (false) { - var alpha = false; + if (true) { + var blend: u2 = 0; for (self.command_buffer[0..self.command_count]) |cmd| { - if (!alpha and cmd.key.transparent == 1) { - alpha = true; - } - - if (alpha and cmd.key.transparent == 0) { - std.log.err("WRONG SORTING!\n", .{}); + if (blend != cmd.key.blend) { + if (cmd.key.blend < blend) { + std.debug.print("WRONG SORTING!\n", .{}); + @panic(""); + } else { + blend = cmd.key.blend; + } } } } @@ -713,6 +805,7 @@ pub fn finish(self: *Render) void { light.pos = dir_light.dir.toVec4(0); light.color_radius = dir_light.color.toVec4(0); gl.viewport(0, 0, DIRECTIONAL_SHADOW_MAP_SIZE, DIRECTIONAL_SHADOW_MAP_SIZE); + gl.namedFramebufferTexture(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.direct_shadow_depth_buffer_texture, 0); const camera_matrix = &self.shadow_matrices; @@ -727,25 +820,27 @@ pub fn finish(self: *Render) void { light.view_mat = view; light.params.shadow_map_idx = shadow_map_idx; light.params.csm_split_count = @floatFromInt(CSM_SPLITS); - const shadow_far = self.camera.far / 2; + const shadow_near = self.camera.near + 4; + const shadow_far = self.camera.far; - var splits: [CSM_SPLITS + 1]f32 = undefined; + var splits: [CSM_SPLITS]f32 = undefined; const splits_count_f: f32 = @floatFromInt(CSM_SPLITS); - for (0..CSM_SPLITS + 1) |split_idx| { + for (0..CSM_SPLITS) |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, shadow_far / self.camera.near, split_i_over_n); - const uniform_split = self.camera.near + split_i_over_n * (shadow_far - self.camera.near); - const split = CSM_EXPO_UNIFORM_FACTOR * expo_split + (1.0 - CSM_EXPO_UNIFORM_FACTOR) * uniform_split; + const split_i_over_n = @min(split_idx_f / splits_count_f, 1.0); + const expo_split = shadow_near * std.math.pow(f32, shadow_far / shadow_near, split_i_over_n); + const uniform_split = shadow_near + split_i_over_n * (shadow_far - shadow_near); + const split = std.math.lerp(uniform_split, expo_split, CSM_UNIFORM_EXPO_SPLIT_WEIGHT); splits[split_idx] = split; } for (0..CSM_SPLITS) |split_idx| { - const split_near = splits[split_idx]; - const split_far = splits[split_idx + 1]; + const split_near = if (split_idx == 0) self.camera.near else splits[split_idx - 1]; + const split_far = splits[split_idx]; - gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.shadow_texture_array, 0, @intCast(shadow_map_idx * CSM_SPLITS + split_idx)); + const texture_layer = shadow_map_idx * CSM_SPLITS + split_idx; + gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.COLOR_ATTACHMENT0, self.shadow_texture_array, 0, @intCast(texture_layer)); const check_fbo_status = gl.checkNamedFramebufferStatus(self.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); @@ -754,12 +849,16 @@ pub fn finish(self: *Render) void { var projection: Mat4 = undefined; { - var camera = self.camera.*; if (self.update_view_frustum) { + self.frozen_camera = self.camera.*; + } + var camera = self.frozen_camera; + { camera.near = split_near; camera.far = split_far; const inv_csm_proj = camera.projection().mul(camera.view_mat).inv(); + self.debug_drawer.drawNDCBox(inv_csm_proj); for (math.ndc_box_corners, 0..) |corner, corner_idx| { const pos4 = inv_csm_proj.mulByVec4(corner.toVec4(1)); self.world_view_frustum_corners[split_idx][corner_idx] = pos4.toVec3().scale(1.0 / pos4.w()); @@ -787,7 +886,7 @@ pub fn finish(self: *Render) void { // NOTE: Use bounding sphere instead of AABB to prevent split size changing with rotation projection = math.orthographic( - center.x() - radius - 0.0001, + center.x() - radius, center.x() + radius, center.y() - radius, center.y() + radius, @@ -824,7 +923,8 @@ pub fn finish(self: *Render) void { gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices)); checkGLError(); - gl.clear(gl.DEPTH_BUFFER_BIT); + gl.clearColor(1.0, 1.0, 1.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer); self.renderShadow(&light_frustum); @@ -834,6 +934,7 @@ pub fn finish(self: *Render) void { if (!finished_dir_lights) { finished_dir_lights = true; gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.cube_shadow).program); + gl.namedFramebufferTexture(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.cube_shadow_depth_buffer_texture, 0); } const pos = point_light.pos; @@ -852,7 +953,7 @@ pub fn finish(self: *Render) void { // For each cube face for (cube_camera_dirs, 0..) |cam_dir, face| { - gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.cube_shadow_texture_array, 0, @intCast(shadow_map_idx * 6 + face)); + gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.COLOR_ATTACHMENT0, self.cube_shadow_texture_array, 0, @intCast(shadow_map_idx * 6 + face)); const check_fbo_status = gl.checkNamedFramebufferStatus(self.shadow_framebuffer, gl.DRAW_FRAMEBUFFER); if (check_fbo_status != gl.FRAMEBUFFER_COMPLETE) { std.log.debug("Shadow Framebuffer Incomplete: {}\n", .{check_fbo_status}); @@ -877,7 +978,8 @@ pub fn finish(self: *Render) void { gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices)); checkGLError(); - gl.clear(gl.DEPTH_BUFFER_BIT); + gl.clearColor(1.0, 1.0, 1.0, 1.0); + gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT); gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer); self.renderShadow(&light_frustum); @@ -886,6 +988,7 @@ pub fn finish(self: *Render) void { } } } + gl.generateTextureMipmap(self.shadow_texture_array); // Light world space to view space for (lights_buf.data[0..lights_buf.count.*]) |*light| { @@ -914,8 +1017,6 @@ pub fn finish(self: *Render) void { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - var switched_to_alpha_blend = false; - var draw_indirect_cmds = self.frame_arena.alloc(DrawIndirectCmd, MAX_DRAW_COMMANDS) catch @panic("OOM"); var draw_cmd_data = self.frame_arena.alloc(DrawCommandData, MAX_DRAW_COMMANDS) catch @panic("OOM"); @@ -931,6 +1032,10 @@ pub fn finish(self: *Render) void { var rendered_count: usize = 0; var rendered_opaque_count: usize = 0; + var rendered_alpha_mask_count: usize = 0; + var rendered_alpha_blend_count: usize = 0; + + var current_blend: u8 = 0; // Prepare indirect draw commands { @@ -943,7 +1048,7 @@ pub fn finish(self: *Render) void { var material_map = std.StringHashMap(i32).init(self.frame_arena); var materials_count: usize = 0; - for (self.command_buffer[0..self.command_count]) |*cmd| { + for (self.command_buffer[0..self.command_count]) |cmd| { const mesh = self.assetman.resolveMesh(cmd.mesh); const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max); @@ -954,10 +1059,23 @@ pub fn finish(self: *Render) void { const material: Material = if (cmd.material_override) |mat| mat else mesh.material; // Opaque objects are drawn, start rendering alpha blended objects - if (material.blend_mode == .AlphaBlend and !switched_to_alpha_blend) { - rendered_opaque_count = rendered_count; - std.log.debug("opaque: {}\n", .{rendered_opaque_count}); - switched_to_alpha_blend = true; + if (@intFromEnum(material.blend_mode) != current_blend) { + if (@intFromEnum(material.blend_mode) < current_blend) { + std.debug.print("mat blend {}, cur blend {}, cmd blend {}, render count {}\n", .{ material, current_blend, cmd, rendered_count }); + // std.debug.assert(false); + } + // std.debug.assert(@intFromEnum(material.blend_mode) > @intFromEnum(current_blend)); + + switch (material.blend_mode) { + .AlphaMask => { + rendered_opaque_count = rendered_count; + }, + .AlphaBlend => { + rendered_alpha_mask_count = rendered_count - rendered_opaque_count; + }, + else => {}, + } + current_blend = @intFromEnum(material.blend_mode); } const material_bytes = std.mem.asBytes(&material); @@ -987,8 +1105,31 @@ pub fn finish(self: *Render) void { } } - if (rendered_opaque_count == 0) { - rendered_opaque_count = rendered_count; + switch (current_blend) { + 0 => { + rendered_opaque_count = rendered_count; + }, + 1 => { + rendered_alpha_mask_count = rendered_count - rendered_opaque_count; + }, + 2 => { + rendered_alpha_blend_count = rendered_count - (rendered_alpha_mask_count + rendered_opaque_count); + }, + else => unreachable, + } + + const DrawCMDSlice = struct { + start: usize, + count: usize, + }; + var blend_mode_cmd_slices: [3]DrawCMDSlice = undefined; + { + // Opaque + blend_mode_cmd_slices[0] = .{ .start = 0, .count = rendered_opaque_count }; + // Alpha mask + blend_mode_cmd_slices[1] = .{ .start = rendered_opaque_count, .count = rendered_alpha_mask_count }; + // Alpha blend + blend_mode_cmd_slices[2] = .{ .start = rendered_opaque_count + rendered_alpha_mask_count, .count = rendered_alpha_blend_count }; } { @@ -1017,7 +1158,6 @@ pub fn finish(self: *Render) void { // Z Prepass { - gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program); gl.bindVertexArray(self.z_prepass_vao); gl.depthFunc(gl.LESS); @@ -1028,13 +1168,18 @@ pub fn finish(self: *Render) void { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); checkGLError(); - gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd)); - checkGLError(); + // Opaque and Alpha Mask + for (0..2) |blend_mode| { + gl.useProgram(self.assetman.resolveShaderProgramWithDefines(a.ShaderPrograms.shaders.z_prepass, &.{.{ .key = "BLEND_MODE", .value = if (blend_mode == 0) "0" else "1" }}).program); + + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, @ptrFromInt(@sizeOf(DrawIndirectCmd) * blend_mode_cmd_slices[blend_mode].start), @intCast(blend_mode_cmd_slices[blend_mode].count), @sizeOf(DrawIndirectCmd)); + checkGLError(); + } } // Main pass { - gl.useProgram(self.assetman.resolveShaderProgramWithDefines(a.ShaderPrograms.shaders.mesh, &.{.{ .key = "OVERRIDE_COLOR", .value = "1" }}).program); + gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program); gl.bindVertexArray(self.mesh_vao); gl.depthFunc(gl.EQUAL); defer gl.depthFunc(gl.LEQUAL); @@ -1054,28 +1199,27 @@ pub fn finish(self: *Render) void { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); checkGLError(); - gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count), @sizeOf(DrawIndirectCmd)); + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_opaque_count + rendered_alpha_mask_count), @sizeOf(DrawIndirectCmd)); checkGLError(); - } - // Alpha Pass - const blended_draws_count = rendered_count - rendered_opaque_count; - if (blended_draws_count > 0) { - std.log.debug("blended: {}\n", .{blended_draws_count}); - gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - gl.depthFunc(gl.LEQUAL); - gl.depthMask(gl.FALSE); + // Alpha Pass + if (rendered_alpha_blend_count > 0) { + std.log.debug("blended: {}\n", .{rendered_alpha_blend_count}); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(gl.FALSE); - gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, @ptrFromInt(@sizeOf(DrawIndirectCmd) * rendered_opaque_count), @intCast(blended_draws_count), @sizeOf(DrawIndirectCmd)); + gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, @ptrFromInt(@sizeOf(DrawIndirectCmd) * blend_mode_cmd_slices[2].start), @intCast(blend_mode_cmd_slices[2].count), @sizeOf(DrawIndirectCmd)); - gl.disable(gl.BLEND); - gl.depthFunc(gl.LEQUAL); - gl.depthMask(gl.TRUE); + gl.disable(gl.BLEND); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(gl.TRUE); + } } // Debug stuff - { + if (true) { gl.polygonMode(gl.FRONT_AND_BACK, gl.LINE); defer gl.polygonMode(gl.FRONT_AND_BACK, gl.FILL); gl.lineWidth(4); diff --git a/src/game.zig b/src/game.zig index 834449b..ffe6636 100644 --- a/src/game.zig +++ b/src/game.zig @@ -192,7 +192,7 @@ export fn game_init(global_allocator: *std.mem.Allocator) void { _ = globals.g_mem.world.addEntity(.{ .flags = .{ .dir_light = true, .rotate = true }, .transform = .{ .rot = Quat.fromEulerAngles(Vec3.new(70, 0, 0)) }, - .light = .{ .color_intensity = Vec4.new(1.00, 0.805, 0.8, 790 * 0.02) }, + .light = .{ .color_intensity = Vec4.new(2.00, 0.805, 0.70, 30) }, .rotate = .{ .axis = Vec3.up(), .rate = -10 }, }); @@ -453,7 +453,7 @@ export fn game_update() bool { if (!ent.data.flags.active) continue; if (ent.data.flags.rotate) { - ent.data.transform.rotate(ent.data.rotate.axis, ent.data.rotate.rate * gmem.delta_time); + // ent.data.transform.rotate(ent.data.rotate.axis, ent.data.rotate.rate * gmem.delta_time); } } } @@ -505,7 +505,7 @@ export fn game_update() bool { gmem.render.drawLight(.{ .directional = .{ .dir = dir4.toVec3(), - .color = color.add(Vec3.new(100, 0, 0)), + .color = color, }, }); }