From 8e9cb3fa5b8d7017b0947f8114a0504642b2c92d Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Sat, 24 Aug 2024 23:26:01 +0400 Subject: [PATCH] Start implementing alpha mask instead of alpha blend for everything, add debug bounds rendering --- assets/shaders/debug.glsl | 29 +++++++ assets/shaders/debug.prog | 5 ++ assets/shaders/mesh.glsl | 7 +- assets/shaders/shadow.glsl | 1 - assets/shaders/z_prepass.glsl | 53 +++++++++++-- src/AssetManager.zig | 30 +++++++ src/Render.zig | 144 ++++++++++++++++++++++++++++++---- src/formats.zig | 4 +- src/math.zig | 16 ++-- tools/asset_compiler.zig | 6 +- 10 files changed, 257 insertions(+), 38 deletions(-) create mode 100644 assets/shaders/debug.glsl create mode 100644 assets/shaders/debug.prog diff --git a/assets/shaders/debug.glsl b/assets/shaders/debug.glsl new file mode 100644 index 0000000..79e334d --- /dev/null +++ b/assets/shaders/debug.glsl @@ -0,0 +1,29 @@ +// UBOs +layout(std140, binding = 0) uniform Matrices { + mat4 projection; + mat4 view; +}; + +layout(location = 2) uniform vec3 color; + +// Input, output blocks + +#if VERTEX_SHADER + +layout(location = 0) in vec3 aPos; + +void main() { + gl_Position = projection * view * vec4(aPos.xyz, 1.0); +} +#endif // VERTEX_SHADER + +#if FRAGMENT_SHADER + +out vec4 FragColor; + +void main() { + FragColor = vec4(vec3(1.0), 1.0f); +} + + +#endif // FRAGMNET_SHADER diff --git a/assets/shaders/debug.prog b/assets/shaders/debug.prog new file mode 100644 index 0000000..d97dba7 --- /dev/null +++ b/assets/shaders/debug.prog @@ -0,0 +1,5 @@ +{ + "shader": "debug.glsl", + "vertex": true, + "fragment": true +} diff --git a/assets/shaders/mesh.glsl b/assets/shaders/mesh.glsl index 34cb3bf..e4b32ff 100644 --- a/assets/shaders/mesh.glsl +++ b/assets/shaders/mesh.glsl @@ -341,6 +341,10 @@ vec3 ibl(int matIdx, vec3 N, vec3 V) { void main() { int matIdx = draw_data[DrawID].materialIdx; + if (getAlbedo(matIdx).a < 0.5) { + FragColor = vec4(0); + return; + } sampler2D normal_map = materials[matIdx].normal_map; vec2 normal_map_uv_scale = materials[matIdx].normal_map_uv_scale; @@ -354,7 +358,8 @@ void main() { vec3 finalColor = vec3(0); int n_lights = clamp(int(lights_count), 0, MAX_POINT_LIGHTS); - for (int i = 0; i < n_lights; i++) { + for (int i = 0; i < MAX_POINT_LIGHTS; i++) { + if (i > lights_count) break; finalColor += microfacetModel(matIdx, i, VertexOut.vPos, N); } diff --git a/assets/shaders/shadow.glsl b/assets/shaders/shadow.glsl index 93ea659..32be33d 100644 --- a/assets/shaders/shadow.glsl +++ b/assets/shaders/shadow.glsl @@ -25,7 +25,6 @@ void main() { void main() { - //gl_FragDepth = gl_FragCoord.z / gl_FragCoord.w; } diff --git a/assets/shaders/z_prepass.glsl b/assets/shaders/z_prepass.glsl index 6649154..66e14ce 100644 --- a/assets/shaders/z_prepass.glsl +++ b/assets/shaders/z_prepass.glsl @@ -1,3 +1,4 @@ +#extension GL_ARB_bindless_texture : enable struct DrawCmdData { mat4 transform; @@ -15,21 +16,63 @@ layout(std430, binding = 3) readonly buffer DrawCmdDatas { DrawCmdData draw_data[]; }; +VERTEX_EXPORT flat uint DrawID; + +VERTEX_EXPORT VertexData { + vec2 uv; +} VertexOut; + #if VERTEX_SHADER layout(location = 0) in vec3 aPos; +layout(location = 2) in vec2 aUV; void main() { - mat4 model = draw_data[gl_BaseInstance + gl_InstanceID].transform; - mat4 viewModel = view * model; - vec4 vPos = viewModel * vec4(aPos.xyz, 1.0); - gl_Position = projection * vPos; + DrawID = gl_BaseInstance + gl_InstanceID; + mat4 model = draw_data[DrawID].transform; + mat4 viewModel = view * model; + vec4 vPos = viewModel * vec4(aPos.xyz, 1.0); + gl_Position = projection * vPos; + VertexOut.uv = aUV; } #endif #if FRAGMENT_SHADER -void main() {} +struct Material { + vec4 albedo; + sampler2D albedo_map; + vec2 albedo_map_uv_scale; + sampler2D normal_map; + vec2 normal_map_uv_scale; + float metallic; + sampler2D metallic_map; + vec2 metallic_map_uv_scale; + float roughness; + sampler2D roughness_map; + vec2 roughness_map_uv_scale; + vec3 emission; + sampler2D emission_map; + vec2 emission_map_uv_scale; +}; + +layout(std430, binding = 2) readonly buffer Materials { + uint materials_count; + Material materials[]; +}; + +vec4 getAlbedo(int materialIdx) { + return textureSize(materials[materialIdx].albedo_map, 0) == ivec2(0) ? vec4(pow(materials[materialIdx].albedo.rgb, vec3(2.2)), materials[materialIdx].albedo.a) : texture(materials[materialIdx].albedo_map, VertexOut.uv * materials[materialIdx].albedo_map_uv_scale); +} + +out vec4 FragColor; + +void main() { + int matIdx = draw_data[DrawID].materialIdx; + if (getAlbedo(matIdx).a < 0.5) { + discard; + } +} #endif diff --git a/src/AssetManager.zig b/src/AssetManager.zig index cb49631..dc5a2b9 100644 --- a/src/AssetManager.zig +++ b/src/AssetManager.zig @@ -24,6 +24,7 @@ const checkGLError = @import("Render.zig").checkGLError; const BuddyAllocator = @import("BuddyAllocator.zig"); const Vec2 = @import("zalgebra").Vec2; const Vec3 = @import("zalgebra").Vec3; +const Mat4 = @import("zalgebra").Mat4; const sdl = @import("sdl.zig"); const tracy = @import("tracy"); @@ -689,6 +690,35 @@ const LoadedScene = struct { pub const AABB = struct { min: Vec3 = Vec3.zero(), max: Vec3 = Vec3.zero(), + + pub fn distance(self: *const AABB, point: Vec3) f32 { + const center = self.min.add(self.max).scale(0.5); + const extent = self.max.sub(self.min).scale(0.5); + + var center_to_point = point.sub(center); + center_to_point.data = @abs(center_to_point.data); + var d = center_to_point.sub(extent); + d.data = @max(d.data, @as(@Vector(3, f32), @splat(0.0))); + + const sq_dist_to_side = d.dot(d); + + if (std.math.approxEqAbs(f32, sq_dist_to_side, 0.0, 0.0001)) { + const diff = point.sub(center); + return diff.dot(diff); + } + + return sq_dist_to_side; + } + + pub fn transformBy(self: *const AABB, matrix: Mat4) AABB { + var center = self.min.add(self.max).scale(0.5).toVec4(1.0); + var extent = self.max.sub(self.min).scale(0.5).toVec4(0.0); + + center = matrix.mulByVec4(center); + extent = matrix.mulByVec4(extent); + + return AABB{ .min = center.sub(extent).toVec3(), .max = center.add(extent).toVec3() }; + } }; pub const BufferSlice = struct { diff --git a/src/Render.zig b/src/Render.zig index fcfe681..dbd0cef 100644 --- a/src/Render.zig +++ b/src/Render.zig @@ -7,6 +7,7 @@ const globals = @import("globals.zig"); pub const Material = @import("formats.zig").Material; const math = @import("math.zig"); const formats = @import("formats.zig"); +const AABB = AssetManager.AABB; // TODO: move AABB out of formats pls const tracy = @import("tracy"); const za = @import("zalgebra"); @@ -39,6 +40,7 @@ frame_arena: std.mem.Allocator, assetman: *AssetManager, camera: *Camera = &default_camera, mesh_vao: gl.GLuint = 0, +z_prepass_vao: gl.GLuint = 0, tripple_buffer_index: usize = MAX_FRAMES_QUEUED - 1, gl_fences: [MAX_FRAMES_QUEUED]?gl.GLsync = [_]?gl.GLsync{null} ** MAX_FRAMES_QUEUED, camera_ubo: gl.GLuint = 0, @@ -85,6 +87,10 @@ camera_view_proj: Mat4 = Mat4.identity(), world_camera_frustum: math.Frustum = .{}, world_view_frustum_corners: [CSM_SPLITS][8]Vec3 = undefined, +// Debug Draw +debug_drawer: DebugDrawer = undefined, +debug_lines_vao: gl.GLuint = 0, + pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetman: *AssetManager) Render { var render = Render{ .allocator = allocator, @@ -137,6 +143,38 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm gl.vertexArrayAttribFormat(vao, Attrib.UV.value(), 2, gl.FLOAT, gl.FALSE, 0); } + { + // Z Prepass Mesh VAO + var vao: gl.GLuint = 0; + gl.createVertexArrays(1, &vao); + std.debug.assert(vao != 0); + render.z_prepass_vao = vao; + + // positions + // gl.vertexArrayVertexBuffer(vao, 0, vertices, 0, @sizeOf(formats.Vector3)); + gl.enableVertexArrayAttrib(vao, Attrib.Position.value()); + gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0); + gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0); + + // uvs + gl.enableVertexArrayAttrib(vao, Attrib.UV.value()); + gl.vertexArrayAttribBinding(vao, Attrib.UV.value(), 2); + gl.vertexArrayAttribFormat(vao, Attrib.UV.value(), 2, gl.FLOAT, gl.FALSE, 0); + } + + { + // Debug Lines VAO + var vao: gl.GLuint = 0; + gl.createVertexArrays(1, &vao); + std.debug.assert(vao != 0); + render.debug_lines_vao = vao; + + // positions + gl.enableVertexArrayAttrib(vao, Attrib.Position.value()); + gl.vertexArrayAttribBinding(vao, Attrib.Position.value(), 0); + gl.vertexArrayAttribFormat(vao, Attrib.Position.value(), 3, gl.FLOAT, gl.FALSE, 0); + } + const PERSISTENT_BUFFER_FLAGS: gl.GLbitfield = gl.MAP_PERSISTENT_BIT | gl.MAP_WRITE_BIT | gl.MAP_COHERENT_BIT; // Camera matrices ubo @@ -372,6 +410,8 @@ pub fn begin(self: *Render) void { self.light_count = 0; self.tripple_buffer_index = (self.tripple_buffer_index + 1) % MAX_FRAMES_QUEUED; + self.debug_drawer = DebugDrawer.init(self.frame_arena); + gl.enable(gl.CULL_FACE); gl.enable(gl.DEPTH_TEST); if (self.gl_fences[self.tripple_buffer_index]) |fence| { @@ -429,9 +469,8 @@ pub const LightCommand = union(LightKind) { }; const DrawCommandKey = packed struct { - mesh: u16 = 0, - distance: u15 = 0, - transparent: u1 = 0, + distance: u30 = 0, + blend: u2 = 0, }; pub fn drawLight(self: *Render, cmd: LightCommand) void { @@ -439,18 +478,78 @@ pub fn drawLight(self: *Render, cmd: LightCommand) void { self.light_count += 1; } +fn invLerp(aa: f32, b: f32, v: f32) f32 { + return (v - aa) / (b - aa); +} + +const DebugDrawer = struct { + const Self = @This(); + + allocator: std.mem.Allocator, + + // Wasting frame arena memory here, maybe redo this + line_list: std.ArrayListUnmanaged([2][3]f32) = .{}, + + pub fn init(allocator: std.mem.Allocator) Self { + return Self{ .allocator = allocator }; + } + + pub fn drawAABB(self: *Self, in_aabb: AABB) void { + var aabb = in_aabb; + var new_lines: [12][2][3]f32 = undefined; + + var min: [3]f32 = .{ aabb.min.x(), aabb.min.y(), aabb.min.z() }; + var max: [3]f32 = .{ aabb.max.x(), aabb.max.y(), aabb.max.z() }; + + for (0..2) |i| { + new_lines[i * 6 + 0] = .{ min, .{ max[0], min[1], min[2] } }; + new_lines[i * 6 + 1] = .{ min, .{ min[0], max[1], min[2] } }; + new_lines[i * 6 + 2] = .{ min, .{ min[0], min[1], max[2] } }; + + new_lines[i * 6 + 3] = .{ max, .{ min[0], max[1], max[2] } }; + new_lines[i * 6 + 4] = .{ max, .{ max[0], min[1], max[2] } }; + new_lines[i * 6 + 5] = .{ max, .{ max[0], max[1], min[2] } }; + + std.mem.swap(f32, &min[1], &max[1]); + } + + self.line_list.appendSlice(self.allocator, &new_lines) catch {}; + } + + pub fn uploadLinesBuffer(self: *const Self) gl.GLuint { + var buf: gl.GLuint = 0; + gl.createBuffers(1, &buf); + std.debug.assert(buf != 0); + + gl.namedBufferStorage(buf, @intCast(@sizeOf(f32) * 3 * 2 * self.line_list.items.len), self.line_list.items.ptr, 0); + + return buf; + } +}; + pub fn draw(self: *Render, cmd: DrawCommand) void { self.command_buffer[self.command_count] = cmd; // TODO: don't load the whole mesh here const mesh = self.assetman.resolveMesh(cmd.mesh); + const aabb = mesh.aabb.transformBy(cmd.transform); + + 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 max_value = @as(f32, @floatFromInt(std.math.maxInt(u15))); - const dist: u15 = @intFromFloat(std.math.clamp(view_origin.distance(cmd.transform.extractTranslation()) / max_value, 0.0, max_value)); + const distance = view_origin.distance(aabb.min.add(aabb.max).scale(0.5)); + const alpha = std.math.clamp(invLerp(self.camera.near, self.camera.far, distance), 0.0, 1.0); + + 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); const key = DrawCommandKey{ - .transparent = if (material.blend_mode == .AlphaBlend) 1 else 0, - .distance = if (material.blend_mode == .AlphaBlend) dist else dist, // TODO: calculate distance. Opaque should be front to back, transparent back to front - .mesh = @intCast(cmd.mesh.id % std.math.maxInt(u16)), + .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 + // .mesh = @intCast(cmd.mesh.id % std.math.maxInt(u16)), }; self.command_buffer[self.command_count].key = key; self.command_count += 1; @@ -846,11 +945,11 @@ pub fn finish(self: *Render) void { var materials_count: usize = 0; 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); + const aabb = math.AABB.fromMinMax(mesh.aabb.min, mesh.aabb.max); - // if (!self.world_camera_frustum.intersectAABB(aabb.transform(cmd.transform))) { - // continue; - // } + if (!self.world_camera_frustum.intersectAABB(aabb.transform(cmd.transform))) { + continue; + } const material: Material = if (cmd.material_override) |mat| mat else mesh.material; @@ -919,11 +1018,13 @@ pub fn finish(self: *Render) void { // Z Prepass { gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program); - gl.bindVertexArray(self.shadow_vao); + gl.bindVertexArray(self.z_prepass_vao); gl.depthFunc(gl.LESS); self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value()); checkGLError(); + self.assetman.vertex_heap.uvs.bind(Render.Attrib.UV.value()); + checkGLError(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); checkGLError(); @@ -936,6 +1037,7 @@ pub fn finish(self: *Render) void { gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program); gl.bindVertexArray(self.mesh_vao); gl.depthFunc(gl.EQUAL); + defer gl.depthFunc(gl.LEQUAL); gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.EnvBRDF.value(), self.assetman.resolveTexture(a.Textures.@"ibl_brdf_lut.norm").handle); gl.GL_ARB_bindless_texture.uniformHandleui64ARB(Uniform.ShadowMap2D.value(), self.shadow_texture_handle); @@ -978,8 +1080,20 @@ pub fn finish(self: *Render) void { defer gl.polygonMode(gl.FRONT_AND_BACK, gl.FILL); gl.lineWidth(4); + const debug_lines_buffer = self.debug_drawer.uploadLinesBuffer(); + defer gl.deleteBuffers(1, &debug_lines_buffer); + + gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.debug).program); + gl.bindVertexArray(self.debug_lines_vao); + + gl.uniform3f(Uniform.Color.value(), 1.0, 1.0, 1.0); + + gl.bindVertexBuffer(Render.Attrib.Position.value(), debug_lines_buffer, 0, @intCast(@sizeOf(f32) * 3)); + + gl.drawArrays(gl.LINES, 0, @intCast(self.debug_drawer.line_list.items.len * 2)); + // Frustum debug stuff, drawn only when view frustum is fixed - if (!self.update_view_frustum) { + if (false and !self.update_view_frustum) { gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.unlit).program); // Draw wire frustum cubes @@ -1162,9 +1276,9 @@ const cube_camera_dirs = [6]CubeCameraDir{ }; fn renderShadow(self: *Render, frustum: *const math.Frustum) void { + _ = frustum; // autofix const zone = tracy.initZone(@src(), .{ .name = "Render.renderShadow" }); defer zone.deinit(); - _ = frustum; // autofix self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value()); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); diff --git a/src/formats.zig b/src/formats.zig index 88e5bc9..41f6cfa 100644 --- a/src/formats.zig +++ b/src/formats.zig @@ -369,8 +369,8 @@ test "write and read scene" { pub const Material = extern struct { pub const BlendMode = enum(u8) { Opaque = 0, - AlphaBlend = 1, - AlphaMask = 2, + AlphaMask = 1, + AlphaBlend = 2, }; albedo: Vec4 = Vec4.one(), diff --git a/src/math.zig b/src/math.zig index 4cf7a6a..6bd7114 100644 --- a/src/math.zig +++ b/src/math.zig @@ -84,19 +84,14 @@ pub const AABB = struct { return AABB{ .origin = origin, .extents = extents }; } - // TODO: optimize pub fn transform(self: *const AABB, matrix: Mat4) AABB { - var min = Vec3.new(std.math.floatMax(f32), std.math.floatMax(f32), std.math.floatMax(f32)); - var max = Vec3.new(std.math.floatMin(f32), std.math.floatMin(f32), std.math.floatMin(f32)); + var origin = self.origin.toVec4(1.0); + var extents = self.extents.toVec4(0.0); - inline for (box_corners) |corner| { - const corner_pos = matrix.mulByVec4(self.origin.add(self.extents.mul(corner)).toVec4(1)); - const corner_pos3 = corner_pos.toVec3(); - min = corner_pos3.min(min); - max = corner_pos3.max(max); - } + origin = matrix.mulByVec4(origin); + extents = matrix.mulByVec4(extents); - return AABB.fromMinMax(min, max); + return AABB{ .origin = origin.toVec3(), .extents = extents.toVec3() }; } pub fn toSphere(self: *const AABB) BoundingSphere { @@ -232,6 +227,7 @@ pub const Frustum = struct { } pub fn intersectAABB(self: *const Frustum, aabb: AABB) bool { + @setRuntimeSafety(false); return self.intersectAABBInternal(aabb, false); } diff --git a/tools/asset_compiler.zig b/tools/asset_compiler.zig index 5f63461..4f31a13 100644 --- a/tools/asset_compiler.zig +++ b/tools/asset_compiler.zig @@ -218,10 +218,8 @@ fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std // TODO: rgba mat_output.albedo = Vec4.new(base_color.r, base_color.g, base_color.b, base_color.a); - if (std.math.approxEqAbs(f32, base_color.a, 0.0, std.math.floatEps(f32))) { + if (base_color.a < 1.0) { mat_output.blend_mode = .AlphaMask; - } else if (base_color.a < 1.0) { - mat_output.blend_mode = .AlphaBlend; } } @@ -231,7 +229,7 @@ fn processScene(allocator: std.mem.Allocator, input: []const u8, output_dir: std const entry = mat_texture.path.resolveAssetListEntry(texture_outputs, &has_alpha); mat_output.albedo_map.id = entry.getAssetId(); if (has_alpha) { - mat_output.blend_mode = .AlphaBlend; + mat_output.blend_mode = .AlphaMask; } }