Better occlusion and lighting for non-cube models.

Full faces of non-cube models now block light and occlude neighbor faces, whereas the others will let light through.
This does make light propapagation significantly (~40%) slower though due to the extra logic involved.

Closes #295
This commit is contained in:
IntegratedQuantum 2024-03-30 22:12:08 +01:00
parent 0bee580059
commit 4ab40cc85b
3 changed files with 56 additions and 37 deletions

View File

@ -30,6 +30,7 @@ pub const Model = struct {
max: Vec3f,
internalQuads: []u16,
neighborFacingQuads: [6][]u16,
isNeighborOccluded: [6]bool,
fn getFaceNeighbor(quad: *const QuadInfo) ?u3 {
var allZero: @Vector(3, bool) = .{true, true, true};
@ -47,6 +48,19 @@ pub const Model = struct {
return null;
}
fn fullyOccludesNeighbor(quad: *const QuadInfo) bool {
var zeroes: @Vector(3, u32) = .{0, 0, 0};
var ones: @Vector(3, u32) = .{0, 0, 0};
for(quad.corners) |corner| {
zeroes += @select(u32, approxEqAbs(corner, @splat(0), @splat(0.0001)), .{1, 1, 1}, .{0, 0, 0});
ones += @select(u32, approxEqAbs(corner, @splat(1), @splat(0.0001)), .{1, 1, 1}, .{0, 0, 0});
}
// For full coverage there will 2 ones and 2 zeroes for two components, while the other one is constant.
const hasTwoZeroes = zeroes == @Vector(3, u32){2, 2, 2};
const hasTwoOnes = ones == @Vector(3, u32){2, 2, 2};
return @popCount(@as(u3, @bitCast(hasTwoOnes))) == 2 and @popCount(@as(u3, @bitCast(hasTwoZeroes))) == 2;
}
pub fn init(quadInfos: []const QuadInfo) u16 {
const modelIndex: u16 = @intCast(models.items.len);
const self = models.addOne();
@ -54,6 +68,7 @@ pub const Model = struct {
var internalAmount: usize = 0;
self.min = .{1, 1, 1};
self.max = .{0, 0, 0};
self.isNeighborOccluded = .{false} ** 6;
for(quadInfos) |*quad| {
for(quad.corners) |corner| {
self.min = @min(self.min, corner);
@ -88,6 +103,13 @@ pub const Model = struct {
internalIndex += 1;
}
}
for(0..6) |neighbor| {
for(self.neighborFacingQuads[neighbor]) |quad| {
if(fullyOccludesNeighbor(&quads.items[quad])) {
self.isNeighborOccluded[neighbor] = true;
}
}
}
return modelIndex;
}

View File

@ -558,12 +558,11 @@ pub const ChunkMesh = struct {
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
const rotatedModel = blocks.meshes.model(block);
const model = &models.models.items[rotatedModel];
_ = neighbor;
_ = model; // TODO: Check if the neighbor model occludes this one. (maybe not that relevant)
return block.typ != 0 and (
other.typ == 0
or (!std.meta.eql(block, other) and other.viewThrough())
or blocks.meshes.model(other) != 0
or !models.models.items[blocks.meshes.model(other)].isNeighborOccluded[neighbor ^ 1]
);
}

View File

@ -74,6 +74,31 @@ pub const ChannelChunk = struct {
return .{self.data[index][0].load(.Unordered), self.data[index][1].load(.Unordered), self.data[index][2].load(.Unordered)};
}
fn calculateIncomingOcclusion(result: *[3]u8, block: blocks.Block, voxelSize: u31, neighbor: usize) void {
if(main.models.models.items[blocks.meshes.model(block)].isNeighborOccluded[neighbor]) {
var absorption: [3]u8 = extractColor(block.absorption());
absorption[0] *|= @intCast(voxelSize);
absorption[1] *|= @intCast(voxelSize);
absorption[2] *|= @intCast(voxelSize);
result[0] -|= absorption[0];
result[1] -|= absorption[1];
result[2] -|= absorption[2];
}
}
fn calculateOutgoingOcclusion(result: *[3]u8, block: blocks.Block, voxelSize: u31, neighbor: usize) void {
const model = &main.models.models.items[blocks.meshes.model(block)];
if(model.isNeighborOccluded[neighbor] and !model.isNeighborOccluded[neighbor ^ 1]) { // Avoid calculating the absorption twice.
var absorption: [3]u8 = extractColor(block.absorption());
absorption[0] *|= @intCast(voxelSize);
absorption[1] *|= @intCast(voxelSize);
absorption[2] *|= @intCast(voxelSize);
result[0] -|= absorption[0];
result[1] -|= absorption[1];
result[2] -|= absorption[2];
}
}
fn propagateDirect(self: *ChannelChunk, lightQueue: *main.utils.CircularBufferQueue(Entry)) void {
var neighborLists: [6]main.ListUnmanaged(Entry) = .{.{}} ** 6;
defer {
@ -110,19 +135,14 @@ pub const ChannelChunk = struct {
result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
}
calculateOutgoingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor);
if(result.value[0] == 0 and result.value[1] == 0 and result.value[2] == 0) continue;
if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) {
neighborLists[neighbor].append(main.stackAllocator, result);
continue;
}
const neighborIndex = chunk.getIndex(nx, ny, nz);
var absorption: [3]u8 = extractColor(self.ch.blocks[neighborIndex].absorption());
absorption[0] *|= @intCast(self.ch.pos.voxelSize);
absorption[1] *|= @intCast(self.ch.pos.voxelSize);
absorption[2] *|= @intCast(self.ch.pos.voxelSize);
result.value[0] -|= absorption[0];
result.value[1] -|= absorption[1];
result.value[2] -|= absorption[2];
calculateIncomingOcclusion(&result.value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor ^ 1);
if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.enqueue(result);
}
}
@ -196,18 +216,13 @@ pub const ChannelChunk = struct {
result.value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
result.value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
}
calculateOutgoingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor);
if(nx < 0 or nx >= chunk.chunkSize or ny < 0 or ny >= chunk.chunkSize or nz < 0 or nz >= chunk.chunkSize) {
neighborLists[neighbor].append(main.stackAllocator, result);
continue;
}
const neighborIndex = chunk.getIndex(nx, ny, nz);
var absorption: [3]u8 = extractColor(self.ch.blocks[neighborIndex].absorption());
absorption[0] *|= @intCast(self.ch.pos.voxelSize);
absorption[1] *|= @intCast(self.ch.pos.voxelSize);
absorption[2] *|= @intCast(self.ch.pos.voxelSize);
result.value[0] -|= absorption[0];
result.value[1] -|= absorption[1];
result.value[2] -|= absorption[2];
calculateIncomingOcclusion(&result.value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor ^ 1);
lightQueue.enqueue(result);
}
}
@ -233,13 +248,7 @@ pub const ChannelChunk = struct {
for(lights) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z);
var result = entry;
var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption());
absorption[0] *|= @intCast(self.ch.pos.voxelSize);
absorption[1] *|= @intCast(self.ch.pos.voxelSize);
absorption[2] *|= @intCast(self.ch.pos.voxelSize);
result.value[0] -|= absorption[0];
result.value[1] -|= absorption[1];
result.value[2] -|= absorption[2];
calculateIncomingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, entry.sourceDir);
if(result.value[0] != 0 or result.value[1] != 0 or result.value[2] != 0) lightQueue.enqueue(result);
}
self.propagateDirect(lightQueue);
@ -250,13 +259,7 @@ pub const ChannelChunk = struct {
for(lights) |entry| {
const index = chunk.getIndex(entry.x, entry.y, entry.z);
var result = entry;
var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption());
absorption[0] *|= @intCast(self.ch.pos.voxelSize);
absorption[1] *|= @intCast(self.ch.pos.voxelSize);
absorption[2] *|= @intCast(self.ch.pos.voxelSize);
result.value[0] -|= absorption[0];
result.value[1] -|= absorption[1];
result.value[2] -|= absorption[2];
calculateIncomingOcclusion(&result.value, self.ch.blocks[index], self.ch.pos.voxelSize, entry.sourceDir);
lightQueue.enqueue(result);
}
return self.propagateDestructive(lightQueue, constructiveEntries, false);
@ -311,14 +314,9 @@ pub const ChannelChunk = struct {
value[1] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
value[2] -|= 8*|@as(u8, @intCast(self.ch.pos.voxelSize));
}
calculateOutgoingOcclusion(&value, self.ch.blocks[neighborIndex], self.ch.pos.voxelSize, neighbor);
if(value[0] == 0 and value[1] == 0 and value[2] == 0) continue;
var absorption: [3]u8 = extractColor(self.ch.blocks[index].absorption());
absorption[0] *|= @intCast(self.ch.pos.voxelSize);
absorption[1] *|= @intCast(self.ch.pos.voxelSize);
absorption[2] *|= @intCast(self.ch.pos.voxelSize);
value[0] -|= absorption[0];
value[1] -|= absorption[1];
value[2] -|= absorption[2];
calculateIncomingOcclusion(&value, self.ch.blocks[index], self.ch.pos.voxelSize, neighbor ^ 1);
if(value[0] != 0 or value[1] != 0 or value[2] != 0) lightQueue.enqueue(.{.x = @intCast(x), .y = @intCast(y), .z = @intCast(z), .value = value, .sourceDir = @intCast(neighbor), .activeValue = 0b111});
}
}