Refactor lights

This commit is contained in:
sergeypdev 2024-03-09 21:22:42 +04:00
parent f1123a1e15
commit 6b4fe69505
2 changed files with 162 additions and 101 deletions

View File

@ -16,8 +16,9 @@ const Quat = za.Quat;
const Vec2_i32 = za.Vec2_i32;
pub const MAX_FRAMES_QUEUED = 3;
pub const MAX_POINT_LIGHTS = 8;
pub const MAX_LIGHTS = 8;
pub const MAX_DRAW_COMMANDS = 4096;
pub const MAX_LIGHT_COMMANDS = 2048;
pub const Render = @This();
@ -33,7 +34,9 @@ gl_fences: [MAX_FRAMES_QUEUED]?gl.GLsync = [_]?gl.GLsync{null} ** MAX_FRAMES_QUE
camera_ubo: gl.GLuint = 0,
camera_matrices: []u8 = &.{},
point_lights_ubo: gl.GLuint = 0,
point_lights: []u8 = &.{},
point_lights: []u8 = &.{}, // TODO: remove
lights: [MAX_LIGHT_COMMANDS]LightCommand = undefined,
light_count: usize = 0,
command_buffer: [MAX_DRAW_COMMANDS]DrawCommand = undefined,
command_count: usize = 0,
ubo_align: usize = 0,
@ -136,7 +139,7 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
gl.createBuffers(1, &render.point_lights_ubo);
std.debug.assert(render.camera_ubo != 0);
const buf_size = render.uboAlignedSizeOf(PointLightArray) * MAX_FRAMES_QUEUED;
const buf_size = render.uboAlignedSizeOf(LightArray) * MAX_FRAMES_QUEUED;
gl.namedBufferStorage(
render.point_lights_ubo,
@intCast(buf_size),
@ -193,7 +196,7 @@ pub fn init(allocator: std.mem.Allocator, frame_arena: std.mem.Allocator, assetm
checkGLError();
std.debug.assert(render.cube_shadow_texture_array != 0);
gl.textureStorage3D(render.cube_shadow_texture_array, 1, gl.DEPTH_COMPONENT16, 512, 512, MAX_POINT_LIGHTS * 6);
gl.textureStorage3D(render.cube_shadow_texture_array, 1, gl.DEPTH_COMPONENT16, 512, 512, MAX_LIGHTS * 6);
checkGLError();
gl.textureParameteri(render.cube_shadow_texture_array, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
@ -346,6 +349,7 @@ fn updateScreenBufferSize(self: *Render, width: c_int, height: c_int) void {
pub fn begin(self: *Render) void {
self.command_count = 0;
self.light_count = 0;
self.tripple_buffer_index = (self.tripple_buffer_index + 1) % MAX_FRAMES_QUEUED;
gl.enable(gl.CULL_FACE);
@ -375,10 +379,11 @@ pub fn begin(self: *Render) void {
}
}
pub fn getPointLights(self: *Render) *PointLightArray {
return @alignCast(@ptrCast(self.point_lights[self.tripple_buffer_index * self.uboAlignedSizeOf(PointLightArray) ..].ptr));
fn getLightBuffer(self: *Render) *LightArray {
return @alignCast(@ptrCast(self.point_lights[self.tripple_buffer_index * self.uboAlignedSizeOf(LightArray) ..].ptr));
}
// TODO: get rid of this
pub fn flushUBOs(self: *Render) void {
const idx = self.tripple_buffer_index;
@ -387,12 +392,37 @@ pub fn flushUBOs(self: *Render) void {
gl.UNIFORM_BUFFER,
UBO.PointLights.value(),
self.point_lights_ubo,
idx * self.uboAlignedSizeOf(PointLightArray),
@intCast(self.uboAlignedSizeOf(PointLightArray)),
idx * self.uboAlignedSizeOf(LightArray),
@intCast(self.uboAlignedSizeOf(LightArray)),
);
checkGLError();
}
pub const LightKind = enum {
directional,
point,
// Spot, // TODO
};
pub const PointLight = struct {
color: Vec3,
pos: Vec3,
radius: f32,
};
pub const LightCommand = union(LightKind) {
directional: struct {
color: Vec3,
dir: Vec3,
},
point: PointLight,
};
pub fn drawLight(self: *Render, cmd: LightCommand) void {
self.lights[self.light_count] = cmd;
self.light_count += 1;
}
pub fn draw(self: *Render, cmd: DrawCommand) void {
self.command_buffer[self.command_count] = cmd;
self.command_count += 1;
@ -401,79 +431,64 @@ pub fn draw(self: *Render, cmd: DrawCommand) void {
pub fn finish(self: *Render) void {
const ginit = globals.g_init;
const lights = self.getPointLights();
const lights = self.lights[0..self.light_count];
// Sort lights: directional first
{
std.mem.sortUnstable(LightCommand, lights, {}, struct {
pub fn lessThan(_: void, lhs: LightCommand, rhs: LightCommand) bool {
_ = rhs; // autofix
return switch (lhs) {
.directional => true,
.point => false,
};
}
}.lessThan);
}
const lights_buf = self.getLightBuffer();
lights_buf.count = 0;
// Light shadow maps
{
gl.bindVertexArray(self.shadow_vao);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, self.shadow_framebuffer);
for (lights.lights[0..lights.count], 0..) |*light, i| {
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.shadow).program);
// Directional light
if (std.math.approxEqAbs(f32, light.pos.w(), 0, std.math.floatEps(f32))) {
gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.shadow_texture_array, 0, 0);
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});
}
var finished_dir_lights = false;
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.shadow).program);
gl.viewport(0, 0, 2048, 2048);
for (lights) |light_cmd| {
const i = lights_buf.count;
if (i == lights_buf.lights.len) break;
const camera_matrix = &self.shadow_matrices;
camera_matrix.* = .{
.projection = math.orthographic(-4, 4, -4, 4, -5, 5),
.view = Mat4.lookAt(
Vec3.new(light.pos.x(), light.pos.y(), light.pos.z()).scale(-1),
Vec3.zero(),
Vec3.up(),
),
};
const light = &lights_buf.lights[i];
lights_buf.count += 1;
const shadow_view_proj = camera_matrix.projection.mul(camera_matrix.view);
const light_frustum = math.Frustum.new(shadow_view_proj);
light.shadow_vp = shadow_view_proj;
gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices));
checkGLError();
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer);
self.renderShadow(&light_frustum);
} else {
// Point Light
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.cube_shadow).program);
const pos = Vec3.new(light.pos.x(), light.pos.y(), light.pos.z());
light.shadow_vp = Mat4.fromTranslate(pos.negate());
// 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(i * 6 + face));
switch (light_cmd) {
.directional => |dir_light| {
light.pos = dir_light.dir.toVec4(0);
light.color_radius = dir_light.color.toVec4(0);
gl.namedFramebufferTextureLayer(self.shadow_framebuffer, gl.DEPTH_ATTACHMENT, self.shadow_texture_array, 0, 0);
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});
}
gl.viewport(0, 0, 512, 512);
gl.viewport(0, 0, 2048, 2048);
const near_far = Vec2.new(0.1, 10);
const camera_matrix = &self.shadow_matrices;
camera_matrix.* = .{
.projection = math.perspective(90, 1, near_far.x(), near_far.y()),
.projection = math.orthographic(-4, 4, -4, 4, -5, 5),
.view = Mat4.lookAt(
pos,
pos.add(cam_dir.target),
cam_dir.up,
dir_light.dir.scale(-1),
Vec3.zero(),
Vec3.up(),
),
};
const shadow_view_proj = camera_matrix.projection.mul(camera_matrix.view);
const light_frustum = math.Frustum.new(shadow_view_proj);
//light.shadow_vp = shadow_view_proj;
light.near_far = near_far;
gl.uniform2f(Uniform.NearFarPlanes.value(), near_far.x(), near_far.y());
light.shadow_vp = shadow_view_proj;
gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices));
checkGLError();
@ -482,13 +497,62 @@ pub fn finish(self: *Render) void {
gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer);
self.renderShadow(&light_frustum);
}
},
.point => |point_light| {
if (!finished_dir_lights) {
finished_dir_lights = true;
gl.useProgram(self.assetman.resolveShaderProgram(a.ShaderPrograms.shaders.cube_shadow).program);
}
const pos = point_light.pos;
light.pos = pos.toVec4(1);
light.color_radius = point_light.color.toVec4(point_light.radius);
light.shadow_vp = Mat4.fromTranslate(pos.negate());
// 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(i * 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});
}
gl.viewport(0, 0, 512, 512);
const range = pointLightRange(&point_light);
const near_far = Vec2.new(0.1, range);
const camera_matrix = &self.shadow_matrices;
camera_matrix.* = .{
.projection = math.perspective(90, 1, near_far.x(), near_far.y()),
.view = Mat4.lookAt(
pos,
pos.add(cam_dir.target),
cam_dir.up,
),
};
const shadow_view_proj = camera_matrix.projection.mul(camera_matrix.view);
const light_frustum = math.Frustum.new(shadow_view_proj);
light.shadow_vp = shadow_view_proj;
light.near_far = near_far;
gl.uniform2f(Uniform.NearFarPlanes.value(), near_far.x(), near_far.y());
gl.namedBufferSubData(self.shadow_matrices_buffer, 0, @sizeOf(CameraMatrices), std.mem.asBytes(&self.shadow_matrices));
checkGLError();
gl.clear(gl.DEPTH_BUFFER_BIT);
gl.bindBufferBase(gl.UNIFORM_BUFFER, UBO.CameraMatrices.value(), self.shadow_matrices_buffer);
self.renderShadow(&light_frustum);
}
},
}
}
}
// Light world space to view space
for (lights.lights[0..lights.count]) |*light| {
for (lights_buf.lights[0..lights_buf.count]) |*light| {
light.pos = self.camera.view_mat.mulByVec4(light.pos);
}
@ -708,6 +772,14 @@ pub fn finish(self: *Render) void {
//c.SDL_Delay(1);
}
pub fn pointLightRange(self: *const PointLight) f32 {
const color = self.color;
const light_intensity = @max(color.x(), color.y(), color.z());
const cutoff = 0.005;
return self.radius * (@sqrt(light_intensity / cutoff) - 1);
}
const CubeCameraDir = struct {
face: gl.GLenum,
target: Vec3,
@ -869,7 +941,7 @@ const CameraMatrices = extern struct {
projection: Mat4 = Mat4.identity(),
view: Mat4 = Mat4.identity(),
};
pub const PointLight = extern struct {
pub const Light = extern struct {
pos: Vec4, // x, y, z, w - vPos
color_radius: Vec4, // x, y, z - color, w - radius
shadow_vp: Mat4 = Mat4.identity(),
@ -877,8 +949,8 @@ pub const PointLight = extern struct {
};
// TODO: rename
pub const PointLightArray = extern struct {
lights: [MAX_POINT_LIGHTS]PointLight,
pub const LightArray = extern struct {
lights: [MAX_LIGHTS]Light,
count: c_uint,
};

View File

@ -434,44 +434,6 @@ export fn game_update() bool {
// Collect point lights
{
const point_lights = gmem.render.getPointLights();
point_lights.count = 0;
for (0..gmem.world.entity_count) |i| {
const ent = &gmem.world.entities[i];
if (!ent.data.flags.active) continue;
if (ent.data.flags.point_light) {
const pos = ent.globalMatrix(&gmem.world).extractTranslation();
var pos4 = Vec4.new(pos.x(), pos.y(), pos.z(), 1.0);
const color = ent.data.light.premultipliedColor();
point_lights.lights[point_lights.count] = .{
.pos = Vec4.new(pos4.x(), pos4.y(), pos4.z(), 1),
.color_radius = Vec4.new(color.x(), color.y(), color.z(), ent.data.point_light.radius),
};
point_lights.count += 1;
if (point_lights.count == Render.MAX_POINT_LIGHTS) {
break;
}
}
if (ent.data.flags.dir_light) {
const dir4 = ent.globalMatrix(&gmem.world).mulByVec4(Vec4.forward());
const color = ent.data.light.premultipliedColor();
point_lights.lights[point_lights.count] = .{
.pos = dir4,
.color_radius = Vec4.new(color.x(), color.y(), color.z(), 1),
};
point_lights.count += 1;
if (point_lights.count == Render.MAX_POINT_LIGHTS) {
break;
}
}
}
gmem.render.flushUBOs();
// Render meshes and lights
for (0..gmem.world.entity_count) |i| {
const ent = &gmem.world.entities[i];
if (!ent.data.flags.active) continue;
@ -490,7 +452,34 @@ export fn game_update() bool {
.transform = ent.globalMatrix(&gmem.world).*,
});
}
if (ent.data.flags.point_light) {
const pos = ent.globalMatrix(&gmem.world).extractTranslation();
const color = ent.data.light.premultipliedColor();
gmem.render.drawLight(.{
.point = .{
.pos = pos,
.radius = ent.data.point_light.radius,
.color = color,
},
});
}
if (ent.data.flags.dir_light) {
const dir4 = ent.globalMatrix(&gmem.world).mulByVec4(Vec4.forward());
const color = ent.data.light.premultipliedColor();
gmem.render.drawLight(.{
.directional = .{
.dir = dir4.toVec3(),
.color = color,
},
});
}
}
// TODO: get rid of this
gmem.render.flushUBOs();
}
}