From 26f4dd2ff2a9cc2f4988e9c97f5a22633a4ccdc5 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sat, 1 Oct 2022 16:46:58 +0200 Subject: [PATCH] Fix chunk meshes at lod borders. --- assets/cubyz/shaders/chunks/chunk_vertex.vs | 28 ++++++---- src/chunk.zig | 58 +++++++++++++++++++-- src/renderer.zig | 37 +++++++++++-- 3 files changed, 102 insertions(+), 21 deletions(-) diff --git a/assets/cubyz/shaders/chunks/chunk_vertex.vs b/assets/cubyz/shaders/chunks/chunk_vertex.vs index f6fb2b93..f864ac1f 100644 --- a/assets/cubyz/shaders/chunks/chunk_vertex.vs +++ b/assets/cubyz/shaders/chunks/chunk_vertex.vs @@ -5,7 +5,7 @@ out vec2 outTexCoord; flat out float textureIndex; out float outNormalVariation; - +uniform int visibilityMask; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform vec3 modelPosition; @@ -46,13 +46,13 @@ const vec3[6] normals = vec3[6]( vec3(0, 0, 1), vec3(0, 0, -1) ); -const vec3[6] positionOffset = vec3[6]( - vec3(0, 0, 0), - vec3(0, 1, 0), - vec3(0, 0, 0), - vec3(1, 0, 0), - vec3(0, 0, 0), - vec3(0, 0, 1) +const ivec3[6] positionOffset = ivec3[6]( + ivec3(0, 0, 0), + ivec3(0, 1, 0), + ivec3(0, 0, 0), + ivec3(1, 0, 0), + ivec3(0, 0, 0), + ivec3(0, 0, 1) ); const ivec3[6] textureX = ivec3[6]( ivec3(1, 0, 0), @@ -81,14 +81,20 @@ void main() { textureIndex = texCoordz + time / animationTimes[texCoordz] % animationFrames[texCoordz]; outTexCoord = vec2(float(vertexID>>1 & 1)*voxelSize, float(vertexID & 1)*voxelSize); - vec3 position = vec3( + ivec3 position = ivec3( encodedPosition & 31, encodedPosition >> 5 & 31, encodedPosition >> 10 & 31 ); + int octantIndex = (position.x >> 4) | (position.y >> 4)<<1 | (position.z >> 4)<<2; + if((visibilityMask & 1<>1 & 1)*textureX[normal]; - position += vec3(equal(textureY[normal], ivec3(-1, -1, -1))) + (vertexID & 1)*textureY[normal]; + position += ivec3(equal(textureX[normal], ivec3(-1, -1, -1))) + (vertexID>>1 & 1)*textureX[normal]; + position += ivec3(equal(textureY[normal], ivec3(-1, -1, -1))) + (vertexID & 1)*textureY[normal]; vec3 globalPosition = position*voxelSize + modelPosition; diff --git a/src/chunk.zig b/src/chunk.zig index 667706bb..1cd479b4 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -10,6 +10,7 @@ const Shader = graphics.Shader; const SSBO = graphics.SSBO; const main = @import("main.zig"); const renderer = @import("renderer.zig"); +const settings = @import("settings.zig"); const vec = @import("vec.zig"); const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; @@ -535,6 +536,7 @@ pub const meshing = struct { @"waterFog.color": c_int, @"waterFog.density": c_int, time: c_int, + visibilityMask: c_int, } = undefined; var vao: c_uint = undefined; var vbo: c_uint = undefined; @@ -600,6 +602,7 @@ pub const meshing = struct { vertexCount: u31 = 0, generated: bool = false, mutex: std.Thread.Mutex = std.Thread.Mutex{}, + visibilityMask: u8 = 0xff, pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh { return ChunkMesh{ @@ -664,11 +667,10 @@ pub const meshing = struct { pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void { std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. if(self.chunk == null) return; // In the mean-time the mesh was discarded and recreated and all the data was lost. - self.generated = true; self.faces.shrinkRetainingCapacity(self.coreCount); for(Neighbors.iterable) |neighbor| { self.neighborStart[neighbor] = @intCast(u31, self.faces.items.len); - var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, neighbor); + var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, self.pos.voxelSize, neighbor); if(nullNeighborMesh) |neighborMesh| { std.debug.assert(neighborMesh != self); neighborMesh.mutex.lock(); @@ -726,16 +728,61 @@ pub const meshing = struct { } neighborMesh.vertexCount = @intCast(u31, 6*(neighborMesh.faces.items.len-1)/2); neighborMesh.faceData.bufferData(u32, neighborMesh.faces.items); - } else { - // TODO: Resolution boundary. + continue; + } + } + // lod border: + if(self.pos.voxelSize == 1 << settings.highestLOD) continue; + var neighborMesh = renderer.RenderStructure.getNeighbor(self.pos, 2*self.pos.voxelSize, neighbor) orelse return error.LODMissing; + neighborMesh.mutex.lock(); + defer neighborMesh.mutex.unlock(); + if(neighborMesh.generated) { + const x3: u8 = if(neighbor & 1 == 0) @intCast(u8, chunkMask) else 0; + const offsetX = @divExact(self.pos.wx, self.pos.voxelSize) & chunkSize; + const offsetY = @divExact(self.pos.wy, self.pos.voxelSize) & chunkSize; + const offsetZ = @divExact(self.pos.wz, self.pos.voxelSize) & chunkSize; + var x1: u8 = 0; + while(x1 < chunkSize): (x1 += 1) { + var x2: u8 = 0; + while(x2 < chunkSize): (x2 += 1) { + var x: u8 = undefined; + var y: u8 = undefined; + var z: u8 = undefined; + if(Neighbors.relX[neighbor] != 0) { + x = x3; + y = x1; + z = x2; + } else if(Neighbors.relY[neighbor] != 0) { + x = x1; + y = x3; + z = x2; + } else { + x = x2; + y = x1; + z = x3; + } + var otherX = @intCast(u8, (x+%Neighbors.relX[neighbor]+%offsetX >> 1) & chunkMask); + var otherY = @intCast(u8, (y+%Neighbors.relY[neighbor]+%offsetY >> 1) & chunkMask); + var otherZ = @intCast(u8, (z+%Neighbors.relZ[neighbor]+%offsetZ >> 1) & chunkMask); + var block = (&self.chunk.?.blocks)[getIndex(x, y, z)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. + var otherBlock = (&neighborMesh.chunk.?.blocks)[getIndex(otherX, otherY, otherZ)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. + if(block.typ == 0 and otherBlock.typ != 0) { // TODO: Transparency + const normal: u32 = neighbor ^ 1; + const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10; + const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor] | normal<<24; + try self.faces.append(position); + try self.faces.append(textureNormal); + } + } } } else { - // TODO: Resolution boundary. + return error.LODMissing; } } self.neighborStart[6] = @intCast(u31, self.faces.items.len); self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2); self.faceData.bufferData(u32, self.faces.items); + self.generated = true; } pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { @@ -749,6 +796,7 @@ pub const meshing = struct { @floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y), @floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z) ); + c.glUniform1i(uniforms.visibilityMask, self.visibilityMask); self.faceData.bind(3); c.glDrawElements(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null); } diff --git a/src/renderer.zig b/src/renderer.zig index 745eec0a..0ad5f728 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -493,11 +493,12 @@ pub const RenderStructure = struct { return storageLists[lod][@intCast(usize, index)]; } - pub fn getNeighbor(_pos: chunk.ChunkPosition, neighbor: u3) ?*chunk.meshing.ChunkMesh { + pub fn getNeighbor(_pos: chunk.ChunkPosition, resolution: chunk.UChunkCoordinate, neighbor: u3) ?*chunk.meshing.ChunkMesh { var pos = _pos; pos.wx += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relX[neighbor]; pos.wy += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relY[neighbor]; pos.wz += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relZ[neighbor]; + pos.voxelSize = resolution; var node = _getNode(pos) orelse return null; return &node.mesh; } @@ -518,6 +519,7 @@ pub const RenderStructure = struct { const lod = @intCast(u5, _lod); var maxRenderDistance = renderDistance*chunk.chunkSize << lod; if(lod != 0) maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, maxRenderDistance)*LODFactor)); + var sizeShift = chunk.chunkShift + lod; const size = @intCast(chunk.UChunkCoordinate, chunk.chunkSize << lod); const mask: chunk.ChunkCoordinate = size - 1; const invMask: chunk.ChunkCoordinate = ~mask; @@ -576,12 +578,13 @@ pub const RenderStructure = struct { .x = @intToFloat(f32, size), .y = @intToFloat(f32, size), .z = @intToFloat(f32, size), - }) and node.?.drawableChildren < 8) { // TODO: Case where more than 0 and less than 8 exist. + }) and node.?.mesh.visibilityMask != 0) { try meshes.append(&node.?.mesh); } if(lod+1 != storageLists.len and node.?.mesh.generated) { if(_getNode(.{.wx=x, .wy=y, .wz=z, .voxelSize=@as(chunk.UChunkCoordinate, 1)<<(lod+1)})) |parent| { - parent.drawableChildren += 1; + const octantIndex = @intCast(u3, (x>>sizeShift & 1) | (y>>sizeShift & 1)<<1 | (z>>sizeShift & 1)<<2); + parent.mesh.visibilityMask &= ~(@as(u8, 1) << octantIndex); } } node.?.drawableChildren = 0; @@ -603,6 +606,22 @@ pub const RenderStructure = struct { for(oldList) |nullMesh| { if(nullMesh) |mesh| { if(mesh.shouldBeRemoved) { + if(mesh.mesh.pos.voxelSize != 1 << settings.highestLOD) { + if(_getNode(.{.wx=mesh.mesh.pos.wx, .wy=mesh.mesh.pos.wy, .wz=mesh.mesh.pos.wz, .voxelSize=2*mesh.mesh.pos.voxelSize})) |parent| { + const octantIndex = @intCast(u3, (mesh.mesh.pos.wx>>sizeShift & 1) | (mesh.mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.mesh.pos.wz>>sizeShift & 1)<<2); + parent.mesh.visibilityMask |= @as(u8, 1) << octantIndex; + } + } + // Update the neighbors, so we don't get cracks when we look back: + for(chunk.Neighbors.iterable) |neighbor| { + if(getNeighbor(mesh.mesh.pos, mesh.mesh.pos.voxelSize, neighbor)) |neighborMesh| { + if(neighborMesh.generated) { + neighborMesh.mutex.lock(); + defer neighborMesh.mutex.unlock(); + try neighborMesh.uploadDataAndFinishNeighbors(); + } + } + } if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing. mesh.mesh.mutex.unlock(); mesh.mesh.deinit(); @@ -641,14 +660,22 @@ pub const RenderStructure = struct { mutex.lock(); defer mutex.unlock(); while(updatableList.items.len != 0) { - const pos = updatableList.pop(); + const pos = updatableList.orderedRemove(0); mutex.unlock(); defer mutex.lock(); const nullNode = _getNode(pos); if(nullNode) |node| { node.mesh.mutex.lock(); defer node.mesh.mutex.unlock(); - try node.mesh.uploadDataAndFinishNeighbors(); + node.mesh.uploadDataAndFinishNeighbors() catch |err| { + if(err == error.LODMissing) { + mutex.lock(); + defer mutex.unlock(); + try updatableList.append(pos); + } else { + return err; + } + }; } if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. }