Try to optimize for many draw calls

This commit is contained in:
sergeypdev 2024-07-26 17:03:35 +04:00
parent f21bc2245a
commit b0163b1f6b
5 changed files with 187 additions and 133 deletions

View File

@ -9,6 +9,7 @@
struct DrawCmdData { struct DrawCmdData {
mat4 transform; mat4 transform;
int materialIdx;
}; };
// UBOs // UBOs
@ -18,7 +19,6 @@ layout(std140, binding = 0) uniform Matrices {
}; };
layout(std430, binding = 3) readonly buffer DrawCmdDatas { layout(std430, binding = 3) readonly buffer DrawCmdDatas {
uint draws_count;
// Access by gl_DrawID // Access by gl_DrawID
DrawCmdData draw_data[]; DrawCmdData draw_data[];
}; };
@ -149,13 +149,13 @@ int getCSMSplit(int lightIdx, float depth) {
} }
EvalMaterial evalMaterial() { EvalMaterial evalMaterial() {
Material mat = materials[DrawID];
EvalMaterial result; EvalMaterial result;
result.albedo = textureSize(mat.albedo_map, 0) == ivec2(0) ? vec4(pow(mat.albedo.rgb, vec3(2.2)), mat.albedo.a) : texture(mat.albedo_map, VertexOut.uv * mat.albedo_map_uv_scale); int materialIdx = draw_data[DrawID].materialIdx;
float fMetallic = textureSize(mat.metallic_map, 0) == ivec2(0) ? mat.metallic : texture(mat.metallic_map, VertexOut.uv * mat.metallic_map_uv_scale).b; result.albedo = 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);
float fMetallic = textureSize(materials[materialIdx].metallic_map, 0) == ivec2(0) ? materials[materialIdx].metallic : texture(materials[materialIdx].metallic_map, VertexOut.uv * materials[materialIdx].metallic_map_uv_scale).b;
result.metallic = fMetallic > 0.1; result.metallic = fMetallic > 0.1;
result.roughness = max(0.01, textureSize(mat.roughness_map, 0) == ivec2(0) ? mat.roughness : texture(mat.roughness_map, VertexOut.uv * mat.roughness_map_uv_scale).g); result.roughness = max(0.01, textureSize(materials[materialIdx].roughness_map, 0) == ivec2(0) ? materials[materialIdx].roughness : texture(materials[materialIdx].roughness_map, VertexOut.uv * materials[materialIdx].roughness_map_uv_scale).g);
result.emission = textureSize(mat.emission_map, 0) == ivec2(0) ? mat.emission : texture(mat.emission_map, VertexOut.uv * mat.emission_map_uv_scale).rgb; result.emission = textureSize(materials[materialIdx].emission_map, 0) == ivec2(0) ? materials[materialIdx].emission : texture(materials[materialIdx].emission_map, VertexOut.uv * materials[materialIdx].emission_map_uv_scale).rgb;
return result; return result;
} }
@ -223,21 +223,7 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
// 0 - means directional, 1 - means point light // 0 - means directional, 1 - means point light
bool point = subgroupAll(lights[light_idx].vPos.w == 1); bool point = subgroupAll(lights[light_idx].vPos.w == 1);
vec3 lightI = lights[light_idx].color.rgb; vec3 lightI = lights[light_idx].color.rgb;
float lightRadius = max(lights[light_idx].color.a, eps);
vec3 L = mix(-lights[light_idx].vPos.xyz, lights[light_idx].vPos.xyz - P, lights[light_idx].vPos.w); vec3 L = mix(-lights[light_idx].vPos.xyz, lights[light_idx].vPos.xyz - P, lights[light_idx].vPos.w);
float dist = max(length(L), eps);
L /= dist;
// TODO: I think this is uniform control flow
// so makes sense to use `if` there for directional/point
// and don't calculate attenuation for directional at all
float att = lightAttenuation(
point,
dist,
lightRadius
);
lightI *= att;
vec3 V = normalize(-P); vec3 V = normalize(-P);
vec3 H = normalize(V + L); vec3 H = normalize(V + L);
@ -255,6 +241,16 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
float shadow_mult = 1; float shadow_mult = 1;
vec4 shadow_offset = vec4(VertexOut.wNormal * normal_offset_scale, 0); vec4 shadow_offset = vec4(VertexOut.wNormal * normal_offset_scale, 0);
if (point) { if (point) {
float dist = max(length(L), eps);
L /= dist;
float lightRadius = max(lights[light_idx].color.a, eps);
float att = lightAttenuation(
point,
dist,
lightRadius
);
lightI *= att;
vec2 shadow_map_texel_size = 1.0 / vec2(textureSize(cube_shadow_maps, 0)); vec2 shadow_map_texel_size = 1.0 / vec2(textureSize(cube_shadow_maps, 0));
shadow_offset *= shadow_map_texel_size.x; shadow_offset *= shadow_map_texel_size.x;
vec3 shadow_dir = (lights[light_idx].view_mat * vec4(VertexOut.wPos, 1.0)).xyz; vec3 shadow_dir = (lights[light_idx].view_mat * vec4(VertexOut.wPos, 1.0)).xyz;
@ -295,7 +291,7 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
texcoord.z = shadow_map_idx + csm_split_idx; texcoord.z = shadow_map_idx + csm_split_idx;
float sum = 0; float sum = 0;
sum = texture(shadow_maps, vec4(texcoord.xy, texcoord.zw)); sum = 1.0; // texture(shadow_maps, vec4(texcoord.xy, texcoord.zw));
// for (float y = -0.5; y <= 0.5; y += 1) { // for (float y = -0.5; y <= 0.5; y += 1) {
// for (float x = -0.5; x <= 0.5; x += 1) { // for (float x = -0.5; x <= 0.5; x += 1) {
// sum += ; // sum += ;
@ -308,14 +304,19 @@ vec3 microfacetModel(EvalMaterial mat, int light_idx, vec3 P, vec3 N) {
vec3 specBrdf = geomSmith(mat, NDotL) * geomSmith(mat, NDotV) * 0.25 * ggxDistribution(mat, NDotH) * schlickFresnel(mat, LDotH); vec3 specBrdf = geomSmith(mat, NDotL) * geomSmith(mat, NDotV) * 0.25 * ggxDistribution(mat, NDotH) * schlickFresnel(mat, LDotH);
return (PI * specBrdf + diffuseBrdf) * NDotL * shadow_mult * lightI + mat.emission; vec3 vecTerm = PI * specBrdf + diffuseBrdf;
float scalarTerm = NDotL * shadow_mult;
return scalarTerm * lightI + mat.emission;
} }
void main() { void main() {
Material mat = materials[DrawID]; int materialIdx = draw_data[DrawID].materialIdx;
sampler2D normal_map = materials[materialIdx].normal_map;
vec2 normal_map_uv_scale = materials[materialIdx].normal_map_uv_scale;
EvalMaterial material = evalMaterial(); EvalMaterial material = evalMaterial();
vec3 N = textureSize(mat.normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(mat.normal_map, VertexOut.uv * mat.normal_map_uv_scale).xy, 0); vec3 N = textureSize(normal_map, 0) == ivec2(0) ? vec3(0.5) : vec3(texture(normal_map, VertexOut.uv * normal_map_uv_scale).xy, 0);
N = N * 2.0 - 1.0; N = N * 2.0 - 1.0;
N.z = sqrt(clamp(1 - N.x * N.x - N.y * N.y, 0, 1)); N.z = sqrt(clamp(1 - N.x * N.x - N.y * N.y, 0, 1));
N = normalize(N); N = normalize(N);
@ -324,8 +325,8 @@ void main() {
vec3 finalColor = vec3(0); vec3 finalColor = vec3(0);
// int n_lights = clamp(int(lights_count), 0, MAX_POINT_LIGHTS); // int n_lights = clamp(int(lights_count), 0, MAX_POINT_LIGHTS);
for (int i = 0; i < lights_count; i++) { for (int i = 0; i < MAX_POINT_LIGHTS; i++) {
// if (i >= lights_count) break; if (i >= lights_count) break;
finalColor += microfacetModel(material, i, VertexOut.vPos, N); finalColor += microfacetModel(material, i, VertexOut.vPos, N);
} }

View File

@ -1,6 +1,7 @@
struct DrawCmdData { struct DrawCmdData {
mat4 transform; mat4 transform;
int materialIdx;
}; };
// UBOs // UBOs
@ -10,7 +11,6 @@ layout(std140, binding = 0) uniform Matrices {
}; };
layout(std430, binding = 3) readonly buffer DrawCmdDatas { layout(std430, binding = 3) readonly buffer DrawCmdDatas {
uint draws_count;
// Access by gl_DrawID // Access by gl_DrawID
DrawCmdData draw_data[]; DrawCmdData draw_data[];
}; };

View File

@ -6,6 +6,7 @@ const a = @import("asset_manifest");
const globals = @import("globals.zig"); const globals = @import("globals.zig");
pub const Material = @import("formats.zig").Material; pub const Material = @import("formats.zig").Material;
const math = @import("math.zig"); const math = @import("math.zig");
const formats = @import("formats.zig");
const za = @import("zalgebra"); const za = @import("zalgebra");
const Vec2 = za.Vec2; const Vec2 = za.Vec2;
@ -17,7 +18,7 @@ const Vec2_i32 = za.Vec2_i32;
pub const MAX_FRAMES_QUEUED = 3; pub const MAX_FRAMES_QUEUED = 3;
pub const MAX_LIGHTS = 8; pub const MAX_LIGHTS = 8;
pub const MAX_DRAW_COMMANDS = 4096; pub const MAX_DRAW_COMMANDS = 1024 * 16;
pub const MAX_LIGHT_COMMANDS = 2048; pub const MAX_LIGHT_COMMANDS = 2048;
pub const MAX_MATERIALS = MAX_DRAW_COMMANDS; pub const MAX_MATERIALS = MAX_DRAW_COMMANDS;
pub const CSM_SPLITS = 4; pub const CSM_SPLITS = 4;
@ -722,20 +723,25 @@ pub fn finish(self: *Render) void {
const switched_to_alpha_blend = false; const switched_to_alpha_blend = false;
gl.disable(gl.BLEND); gl.disable(gl.BLEND);
const draw_indirect_cmds_c: [*]u8 = @ptrCast(gl.mapNamedBuffer( var draw_indirect_cmds = self.frame_arena.alloc(DrawIndirectCmd, MAX_DRAW_COMMANDS) catch @panic("OOM");
self.draw_indirect_buffer, var draw_cmd_data = self.frame_arena.alloc(DrawCommandData, MAX_DRAW_COMMANDS) catch @panic("OOM");
gl.WRITE_ONLY,
) orelse { var draw_indirect_buf: gl.GLuint = 0;
checkGLError(); gl.createBuffers(1, &draw_indirect_buf);
@panic("map draw indirect buffer"); checkGLError();
}); defer gl.deleteBuffers(1, &draw_indirect_buf);
var draw_indirect_cmds = std.mem.bytesAsSlice(DrawIndirectCmd, draw_indirect_cmds_c[0 .. @sizeOf(DrawIndirectCmd) * MAX_DRAW_COMMANDS]);
var draw_cmd_data_buf: gl.GLuint = 0;
gl.createBuffers(1, &draw_cmd_data_buf);
checkGLError();
defer gl.deleteBuffers(1, &draw_cmd_data_buf);
const materials = self.materials_pbr_ssbo.getInstance(self.tripple_buffer_index); const materials = self.materials_pbr_ssbo.getInstance(self.tripple_buffer_index);
materials.count.* = 0; materials.count.* = 0;
const draw_cmd_data = self.draw_cmd_data_ssbo.getInstance(self.tripple_buffer_index); var material_map = std.StringHashMap(i32).init(self.frame_arena);
var materials_count: usize = 0;
var rendered_count: usize = 0; var rendered_count: usize = 0;
cmds: for (self.command_buffer[0..self.command_count]) |*cmd| { cmds: for (self.command_buffer[0..self.command_count]) |*cmd| {
const mesh = self.assetman.resolveMesh(cmd.mesh); const mesh = self.assetman.resolveMesh(cmd.mesh);
@ -755,12 +761,20 @@ pub fn finish(self: *Render) void {
// gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
} }
draw_cmd_data.data[rendered_count] = DrawCommandData{ const material_bytes = std.mem.asBytes(&material);
.transform = cmd.transform, const material_copy = self.frame_arena.alloc(u8, material_bytes.len) catch @panic("OOM");
}; @memcpy(material_copy, material_bytes);
const gop = material_map.getOrPut(material_copy) catch @panic("OOM");
if (!gop.found_existing) {
gop.value_ptr.* = @intCast(materials_count);
materials.data[materials_count] = MaterialPBR.fromMaterial(self.assetman, &material);
materials_count += 1;
}
materials.data[rendered_count] = MaterialPBR.fromMaterial(self.assetman, &material); draw_cmd_data[rendered_count] = DrawCommandData{
materials.count.* += 1; .transform = cmd.transform,
.material_index = gop.value_ptr.*,
};
draw_indirect_cmds[rendered_count] = DrawIndirectCmd{ draw_indirect_cmds[rendered_count] = DrawIndirectCmd{
.count = mesh.indices.count, .count = mesh.indices.count,
@ -774,10 +788,6 @@ pub fn finish(self: *Render) void {
rendered_count += 1; rendered_count += 1;
} }
_ = gl.unmapNamedBuffer(self.draw_indirect_buffer);
gl.bindBuffer(gl.DRAW_INDIRECT_BUFFER, self.draw_indirect_buffer);
{ {
const camera_matrix: *CameraMatrices = @alignCast(@ptrCast(self.camera_matrices[self.tripple_buffer_index * self.uboAlignedSizeOf(CameraMatrices) ..].ptr)); const camera_matrix: *CameraMatrices = @alignCast(@ptrCast(self.camera_matrices[self.tripple_buffer_index * self.uboAlignedSizeOf(CameraMatrices) ..].ptr));
camera_matrix.* = .{ camera_matrix.* = .{
@ -796,15 +806,21 @@ pub fn finish(self: *Render) void {
checkGLError(); checkGLError();
} }
gl.namedBufferStorage(draw_indirect_buf, @intCast(@sizeOf(DrawIndirectCmd) * rendered_count), draw_indirect_cmds.ptr, 0);
gl.bindBuffer(gl.DRAW_INDIRECT_BUFFER, draw_indirect_buf);
gl.namedBufferStorage(draw_cmd_data_buf, @intCast(@sizeOf(DrawCommandData) * rendered_count), draw_cmd_data.ptr, 0);
gl.bindBufferBase(gl.SHADER_STORAGE_BUFFER, SSBO.DrawCommandData.value(), draw_cmd_data_buf);
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program); gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.z_prepass).program);
gl.bindVertexArray(self.shadow_vao); gl.bindVertexArray(self.shadow_vao);
self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value()); self.assetman.vertex_heap.vertices.bind(Render.Attrib.Position.value(), 0);
checkGLError(); checkGLError();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, self.assetman.vertex_heap.indices.buffer);
checkGLError(); checkGLError();
gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_count), @sizeOf(DrawIndirectCmd)); // gl.multiDrawElementsIndirect(gl.TRIANGLES, gl.UNSIGNED_INT, null, @intCast(rendered_count), @sizeOf(DrawIndirectCmd));
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program); gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.mesh).program);
gl.bindVertexArray(self.mesh_vao); gl.bindVertexArray(self.mesh_vao);
@ -1314,13 +1330,13 @@ pub fn BufferSSBOAlign(comptime T: type, comptime alignment: usize) type {
_start: [0]T align(alignment), _start: [0]T align(alignment),
pub fn calculateBufSize(max_count: usize, ssbo_align: usize) usize { pub fn calculateBufSize(max_count: usize, ssbo_align: usize) usize {
return std.mem.alignForward(usize, @sizeOf(BufferInstance) + @sizeOf(T) * max_count, ssbo_align); return std.mem.alignForward(usize, @sizeOf(BufferLayout) + std.mem.alignForward(usize, @sizeOf(T), alignment) * max_count, ssbo_align);
} }
pub fn getData(self: *BufferLayout, len: usize) []T { pub fn getData(self: *BufferLayout, len: usize) ([]align(alignment) T) {
var data_c: [*]T = @ptrFromInt(@intFromPtr(self) + @offsetOf(BufferLayout, "_start")); var data_c: [*]align(alignment) T = @ptrFromInt(@intFromPtr(self) + @offsetOf(BufferLayout, "_start"));
return data_c[0..len]; return @alignCast(data_c[0..len]);
} }
}; };
@ -1408,9 +1424,11 @@ const MaterialPBRSSBO = BufferSSBO(MaterialPBR);
const DrawCommandData = extern struct { const DrawCommandData = extern struct {
transform: Mat4, transform: Mat4,
material_index: c_int,
_pad: [0]void align(16) = std.mem.zeroes([0]void),
}; };
const DrawCommandDataSSBO = BufferSSBOAlign(DrawCommandData, 16); const DrawCommandDataSSBO = BufferSSBO(DrawCommandData);
const DrawIndirectCmd = extern struct { const DrawIndirectCmd = extern struct {
count: gl.GLuint, count: gl.GLuint,

View File

@ -193,35 +193,35 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
.rotate = .{ .axis = Vec3.up(), .rate = -10 }, .rotate = .{ .axis = Vec3.up(), .rate = -10 },
}); });
const light_root = globals.g_mem.world.addEntity(.{ // const light_root = globals.g_mem.world.addEntity(.{
.flags = .{ .rotate = true }, // .flags = .{ .rotate = true },
.transform = .{ .pos = Vec3.new(0, 0.1, 0) }, // .transform = .{ .pos = Vec3.new(0, 0.1, 0) },
.rotate = .{ .axis = Vec3.up(), .rate = 60 }, // .rotate = .{ .axis = Vec3.up(), .rate = 60 },
}); // });
const light1 = globals.g_mem.world.addEntity(.{ // const light1 = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(1.8, 1, 0) }, // .transform = .{ .pos = Vec3.new(1.8, 1, 0) },
.flags = .{ .point_light = true, .rotate = true }, // .flags = .{ .point_light = true, .rotate = true },
.light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0) }, // .light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 100.0) },
.point_light = .{ .radius = 0.1 }, // .point_light = .{ .radius = 0.1 },
.rotate = .{ .axis = Vec3.up(), .rate = -40 }, // .rotate = .{ .axis = Vec3.up(), .rate = -40 },
}); // });
light1.ptr.setParent(light_root.handle); // light1.ptr.setParent(light_root.handle);
const light2 = globals.g_mem.world.addEntity(.{ // const light2 = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(-2, 0, 0) }, // .transform = .{ .pos = Vec3.new(-2, 0, 0) },
.flags = .{ .point_light = true, .rotate = true }, // .flags = .{ .point_light = true, .rotate = true },
.light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0) }, // .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 100.0) },
.point_light = .{ .radius = 0.1 }, // .point_light = .{ .radius = 0.1 },
}); // });
light2.ptr.setParent(light1.handle); // light2.ptr.setParent(light1.handle);
_ = globals.g_mem.world.addEntity(.{ // _ = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(1, 0.5, 4) }, // .transform = .{ .pos = Vec3.new(1, 0.5, 4) },
.flags = .{ .point_light = true }, // .flags = .{ .point_light = true },
.light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 10.0) }, // .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 10.0) },
.point_light = .{ .radius = 1 }, // .point_light = .{ .radius = 1 },
}); // });
// Plane // Plane
_ = globals.g_mem.world.addEntity(.{ _ = globals.g_mem.world.addEntity(.{
@ -239,21 +239,23 @@ export fn game_init(global_allocator: *std.mem.Allocator) void {
// 10 dielectric bunnies // 10 dielectric bunnies
{ {
for (0..10) |i| { for (0..100) |y| {
_ = globals.g_mem.world.addEntity(.{ for (0..10) |x| {
.transform = .{ .pos = Vec3.new(@as(f32, @floatFromInt(i)) * 0.3 - 0.3 * 4.5, 0, 0) }, _ = globals.g_mem.world.addEntity(.{
.transform = .{ .pos = Vec3.new(@as(f32, @floatFromInt(x)) * 0.3 - 0.3 * 4.5, 0, @as(f32, @floatFromInt(y)) * 0.3 - 0.3 * 4.5) },
.flags = .{ .mesh = true }, .flags = .{ .mesh = true },
.mesh = .{ .mesh = .{
.handle = a.Meshes.bunny.BunnyStanfordUVUnwrapped_res1_bun_zipper_res1, .handle = a.Meshes.bunny.BunnyStanfordUVUnwrapped_res1_bun_zipper_res1,
.material = .{ .material = .{
.albedo_map = a.Textures.bunny_tex1, .albedo_map = a.Textures.bunny_tex1,
// .normal_map = a.Textures.@"tile.norm", // .normal_map = a.Textures.@"tile.norm",
.roughness = @as(f32, @floatFromInt(i)) / 10.0, .roughness = @as(f32, @floatFromInt(y)) / 100.0,
},
.override_material = true,
}, },
.override_material = true, });
}, }
});
} }
} }
// 10 metallic bunnies // 10 metallic bunnies

View File

@ -86,14 +86,14 @@ pub const AABB = struct {
// TODO: optimize // TODO: optimize
pub fn transform(self: *const AABB, matrix: Mat4) AABB { pub fn transform(self: *const AABB, matrix: Mat4) AABB {
var min = Vec3.zero(); var min = Vec3.new(std.math.floatMax(f32), std.math.floatMax(f32), std.math.floatMax(f32));
var max = Vec3.zero(); var max = Vec3.new(std.math.floatMin(f32), std.math.floatMin(f32), std.math.floatMin(f32));
inline for (box_corners) |corner| { inline for (box_corners) |corner| {
const corner_pos = matrix.mulByVec4(self.origin.add(self.extents.mul(corner)).toVec4(1)); const corner_pos = matrix.mulByVec4(self.origin.add(self.extents.mul(corner)).toVec4(1));
const corner_pos3 = corner_pos.scale(1 / corner_pos.w()).toVec3(); const corner_pos3 = corner_pos.toVec3();
min = Vec3.new(@min(corner_pos3.x(), min.x()), @min(corner_pos3.y(), min.y()), @min(corner_pos3.z(), min.z())); min = corner_pos3.min(min);
max = Vec3.new(@max(corner_pos3.x(), max.x()), @max(corner_pos3.y(), max.y()), @max(corner_pos3.z(), max.z())); max = corner_pos3.max(max);
} }
return AABB.fromMinMax(min, max); return AABB.fromMinMax(min, max);
@ -118,12 +118,22 @@ pub const BoundingSphere = struct {
pub const Frustum = struct { pub const Frustum = struct {
// Plane normals // Plane normals
top: Plane = .{}, // top
right: Plane = .{}, // right
bottom: Plane = .{}, // bottom
left: Plane = .{}, // left
near: Plane = .{}, // near
far: Plane = .{}, // far
planes: [6]Plane = std.mem.zeroes([6]Plane),
pub const PlaneSide = enum {
Top,
Right,
Bottom,
Left,
Near,
Far,
};
/// Extracts frustum planes from matrices using Gribb-Hartmann method /// Extracts frustum planes from matrices using Gribb-Hartmann method
/// If you pass in a projection matrix planes will be in view space. /// If you pass in a projection matrix planes will be in view space.
@ -143,31 +153,36 @@ pub const Frustum = struct {
const far = row4.sub(row3); const far = row4.sub(row3);
return .{ return .{
.top = Plane.new(top), .planes = .{
.right = Plane.new(right), Plane.new(top),
.bottom = Plane.new(bottom), Plane.new(right),
.left = Plane.new(left), Plane.new(bottom),
.near = Plane.new(near), Plane.new(left),
.far = Plane.new(far), Plane.new(near),
Plane.new(far),
},
}; };
} }
pub fn getPlane(self: *const Frustum, side: PlaneSide) *const Plane {
return &self.planes[@intFromEnum(side)];
}
pub fn getNearDist(self: *const Frustum) f32 { pub fn getNearDist(self: *const Frustum) f32 {
return self.near.distance(); return self.getPlane(.Near).distance();
} }
pub fn rangeZ(self: *const Frustum) f32 { pub fn rangeZ(self: *const Frustum) f32 {
return self.far.point().sub(self.near.point()).dot(self.near.normal()); return self.getPLane(.Far).point().sub(self.getPlane(.Near).point()).dot(self.getPlane(.Near).normal());
} }
pub fn transform(self: *const Frustum, matrix: *const Mat4) Frustum { pub fn transform(self: *const Frustum, matrix: *const Mat4) Frustum {
const new_planes = self.planes;
for (new_planes) |*plane| {
plane.* = plane.transform(matrix);
}
return Frustum{ return Frustum{
.top = self.top.transform(matrix), .planes = new_planes,
.right = self.right.transform(matrix),
.bottom = self.bottom.transform(matrix),
.left = self.left.transform(matrix),
.near = self.near.transform(matrix),
.far = self.far.transform(matrix),
}; };
} }
@ -176,26 +191,44 @@ pub const Frustum = struct {
} }
fn intersectAABBInternal(self: *const Frustum, aabb: AABB, comptime skip_near: bool) bool { fn intersectAABBInternal(self: *const Frustum, aabb: AABB, comptime skip_near: bool) bool {
var outside_top_plane = true; for (0..6) |i| {
var outside_bottom_plane = true; if (skip_near and i == @intFromEnum(PlaneSide.Near)) continue;
var outside_left_plane = true;
var outside_right_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));
outside_top_plane = outside_top_plane and self.top.isUnder(p); const plane = self.planes[i];
outside_bottom_plane = outside_bottom_plane and self.bottom.isUnder(p); const nx = plane.normal().x() > 0;
outside_left_plane = outside_left_plane and self.left.isUnder(p); const ny = plane.normal().y() > 0;
outside_right_plane = outside_right_plane and self.right.isUnder(p); const nz = plane.normal().z() > 0;
if (!skip_near) {
outside_near_plane = outside_near_plane and self.near.isUnder(p); const min = aabb.origin.sub(aabb.extents);
const max = aabb.origin.add(aabb.extents);
// TODO: vectorize
const dot = (plane.normal().x() * if (nx) max.x() else min.x()) + (plane.normal().y() * if (ny) max.y() else min.y()) + (plane.normal().z() * if (nz) max.z() else min.z());
if (dot < plane.distance()) {
return false;
} }
outside_far_plane = outside_far_plane and self.far.isUnder(p);
// const dot2 = (plane.normal().x() * if (nx) min.x else max.x) + (plane.normal().y() * if (ny) min.y else max.y) + (plane.normal().z() * if (nz) min.z else max.z);
// planes have unit-length normal, offset = -dot(normal, point on plane)
// const Plane3& plane = planes[i];
// Index nx = plane.normal.x > Real(0);
// Index ny = plane.normal.y > Real(0);
// Index nz = plane.normal.z > Real(0);
//
// // getMinMax(): 0 = return min coordinate. 1 = return max.
// Real dot = (plane.normal.x*box.getMinMax(nx).x) + (plane.normal.y*box.getMinMax(ny).y) + (plane.normal.z*box.getMinMax(nz).z);
//
// if ( dot < -plane.offset )
// return OUTSIDE;
//
// Real dot2 = (plane.normal.x*box.getMinMax(1-nx).x) + (plane.normal.y*box.getMinMax(1-ny).y) + (plane.normal.z*box.getMinMax(1-nz).z);
//
// if ( dot2 <= -plane.offset )
// result = INTERSECTS;
} }
return !(outside_left_plane or outside_right_plane or outside_bottom_plane or outside_top_plane or outside_near_plane or outside_far_plane); return true;
} }
pub fn intersectAABB(self: *const Frustum, aabb: AABB) bool { pub fn intersectAABB(self: *const Frustum, aabb: AABB) bool {