Optimize meshing using bitmasks to reduce the number of blocks that need to be sampled from the chunk.

Improves meshing time from 30s to just 8s at render distance 12
This commit is contained in:
IntegratedQuantum 2024-06-09 15:42:22 +02:00
parent fffb94046d
commit 5f9d740843
4 changed files with 227 additions and 31 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ logs/
saves/ saves/
zig-out/ zig-out/
zig-cache/ zig-cache/
.zig-cache/
serverAssets/ serverAssets/
settings.json settings.json
gui_layout.json gui_layout.json

View File

@ -16,5 +16,5 @@
"drops" : [], "drops" : [],
"model" : "" "model" : "none"
} }

View File

@ -42,6 +42,9 @@ pub const Model = struct {
internalQuads: []u16, internalQuads: []u16,
neighborFacingQuads: [6][]u16, neighborFacingQuads: [6][]u16,
isNeighborOccluded: [6]bool, isNeighborOccluded: [6]bool,
allNeighborsOccluded: bool,
noNeighborsOccluded: bool,
hasNeighborFacingQuads: bool,
fn getFaceNeighbor(quad: *const QuadInfo) ?u3 { fn getFaceNeighbor(quad: *const QuadInfo) ?u3 {
var allZero: @Vector(3, bool) = .{true, true, true}; var allZero: @Vector(3, bool) = .{true, true, true};
@ -130,12 +133,18 @@ pub const Model = struct {
internalIndex += 1; internalIndex += 1;
} }
} }
self.hasNeighborFacingQuads = false;
self.allNeighborsOccluded = true;
self.noNeighborsOccluded = true;
for(0..6) |neighbor| { for(0..6) |neighbor| {
for(self.neighborFacingQuads[neighbor]) |quad| { for(self.neighborFacingQuads[neighbor]) |quad| {
if(fullyOccludesNeighbor(&quads.items[quad])) { if(fullyOccludesNeighbor(&quads.items[quad])) {
self.isNeighborOccluded[neighbor] = true; self.isNeighborOccluded[neighbor] = true;
} }
} }
self.hasNeighborFacingQuads = self.hasNeighborFacingQuads or self.neighborFacingQuads[neighbor].len != 0;
self.allNeighborsOccluded = self.allNeighborsOccluded and self.isNeighborOccluded[neighbor];
self.noNeighborsOccluded = self.noNeighborsOccluded and !self.isNeighborOccluded[neighbor];
} }
return modelIndex; return modelIndex;
} }
@ -316,6 +325,8 @@ pub fn init() void {
nameToIndex = std.StringHashMap(u16).init(main.globalAllocator.allocator); nameToIndex = std.StringHashMap(u16).init(main.globalAllocator.allocator);
nameToIndex.put("none", Model.init(&.{})) catch unreachable;
const cube = Model.init(&box(.{0, 0, 0}, .{1, 1, 1}, .{0, 0})); const cube = Model.init(&box(.{0, 0, 0}, .{1, 1, 1}, .{0, 0}));
nameToIndex.put("cube", cube) catch unreachable; nameToIndex.put("cube", cube) catch unreachable;
fullCube = cube; fullCube = cube;

View File

@ -795,45 +795,229 @@ pub const ChunkMesh = struct {
} }
pub fn generateMesh(self: *ChunkMesh) void { pub fn generateMesh(self: *ChunkMesh) void {
var canSeeNeighbor: [6][chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&canSeeNeighbor), 0);
var canSeeAllNeighbors: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&canSeeAllNeighbors), 0);
var hasFaces: [chunk.chunkSize][chunk.chunkSize]u32 = undefined;
@memset(std.mem.asBytes(&hasFaces), 0);
self.mutex.lock(); self.mutex.lock();
var n: u32 = 0; const OcclusionInfo = packed struct {
var x: u8 = 0; canSeeNeighbor: u6 = 0,
while(x < chunk.chunkSize): (x += 1) { canSeeAllNeighbors: bool = false,
var y: u8 = 0; hasExternalQuads: bool = false,
while(y < chunk.chunkSize): (y += 1) { hasInternalQuads: bool = false,
var z: u8 = 0; };
while(z < chunk.chunkSize): (z += 1) { var paletteCache = main.stackAllocator.alloc(OcclusionInfo, self.chunk.data.paletteLength);
const block = self.chunk.data.getValue(chunk.getIndex(x, y, z)); defer main.stackAllocator.free(paletteCache);
if(block.typ == 0) continue; for(0..self.chunk.data.paletteLength) |i| {
// Check all neighbors: const block = self.chunk.data.palette[i];
for(chunk.Neighbors.iterable) |i| { const model = &models.models.items[blocks.meshes.model(block)];
n += 1; var result: OcclusionInfo = .{};
const x2 = x + chunk.Neighbors.relX[i]; if(model.noNeighborsOccluded or block.viewThrough()) {
const y2 = y + chunk.Neighbors.relY[i]; result.canSeeAllNeighbors = true;
const z2 = z + chunk.Neighbors.relZ[i]; } else if(!model.allNeighborsOccluded) {
if(x2&chunk.chunkMask != x2 or y2&chunk.chunkMask != y2 or z2&chunk.chunkMask != z2) continue; // Neighbor is outside the chunk. for(chunk.Neighbors.iterable) |neighbor| {
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(x2, y2, z2)); if(!model.isNeighborOccluded[neighbor]) {
if(canBeSeenThroughOtherBlock(block, neighborBlock, i)) { result.canSeeNeighbor |= @as(u6, 1) << neighbor;
if(block.transparent()) { }
if(block.hasBackFace()) { }
self.transparentMesh.appendNeighborFacingQuadsToCore(block, i ^ 1, x, y, z, true); }
} if(model.hasNeighborFacingQuads) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, i, x2, y2, z2, false); result.hasExternalQuads = true;
} else { }
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, i, x2, y2, z2, false); if(model.internalQuads.len != 0) {
result.hasInternalQuads = true;
}
paletteCache[i] = result;
}
// Generate the bitMasks:
for(0..chunk.chunkSize) |_x| {
const x: u5 = @intCast(_x);
for(0..chunk.chunkSize) |_y| {
const y: u5 = @intCast(_y);
for(0..chunk.chunkSize) |_z| {
const z: u5 = @intCast(_z);
const paletteId = self.chunk.data.data.getValue(chunk.getIndex(x, y, z));
const occlusionInfo = paletteCache[paletteId];
const setBit = @as(u32, 1) << z;
if(occlusionInfo.canSeeAllNeighbors) {
canSeeAllNeighbors[x][y] |= setBit;
} else if(occlusionInfo.canSeeNeighbor != 0) {
for(chunk.Neighbors.iterable) |neighbor| {
if(occlusionInfo.canSeeNeighbor & @as(u6, 1) << neighbor != 0) {
canSeeNeighbor[neighbor][x][y] |= setBit;
} }
} }
} }
if(block.transparent()) { if(occlusionInfo.hasExternalQuads) {
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false); hasFaces[x][y] |= setBit;
} else { }
self.opaqueMesh.appendInternalQuadsToCore(block, x, y, z, false); if(occlusionInfo.hasInternalQuads) {
const block = self.chunk.data.palette[paletteId];
if(block.transparent()) {
self.transparentMesh.appendInternalQuadsToCore(block, x, y, z, false);
} else {
self.opaqueMesh.appendInternalQuadsToCore(block, x, y, z, false);
}
} }
} }
} }
} }
// Generate the meshes:
{
const neighbor = chunk.Neighbors.dirNegX;
for(1..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x - 1][y] | canSeeAllNeighbors[x - 1][y]);
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x - 1), @intCast(y), z));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x - 1), @intCast(y), z, false);
}
}
}
}
}
{
const neighbor = chunk.Neighbors.dirPosX;
for(0..chunk.chunkSize-1) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x + 1][y] | canSeeAllNeighbors[x + 1][y]);
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x + 1), @intCast(y), z));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x + 1), @intCast(y), z, false);
}
}
}
}
}
{
const neighbor = chunk.Neighbors.dirNegY;
for(0..chunk.chunkSize) |x| {
for(1..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y - 1] | canSeeAllNeighbors[x][y - 1]);
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y - 1), z));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y - 1), z, false);
}
}
}
}
}
{
const neighbor = chunk.Neighbors.dirPosY;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize-1) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y + 1] | canSeeAllNeighbors[x][y + 1]);
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y + 1), z));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y + 1), z, false);
}
}
}
}
}
{
const neighbor = chunk.Neighbors.dirDown;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y] | canSeeAllNeighbors[x][y]) << 1;
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z - 1));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z - 1, false);
}
}
}
}
}
{
const neighbor = chunk.Neighbors.dirUp;
for(0..chunk.chunkSize) |x| {
for(0..chunk.chunkSize) |y| {
var bitMask = hasFaces[x][y] & (canSeeNeighbor[neighbor ^ 1][x][y] | canSeeAllNeighbors[x][y]) >> 1;
while(bitMask != 0) {
const z = @ctz(bitMask);
bitMask &= ~(@as(u32, 1) << @intCast(z));
const block = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z));
if(block.viewThrough() and !block.alwaysViewThrough()) { // Needs to check the neighbor block
const neighborBlock = self.chunk.data.getValue(chunk.getIndex(@intCast(x), @intCast(y), z + 1));
if(std.meta.eql(block, neighborBlock)) continue;
}
if(block.transparent()) {
if(block.hasBackFace()) {
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor ^ 1, @intCast(x), @intCast(y), z, true);
}
self.transparentMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false);
} else {
self.opaqueMesh.appendNeighborFacingQuadsToCore(block, neighbor, @intCast(x), @intCast(y), z + 1, false);
}
}
}
}
}
// Check out the borders: // Check out the borders:
x = 0; var x: u8 = 0;
while(x < chunk.chunkSize): (x += 1) { while(x < chunk.chunkSize): (x += 1) {
var y: u8 = 0; var y: u8 = 0;
while(y < chunk.chunkSize): (y += 1) { while(y < chunk.chunkSize): (y += 1) {