mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-09 03:59:53 -04:00
Make flying through caves fun again (removes CPU-side occlusion culling)
This is not quite the performance improvement I had planned. On the GPU-side the raster occlusion culling step got more expensive, since there now are more chunks checked on average. On the CPU-side the cost of traversing all the occluded chunks is roughly equal to the cost of the previous occlusion culling heuristic. On the bright side the cost is now constant, whereas previously looking into the sky was twice as expensive. fixes #526 fixes #514 fixes #259
This commit is contained in:
parent
d7055f6998
commit
e9fdad732d
@ -174,7 +174,6 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
|
||||
|
||||
// Uses FrustumCulling on the chunks.
|
||||
const frustum = Frustum.init(Vec3f{0, 0, 0}, game.camera.viewMatrix, lastFov, lastWidth, lastHeight);
|
||||
_ = frustum;
|
||||
|
||||
const time: u32 = @intCast(std.time.milliTimestamp() & std.math.maxInt(u32));
|
||||
|
||||
@ -196,7 +195,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
|
||||
|
||||
chunk_meshing.quadsDrawn = 0;
|
||||
chunk_meshing.transparentQuadsDrawn = 0;
|
||||
const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, playerPos, settings.renderDistance);
|
||||
const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, &frustum, playerPos, settings.renderDistance);
|
||||
|
||||
gpu_performance_measuring.startQuery(.chunk_rendering_preparation);
|
||||
const direction = crosshairDirection(game.camera.viewMatrix, lastFov, lastWidth, lastHeight);
|
||||
|
@ -658,17 +658,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
|
||||
self.distance = @abs(fullDx) + @abs(fullDy) + @abs(fullDz);
|
||||
}
|
||||
};
|
||||
const BoundingRectToNeighborChunk = struct {
|
||||
min: Vec3i = @splat(std.math.maxInt(i32)),
|
||||
max: Vec3i = @splat(0),
|
||||
|
||||
fn adjustToBlock(self: *BoundingRectToNeighborChunk, block: Block, pos: Vec3i, neighbor: chunk.Neighbor) void {
|
||||
if(block.viewThrough()) {
|
||||
self.min = @min(self.min, pos);
|
||||
self.max = @max(self.max, pos + neighbor.orthogonalComponents());
|
||||
}
|
||||
}
|
||||
};
|
||||
pos: chunk.ChunkPosition,
|
||||
size: i32,
|
||||
chunk: *chunk.Chunk,
|
||||
@ -695,8 +684,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
|
||||
min: Vec3f = undefined,
|
||||
max: Vec3f = undefined,
|
||||
|
||||
chunkBorders: [6]BoundingRectToNeighborChunk = [1]BoundingRectToNeighborChunk{.{}} ** 6,
|
||||
|
||||
pub fn init(self: *ChunkMesh, pos: chunk.ChunkPosition, ch: *chunk.Chunk) void {
|
||||
self.* = ChunkMesh{
|
||||
.pos = pos,
|
||||
@ -1184,19 +1171,6 @@ pub const ChunkMesh = struct { // MARK: ChunkMesh
|
||||
}
|
||||
}
|
||||
|
||||
// Check out the borders:
|
||||
var x: u8 = 0;
|
||||
while(x < chunk.chunkSize): (x += 1) {
|
||||
var y: u8 = 0;
|
||||
while(y < chunk.chunkSize): (y += 1) {
|
||||
self.chunkBorders[chunk.Neighbor.dirNegX.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(0, x, y)), .{0, x, y}, chunk.Neighbor.dirNegX);
|
||||
self.chunkBorders[chunk.Neighbor.dirPosX.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(chunk.chunkSize-1, x, y)), .{chunk.chunkSize, x, y}, chunk.Neighbor.dirPosX);
|
||||
self.chunkBorders[chunk.Neighbor.dirNegY.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, 0, y)), .{x, 0, y}, chunk.Neighbor.dirNegY);
|
||||
self.chunkBorders[chunk.Neighbor.dirPosY.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, chunk.chunkSize-1, y)), .{x, chunk.chunkSize, y}, chunk.Neighbor.dirPosY);
|
||||
self.chunkBorders[chunk.Neighbor.dirDown.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, y, 0)), .{x, y, 0}, chunk.Neighbor.dirDown);
|
||||
self.chunkBorders[chunk.Neighbor.dirUp.toInt()].adjustToBlock(self.chunk.data.getValue(chunk.getIndex(x, y, chunk.chunkSize-1)), .{x, y, chunk.chunkSize}, chunk.Neighbor.dirUp);
|
||||
}
|
||||
}
|
||||
self.mutex.unlock();
|
||||
|
||||
self.finishNeighbors(lightRefreshList);
|
||||
|
@ -24,8 +24,6 @@ const chunk_meshing = @import("chunk_meshing.zig");
|
||||
const ChunkMeshNode = struct {
|
||||
mesh: ?*chunk_meshing.ChunkMesh = null,
|
||||
lod: u3 = undefined,
|
||||
min: Vec2f = undefined,
|
||||
max: Vec2f = undefined,
|
||||
active: bool = false,
|
||||
rendered: bool = false,
|
||||
finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing
|
||||
@ -536,7 +534,7 @@ fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: i32, meshR
|
||||
}
|
||||
}
|
||||
|
||||
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks()
|
||||
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *const main.renderer.Frustum, playerPos: Vec3d, renderDistance: i32) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks()
|
||||
meshList.clearRetainingCapacity();
|
||||
if(lastRD != renderDistance) {
|
||||
network.Protocols.genericUpdate.sendRenderDistance(conn, renderDistance);
|
||||
@ -565,9 +563,9 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items);
|
||||
network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items, .{lastPx, lastPy, lastPz});
|
||||
|
||||
// Does occlusion using a breadth-first search that caches an on-screen visibility rectangle.
|
||||
// Finds all visible chunks and lod chunks using a breadth-first search.
|
||||
|
||||
const OcclusionData = struct {
|
||||
const SearchData = struct {
|
||||
node: *ChunkMeshNode,
|
||||
distance: f64,
|
||||
|
||||
@ -578,8 +576,7 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Is there a way to combine this with minecraft's approach?
|
||||
var searchList = std.PriorityQueue(OcclusionData, void, OcclusionData.compare).init(main.stackAllocator.allocator, {});
|
||||
var searchList = std.PriorityQueue(SearchData, void, SearchData.compare).init(main.stackAllocator.allocator, {});
|
||||
defer searchList.deinit();
|
||||
{
|
||||
var firstPos = chunk.ChunkPosition{
|
||||
@ -597,8 +594,6 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
const hasMesh = node.finishedMeshing;
|
||||
if(hasMesh) {
|
||||
node.lod = lod;
|
||||
node.min = @splat(-1);
|
||||
node.max = @splat(1);
|
||||
node.active = true;
|
||||
node.rendered = true;
|
||||
searchList.add(.{
|
||||
@ -615,168 +610,32 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
}
|
||||
var nodeList = main.List(*ChunkMeshNode).init(main.stackAllocator);
|
||||
defer nodeList.deinit();
|
||||
const projRotMat = game.projectionMatrix.mul(game.camera.viewMatrix);
|
||||
while(searchList.removeOrNull()) |data| {
|
||||
std.debug.assert(data.node.finishedMeshing);
|
||||
nodeList.append(data.node);
|
||||
data.node.active = false;
|
||||
|
||||
mutex.lock();
|
||||
const mesh = data.node.mesh orelse {
|
||||
mutex.unlock();
|
||||
continue;
|
||||
};
|
||||
mesh.increaseRefCount();
|
||||
defer mesh.decreaseRefCount();
|
||||
mutex.unlock();
|
||||
const mesh = data.node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
|
||||
const pos = mesh.pos;
|
||||
|
||||
mesh.visibilityMask = 0xff;
|
||||
const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{mesh.pos.wx, mesh.pos.wy, mesh.pos.wz})) - playerPos;
|
||||
const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{pos.wx, pos.wy, pos.wz})) - playerPos;
|
||||
const relPosFloat: Vec3f = @floatCast(relPos);
|
||||
var isNeighborLod: [6]bool = .{false} ** 6;
|
||||
for(chunk.Neighbor.iterable) |neighbor| continueNeighborLoop: {
|
||||
neighborLoop: for(chunk.Neighbor.iterable) |neighbor| {
|
||||
const component = neighbor.extractDirectionComponent(relPosFloat);
|
||||
if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*mesh.pos.voxelSize)) <= 0) continue;
|
||||
if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*pos.voxelSize)) <= 0) continue;
|
||||
if(!neighbor.isPositive() and component >= 0) continue;
|
||||
if(@reduce(.Or, mesh.chunkBorders[neighbor.toInt()].max < mesh.chunkBorders[neighbor.toInt()].min)) continue; // There was not a single transparent block along the chunk border. TODO: Find a better solution.
|
||||
const minVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor.toInt()].min*@as(Vec3i, @splat(mesh.pos.voxelSize)));
|
||||
const maxVec: Vec3f = @floatFromInt(mesh.chunkBorders[neighbor.toInt()].max*@as(Vec3i, @splat(mesh.pos.voxelSize)));
|
||||
var xyMin: Vec2f = .{10, 10};
|
||||
var xyMax: Vec2f = .{-10, -10};
|
||||
var numberOfNegatives: u8 = 0;
|
||||
var corners: [5]Vec4f = undefined;
|
||||
var curCorner: usize = 0;
|
||||
for(0..2) |a| {
|
||||
for(0..2) |b| {
|
||||
|
||||
var cornerVector: Vec3f = undefined;
|
||||
switch(neighbor.vectorComponent()) {
|
||||
.x => {
|
||||
cornerVector = @select(f32, @Vector(3, bool){true, a == 0, b == 0}, minVec, maxVec);
|
||||
},
|
||||
.y => {
|
||||
cornerVector = @select(f32, @Vector(3, bool){a == 0, true, b == 0}, minVec, maxVec);
|
||||
},
|
||||
.z => {
|
||||
cornerVector = @select(f32, @Vector(3, bool){a == 0, b == 0, true}, minVec, maxVec);
|
||||
},
|
||||
}
|
||||
corners[curCorner] = projRotMat.mulVec(vec.combine(relPosFloat + cornerVector, 1));
|
||||
if(corners[curCorner][3] < 0) {
|
||||
numberOfNegatives += 1;
|
||||
}
|
||||
curCorner += 1;
|
||||
}
|
||||
}
|
||||
switch(numberOfNegatives) { // Oh, so complicated. But this should only trigger very close to the player.
|
||||
4 => continue,
|
||||
0 => {},
|
||||
1 => {
|
||||
// Needs to duplicate the problematic corner and move it onto the projected plane.
|
||||
var problematicOne: usize = 0;
|
||||
for(0..curCorner) |i| {
|
||||
if(corners[i][3] < 0) {
|
||||
problematicOne = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const problematicVector = corners[problematicOne];
|
||||
// The two neighbors of the quad:
|
||||
const neighborA = corners[problematicOne ^ 1];
|
||||
const neighborB = corners[problematicOne ^ 2];
|
||||
// Move the problematic point towards the neighbor:
|
||||
const one: Vec4f = @splat(1);
|
||||
const weightA: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborA[3]));
|
||||
var towardsA = neighborA*weightA + problematicVector*(one - weightA);
|
||||
towardsA[3] = 0; // Prevent inaccuracies
|
||||
const weightB: Vec4f = @splat(problematicVector[3]/(problematicVector[3] - neighborB[3]));
|
||||
var towardsB = neighborB*weightB + problematicVector*(one - weightB);
|
||||
towardsB[3] = 0; // Prevent inaccuracies
|
||||
corners[problematicOne] = towardsA;
|
||||
corners[curCorner] = towardsB;
|
||||
curCorner += 1;
|
||||
},
|
||||
2 => {
|
||||
// Needs to move the two problematic corners onto the projected plane.
|
||||
var problematicOne: usize = undefined;
|
||||
for(0..curCorner) |i| {
|
||||
if(corners[i][3] < 0) {
|
||||
problematicOne = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const problematicVectorOne = corners[problematicOne];
|
||||
var problematicTwo: usize = undefined;
|
||||
for(problematicOne+1..curCorner) |i| {
|
||||
if(corners[i][3] < 0) {
|
||||
problematicTwo = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const problematicVectorTwo = corners[problematicTwo];
|
||||
|
||||
const commonDirection = problematicOne ^ problematicTwo;
|
||||
const projectionDirection = commonDirection ^ 0b11;
|
||||
// The respective neighbors:
|
||||
const neighborOne = corners[problematicOne ^ projectionDirection];
|
||||
const neighborTwo = corners[problematicTwo ^ projectionDirection];
|
||||
// Move the problematic points towards the neighbor:
|
||||
const one: Vec4f = @splat(1);
|
||||
const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborOne[3]));
|
||||
var towardsOne = neighborOne*weightOne + problematicVectorOne*(one - weightOne);
|
||||
towardsOne[3] = 0; // Prevent inaccuracies
|
||||
corners[problematicOne] = towardsOne;
|
||||
|
||||
const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborTwo[3]));
|
||||
var towardsTwo = neighborTwo*weightTwo + problematicVectorTwo*(one - weightTwo);
|
||||
towardsTwo[3] = 0; // Prevent inaccuracies
|
||||
corners[problematicTwo] = towardsTwo;
|
||||
},
|
||||
3 => {
|
||||
// Throw away the far problematic vector, move the other two onto the projection plane.
|
||||
var neighborIndex: usize = undefined;
|
||||
for(0..curCorner) |i| {
|
||||
if(corners[i][3] >= 0) {
|
||||
neighborIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const neighborVector = corners[neighborIndex];
|
||||
const problematicVectorOne = corners[neighborIndex ^ 1];
|
||||
const problematicVectorTwo = corners[neighborIndex ^ 2];
|
||||
// Move the problematic points towards the neighbor:
|
||||
const one: Vec4f = @splat(1);
|
||||
const weightOne: Vec4f = @splat(problematicVectorOne[3]/(problematicVectorOne[3] - neighborVector[3]));
|
||||
var towardsOne = neighborVector*weightOne + problematicVectorOne*(one - weightOne);
|
||||
towardsOne[3] = 0; // Prevent inaccuracies
|
||||
|
||||
const weightTwo: Vec4f = @splat(problematicVectorTwo[3]/(problematicVectorTwo[3] - neighborVector[3]));
|
||||
var towardsTwo = neighborVector*weightTwo + problematicVectorTwo*(one - weightTwo);
|
||||
towardsTwo[3] = 0; // Prevent inaccuracies
|
||||
|
||||
corners[0] = neighborVector;
|
||||
corners[1] = towardsOne;
|
||||
corners[2] = towardsTwo;
|
||||
curCorner = 3;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
for(0..curCorner) |i| {
|
||||
const projected = corners[i];
|
||||
const xy = vec.xy(projected)/@as(Vec2f, @splat(@max(0, projected[3])));
|
||||
xyMin = @min(xyMin, xy);
|
||||
xyMax = @max(xyMax, xy);
|
||||
}
|
||||
const min = @max(xyMin, data.node.min);
|
||||
const max = @min(xyMax, data.node.max);
|
||||
if(@reduce(.Or, min >= max)) continue; // Nothing to render.
|
||||
var neighborPos = chunk.ChunkPosition{
|
||||
.wx = mesh.pos.wx +% neighbor.relX()*chunk.chunkSize*mesh.pos.voxelSize,
|
||||
.wy = mesh.pos.wy +% neighbor.relY()*chunk.chunkSize*mesh.pos.voxelSize,
|
||||
.wz = mesh.pos.wz +% neighbor.relZ()*chunk.chunkSize*mesh.pos.voxelSize,
|
||||
.voxelSize = mesh.pos.voxelSize,
|
||||
.wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize,
|
||||
.wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize,
|
||||
.wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize,
|
||||
.voxelSize = pos.voxelSize,
|
||||
};
|
||||
if(!getNodePointer(neighborPos).active) { // Don't repeat the same frustum check all the time.
|
||||
if(!frustum.testAAB(relPosFloat + @as(Vec3f, @floatFromInt(Vec3i{neighbor.relX()*chunk.chunkSize*pos.voxelSize, neighbor.relY()*chunk.chunkSize*pos.voxelSize, neighbor.relZ()*chunk.chunkSize*pos.voxelSize})), @splat(@floatFromInt(chunk.chunkSize*pos.voxelSize))))
|
||||
continue;
|
||||
}
|
||||
var lod: u3 = data.node.lod;
|
||||
lodLoop: while(lod <= settings.highestLOD) : (lod += 1) {
|
||||
defer {
|
||||
@ -824,13 +683,8 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
if(lod != data.node.lod) {
|
||||
isNeighborLod[neighbor.toInt()] = true;
|
||||
}
|
||||
if(node.active) {
|
||||
node.min = @min(node.min, min);
|
||||
node.max = @max(node.max, max);
|
||||
} else {
|
||||
if(!node.active) {
|
||||
node.lod = lod;
|
||||
node.min = min;
|
||||
node.max = max;
|
||||
node.active = true;
|
||||
searchList.add(.{
|
||||
.node = node,
|
||||
@ -838,7 +692,7 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
}) catch unreachable;
|
||||
node.rendered = true;
|
||||
}
|
||||
break :continueNeighborLoop;
|
||||
continue :neighborLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -848,20 +702,12 @@ pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: V
|
||||
node.rendered = false;
|
||||
if(!node.finishedMeshing) continue;
|
||||
|
||||
node.mutex.lock();
|
||||
const mesh = node.mesh orelse {
|
||||
node.mutex.unlock();
|
||||
continue;
|
||||
};
|
||||
mesh.increaseRefCount();
|
||||
defer mesh.decreaseRefCount();
|
||||
node.mutex.unlock();
|
||||
const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
|
||||
|
||||
if(mesh.pos.voxelSize != @as(u31, 1) << settings.highestLOD) {
|
||||
const parent = getNodePointer(.{.wx=mesh.pos.wx, .wy=mesh.pos.wy, .wz=mesh.pos.wz, .voxelSize=mesh.pos.voxelSize << 1});
|
||||
parent.mutex.lock();
|
||||
defer parent.mutex.unlock();
|
||||
if(parent.mesh) |parentMesh| {
|
||||
if(parent.finishedMeshing) {
|
||||
const parentMesh = parent.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
|
||||
const sizeShift = chunk.chunkShift + @ctz(mesh.pos.voxelSize);
|
||||
const octantIndex: u3 = @intCast((mesh.pos.wx>>sizeShift & 1) | (mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.pos.wz>>sizeShift & 1)<<2);
|
||||
parentMesh.visibilityMask &= ~(@as(u8, 1) << octantIndex);
|
||||
|
Loading…
x
Reference in New Issue
Block a user