From d450fa5f62eaa47d641d2d25549dc1d241ecf27c Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Thu, 25 Apr 2024 11:47:47 +0200 Subject: [PATCH] Improve CPU-side light interpolation code, reducing the number of samples needed in the average case of axis aligned blocks. Fixes #296 Helps with #277 --- src/models.zig | 37 +++++++++++++++++- src/renderer/chunk_meshing.zig | 71 +++++++++++++++++++++++++++------- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/models.zig b/src/models.zig index c486491c..01f380ed 100644 --- a/src/models.zig +++ b/src/models.zig @@ -21,6 +21,13 @@ pub const QuadInfo = extern struct { textureSlot: u32, }; +const ExtraQuadInfo = struct { + faceNeighbor: ?u3, + isFullQuad: bool, + hasOnlyCornerVertices: bool, + alignedNormalDirection: ?u3, +}; + const gridSize = 4096; fn snapToGrid(x: anytype) @TypeOf(x) { @@ -200,18 +207,44 @@ pub fn getModelIndex(string: []const u8) u16 { } pub var quads: main.List(QuadInfo) = undefined; +pub var extraQuadInfos: main.List(ExtraQuadInfo) = undefined; pub var models: main.List(Model) = undefined; pub var fullCube: u16 = undefined; var quadDeduplication: std.AutoHashMap([@sizeOf(QuadInfo)]u8, u16) = undefined; -fn addQuad(info: QuadInfo) u16 { // TODO: Merge duplicates +fn addQuad(info: QuadInfo) u16 { if(quadDeduplication.get(std.mem.toBytes(info))) |id| { return id; } const index: u16 = @intCast(quads.items.len); quads.append(info); quadDeduplication.put(std.mem.toBytes(info), index) catch unreachable; + + var extraQuadInfo: ExtraQuadInfo = undefined; + extraQuadInfo.faceNeighbor = Model.getFaceNeighbor(&info); + extraQuadInfo.isFullQuad = Model.fullyOccludesNeighbor(&info); + { + var zeroes: @Vector(3, u32) = .{0, 0, 0}; + var ones: @Vector(3, u32) = .{0, 0, 0}; + for(info.corners) |corner| { + zeroes += @select(u32, corner == @as(Vec3f, @splat(0)), .{1, 1, 1}, .{0, 0, 0}); + ones += @select(u32, corner == @as(Vec3f, @splat(1)), .{1, 1, 1}, .{0, 0, 0}); + } + const cornerValues = @reduce(.Add, zeroes) + @reduce(.Add, ones); + extraQuadInfo.hasOnlyCornerVertices = cornerValues == 4*3; + } + { + extraQuadInfo.alignedNormalDirection = null; + if(@reduce(.And, info.normal == Vec3f{-1, 0, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirNegX; + if(@reduce(.And, info.normal == Vec3f{1, 0, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirPosX; + if(@reduce(.And, info.normal == Vec3f{0, -1, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirNegY; + if(@reduce(.And, info.normal == Vec3f{0, 1, 0})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirPosY; + if(@reduce(.And, info.normal == Vec3f{0, 0, -1})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirDown; + if(@reduce(.And, info.normal == Vec3f{0, 0, 1})) extraQuadInfo.alignedNormalDirection = chunk.Neighbors.dirUp; + } + extraQuadInfos.append(extraQuadInfo); + return index; } @@ -278,6 +311,7 @@ fn openBox(min: Vec3f, max: Vec3f, uvOffset: Vec2f, openSide: enum{x, y, z}) [4] pub fn init() void { models = main.List(Model).init(main.globalAllocator); quads = main.List(QuadInfo).init(main.globalAllocator); + extraQuadInfos = main.List(ExtraQuadInfo).init(main.globalAllocator); quadDeduplication = std.AutoHashMap([@sizeOf(QuadInfo)]u8, u16).init(main.globalAllocator.allocator); nameToIndex = std.StringHashMap(u16).init(main.globalAllocator.allocator); @@ -361,5 +395,6 @@ pub fn deinit() void { } models.deinit(); quads.deinit(); + extraQuadInfos.deinit(); quadDeduplication.deinit(); } \ No newline at end of file diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index ddf9efd5..3a7314d2 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -303,9 +303,61 @@ const PrimitiveMesh = struct { return result; } + fn getCornerLightAligned(parent: *ChunkMesh, pos: Vec3i, direction: u3) [6]u8 { // Fast path for algined normals, leading to 4 instead of 8 light samples. + const normal: Vec3f = @floatFromInt(Vec3i{chunk.Neighbors.relX[direction], chunk.Neighbors.relY[direction], chunk.Neighbors.relZ[direction]}); + const lightPos = @as(Vec3f, @floatFromInt(pos)) + normal*@as(Vec3f, @splat(0.5)) - @as(Vec3f, @splat(0.5)); + const startPos: Vec3i = @intFromFloat(@floor(lightPos)); + var val: [6]f32 = .{0, 0, 0, 0, 0, 0}; + var dx: i32 = 0; + while(dx <= 1): (dx += 1) { + var dy: i32 = 0; + while(dy <= 1): (dy += 1) { + const weight: f32 = 1.0/4.0; + const finalPos = startPos +% @as(Vec3i, @intCast(@abs(chunk.Neighbors.textureX[direction])))*@as(Vec3i, @splat(dx)) +% @as(Vec3i, @intCast(@abs(chunk.Neighbors.textureY[direction]*@as(Vec3i, @splat(dy))))); + const lightVal: [6]u8 = getLightAt(parent, finalPos[0], finalPos[1], finalPos[2]); + for(0..6) |i| { + val[i] += @as(f32, @floatFromInt(lightVal[i]))*weight; + } + } + } + var result: [6]u8 = undefined; + for(0..6) |i| { + result[i] = std.math.lossyCast(u8, val[i]); + } + return result; + } + + fn packLightValues(rawVals: [4][6]u5) [4]u32 { + var result: [4]u32 = undefined; + for(0..4) |i| { + result[i] = ( + @as(u32, rawVals[i][0]) << 25 | + @as(u32, rawVals[i][1]) << 20 | + @as(u32, rawVals[i][2]) << 15 | + @as(u32, rawVals[i][3]) << 10 | + @as(u32, rawVals[i][4]) << 5 | + @as(u32, rawVals[i][5]) << 0 + ); + } + return result; + } + fn getLight(parent: *ChunkMesh, blockPos: Vec3i, quadIndex: u16) [4]u32 { - // TODO: This is doing 12 interpolations of 8 values each. For full cube models only 4 interpolations or 4 values each would be needed. const normal = models.quads.items[quadIndex].normal; + if(models.extraQuadInfos.items[quadIndex].hasOnlyCornerVertices) { // Fast path for simple quads. + var rawVals: [4][6]u5 = undefined; + for(0..4) |i| { + const vertexPos = models.quads.items[quadIndex].corners[i]; + const fullPos = blockPos +% @as(Vec3i, @intFromFloat(vertexPos)); + const fullValues = if(models.extraQuadInfos.items[quadIndex].alignedNormalDirection) |dir| + getCornerLightAligned(parent, fullPos, dir) + else getCornerLight(parent, fullPos, normal); + for(0..6) |j| { + rawVals[i][j] = std.math.lossyCast(u5, fullValues[j]/8); + } + } + return packLightValues(rawVals); + } var cornerVals: [2][2][2][6]u8 = undefined; { var dx: u31 = 0; @@ -314,7 +366,9 @@ const PrimitiveMesh = struct { while(dy <= 1) : (dy += 1) { var dz: u31 = 0; while(dz <= 1) : (dz += 1) { - cornerVals[dx][dy][dz] = getCornerLight(parent, blockPos +% Vec3i{dx, dy, dz}, normal); + cornerVals[dx][dy][dz] = if(models.extraQuadInfos.items[quadIndex].alignedNormalDirection) |dir| + getCornerLightAligned(parent, blockPos +% Vec3i{dx, dy, dz}, dir) + else getCornerLight(parent, blockPos +% Vec3i{dx, dy, dz}, normal); } } } @@ -346,18 +400,7 @@ const PrimitiveMesh = struct { rawVals[i][j] = std.math.lossyCast(u5, val[j]/8); } } - var result: [4]u32 = undefined; - for(0..4) |i| { - result[i] = ( - @as(u32, rawVals[i][0]) << 25 | - @as(u32, rawVals[i][1]) << 20 | - @as(u32, rawVals[i][2]) << 15 | - @as(u32, rawVals[i][3]) << 10 | - @as(u32, rawVals[i][4]) << 5 | - @as(u32, rawVals[i][5]) << 0 - ); - } - return result; + return packLightValues(rawVals); } fn uploadData(self: *PrimitiveMesh, isNeighborLod: [6]bool) void {