Use variance shadow maps, tweak CSM splits

This commit is contained in:
sergeypdev 2024-09-08 22:16:12 +04:00
parent e122804766
commit 743e3293bd
6 changed files with 296 additions and 86 deletions

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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,15 +294,29 @@ 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);
// 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);
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 (blend != cmd.key.blend) {
if (cmd.key.blend < blend) {
std.debug.print("WRONG SORTING!\n", .{});
@panic("");
} else {
blend = cmd.key.blend;
}
if (alpha and cmd.key.transparent == 0) {
std.log.err("WRONG SORTING!\n", .{});
}
}
}
@ -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) {
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;
std.log.debug("opaque: {}\n", .{rendered_opaque_count});
switched_to_alpha_blend = true;
},
.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) {
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));
// 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});
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);
}
}
// Debug stuff
{
if (true) {
gl.polygonMode(gl.FRONT_AND_BACK, gl.LINE);
defer gl.polygonMode(gl.FRONT_AND_BACK, gl.FILL);
gl.lineWidth(4);

View File

@ -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,
},
});
}