fix missing AABB expansion for non-AO faces

This commit is contained in:
eliotbyte 2025-11-02 17:30:30 +03:00
parent 3a78d5f334
commit 0eec97726d
2 changed files with 446 additions and 421 deletions

View file

@ -245,10 +245,17 @@ void BlocksRenderer::face(
tint *= d; tint *= d;
} }
const auto nZ2 = lights ? nZ : Z; const auto nZ2 = lights ? nZ : Z;
vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, tint, nZ2, lights ? 0 : 1); auto p0 = coord + (-X - Y + Z) * s;
vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, tint, nZ2, lights ? 0 : 1); auto p1 = coord + ( X - Y + Z) * s;
vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, tint, nZ2, lights ? 0 : 1); auto p2 = coord + ( X + Y + Z) * s;
vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, tint, nZ2, lights ? 0 : 1); auto p3 = coord + (-X + Y + Z) * s;
vertex(p0, region.u1, region.v1, tint, nZ2, lights ? 0 : 1);
vertex(p1, region.u2, region.v1, tint, nZ2, lights ? 0 : 1);
vertex(p2, region.u2, region.v2, tint, nZ2, lights ? 0 : 1);
vertex(p3, region.u1, region.v2, tint, nZ2, lights ? 0 : 1);
if (!densePass) {
expand_aabb_4(localAabb, localAabbInit, p0, p1, p2, p3);
}
index(0, 1, 2, 0, 2, 3); index(0, 1, 2, 0, 2, 3);
} }

View file

@ -1,417 +1,435 @@
#include "ChunksRenderer.hpp" #include "ChunksRenderer.hpp"
#include "BlocksRenderer.hpp" #include "BlocksRenderer.hpp"
#include "debug/Logger.hpp" #include "debug/Logger.hpp"
#include "assets/Assets.hpp" #include "assets/Assets.hpp"
#include "graphics/core/Mesh.hpp" #include "graphics/core/Mesh.hpp"
#include "graphics/core/Shader.hpp" #include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp" #include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp" #include "graphics/core/Atlas.hpp"
#include "voxels/Chunk.hpp" #include "voxels/Chunk.hpp"
#include "voxels/Chunks.hpp" #include "voxels/Chunks.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "maths/FrustumCulling.hpp" #include "maths/FrustumCulling.hpp"
#include "util/listutil.hpp" #include "util/listutil.hpp"
#include "settings.hpp" #include "settings.hpp"
#include <algorithm> #include <algorithm>
static debug::Logger logger("chunks-render"); static debug::Logger logger("chunks-render");
size_t ChunksRenderer::visibleChunks = 0; size_t ChunksRenderer::visibleChunks = 0;
namespace { namespace {
struct CullingBounds { glm::vec3 min; glm::vec3 max; }; struct CullingBounds { glm::vec3 min; glm::vec3 max; };
static constexpr float K_CHUNK_CENTER_BIAS = 0.5f; static constexpr float K_CHUNK_CENTER_BIAS = 0.5f;
// Minimal thickness to avoid culling flicker for geometry that forms 2D sheets
static inline bool has_volume(const AABB& aabb) { static constexpr float K_AABB_MIN_EXTENT = 1e-2f;
auto s = aabb.size();
return s.x > 0.0f || s.y > 0.0f || s.z > 0.0f; static inline bool has_volume(const AABB& aabb) {
} auto s = aabb.size();
return s.x > 0.0f || s.y > 0.0f || s.z > 0.0f;
static inline CullingBounds compute_chunk_culling_bounds( }
const Chunk& chunk,
const std::unordered_map<glm::ivec2, ChunkMesh>& meshes static inline CullingBounds compute_chunk_culling_bounds(
) { const Chunk& chunk,
glm::vec3 min(chunk.x * CHUNK_W, chunk.bottom, chunk.z * CHUNK_D); const std::unordered_map<glm::ivec2, ChunkMesh>& meshes
glm::vec3 max( ) {
chunk.x * CHUNK_W + CHUNK_W, glm::vec3 min(chunk.x * CHUNK_W, chunk.bottom, chunk.z * CHUNK_D);
chunk.top, glm::vec3 max(
chunk.z * CHUNK_D + CHUNK_D chunk.x * CHUNK_W + CHUNK_W,
); chunk.top,
auto it = meshes.find({chunk.x, chunk.z}); chunk.z * CHUNK_D + CHUNK_D
if (it != meshes.end()) { );
const auto& aabb = it->second.localAabb; auto it = meshes.find({chunk.x, chunk.z});
if (has_volume(aabb)) { if (it != meshes.end()) {
min = glm::vec3(chunk.x * CHUNK_W + aabb.min().x + K_CHUNK_CENTER_BIAS, const auto& aabb = it->second.localAabb;
(std::max)(static_cast<float>(chunk.bottom), aabb.min().y + K_CHUNK_CENTER_BIAS), if (has_volume(aabb)) {
chunk.z * CHUNK_D + aabb.min().z + K_CHUNK_CENTER_BIAS); // Convert to world coords (same 0.5 bias as draw model matrix)
max = glm::vec3(chunk.x * CHUNK_W + aabb.max().x + K_CHUNK_CENTER_BIAS, min = glm::vec3(chunk.x * CHUNK_W + aabb.min().x + K_CHUNK_CENTER_BIAS,
(std::min)(static_cast<float>(chunk.top), aabb.max().y + K_CHUNK_CENTER_BIAS), aabb.min().y + K_CHUNK_CENTER_BIAS,
chunk.z * CHUNK_D + aabb.max().z + K_CHUNK_CENTER_BIAS); chunk.z * CHUNK_D + aabb.min().z + K_CHUNK_CENTER_BIAS);
} max = glm::vec3(chunk.x * CHUNK_W + aabb.max().x + K_CHUNK_CENTER_BIAS,
} aabb.max().y + K_CHUNK_CENTER_BIAS,
return {min, max}; chunk.z * CHUNK_D + aabb.max().z + K_CHUNK_CENTER_BIAS);
}
} // Clamp vertically to chunk vertical span to keep bounds tight and valid
min.y = (std::max)(static_cast<float>(chunk.bottom), min.y);
class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, RendererResult> { max.y = (std::min)(static_cast<float>(chunk.top), max.y);
const Chunks& chunks;
BlocksRenderer renderer; // Ensure non-degenerate extents to avoid view-dependent flicker
public: glm::vec3 size = max - min;
RendererWorker( auto inflate_axis = [&](int axis) {
const Level& level, float c = (min[axis] + max[axis]) * 0.5f;
const Chunks& chunks, min[axis] = c - K_AABB_MIN_EXTENT * 0.5f;
const ContentGfxCache& cache, max[axis] = c + K_AABB_MIN_EXTENT * 0.5f;
const EngineSettings& settings };
) if (size.x < K_AABB_MIN_EXTENT) inflate_axis(0);
: chunks(chunks), if (size.y < K_AABB_MIN_EXTENT) inflate_axis(1);
renderer( if (size.z < K_AABB_MIN_EXTENT) inflate_axis(2);
settings.graphics.denseRender.get() }
? settings.graphics.chunkMaxVerticesDense.get() }
: settings.graphics.chunkMaxVertices.get(), return {min, max};
level.content, }
cache, }
settings
) { class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, RendererResult> {
} const Chunks& chunks;
BlocksRenderer renderer;
RendererResult operator()(const std::shared_ptr<Chunk>& chunk) override { public:
renderer.build(chunk.get(), &chunks); RendererWorker(
if (renderer.isCancelled()) { const Level& level,
return RendererResult { const Chunks& chunks,
glm::ivec2(chunk->x, chunk->z), true, ChunkMeshData {}}; const ContentGfxCache& cache,
} const EngineSettings& settings
auto meshData = renderer.createMesh(); )
return RendererResult { : chunks(chunks),
glm::ivec2(chunk->x, chunk->z), false, std::move(meshData)}; renderer(
} settings.graphics.denseRender.get()
}; ? settings.graphics.chunkMaxVerticesDense.get()
: settings.graphics.chunkMaxVertices.get(),
ChunksRenderer::ChunksRenderer( level.content,
const Level* level, cache,
const Chunks& chunks, settings
const Assets& assets, ) {
const Frustum& frustum, }
const ContentGfxCache& cache,
const EngineSettings& settings RendererResult operator()(const std::shared_ptr<Chunk>& chunk) override {
) renderer.build(chunk.get(), &chunks);
: chunks(chunks), if (renderer.isCancelled()) {
assets(assets), return RendererResult {
frustum(frustum), glm::ivec2(chunk->x, chunk->z), true, ChunkMeshData {}};
settings(settings), }
threadPool( auto meshData = renderer.createMesh();
"chunks-render-pool", return RendererResult {
[&]() { glm::ivec2(chunk->x, chunk->z), false, std::move(meshData)};
return std::make_shared<RendererWorker>( }
*level, chunks, cache, settings };
);
}, ChunksRenderer::ChunksRenderer(
[&](RendererResult& result) { const Level* level,
if (!result.cancelled) { const Chunks& chunks,
auto meshData = std::move(result.meshData); const Assets& assets,
meshes[result.key] = ChunkMesh { const Frustum& frustum,
std::make_unique<Mesh<ChunkVertex>>(meshData.mesh), const ContentGfxCache& cache,
std::move(meshData.sortingMesh)}; const EngineSettings& settings
meshes[result.key].localAabb = meshData.localAabb; )
} : chunks(chunks),
inwork.erase(result.key); assets(assets),
}, frustum(frustum),
settings.graphics.chunkMaxRenderers.get() settings(settings),
) { threadPool(
threadPool.setStopOnFail(false); "chunks-render-pool",
renderer = std::make_unique<BlocksRenderer>( [&]() {
settings.graphics.chunkMaxVertices.get(), return std::make_shared<RendererWorker>(
level->content, cache, settings *level, chunks, cache, settings
); );
logger.info() << "created " << threadPool.getWorkersCount() << " workers"; },
logger.info() << "memory consumption is " [&](RendererResult& result) {
<< renderer->getMemoryConsumption() * threadPool.getWorkersCount() if (!result.cancelled) {
<< " B"; auto meshData = std::move(result.meshData);
} meshes[result.key] = ChunkMesh {
std::make_unique<Mesh<ChunkVertex>>(meshData.mesh),
ChunksRenderer::~ChunksRenderer() = default; std::move(meshData.sortingMesh)};
meshes[result.key].localAabb = meshData.localAabb;
const Mesh<ChunkVertex>* ChunksRenderer::render( }
const std::shared_ptr<Chunk>& chunk, bool important inwork.erase(result.key);
) { },
chunk->flags.modified = false; settings.graphics.chunkMaxRenderers.get()
if (important) { ) {
auto mesh = renderer->render(chunk.get(), &chunks); threadPool.setStopOnFail(false);
meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh { renderer = std::make_unique<BlocksRenderer>(
std::move(mesh.mesh), std::move(mesh.sortingMeshData) settings.graphics.chunkMaxVertices.get(),
}; level->content, cache, settings
// propagate local aabb from immediate path too );
meshes[glm::ivec2(chunk->x, chunk->z)].localAabb = renderer->getLocalAabb(); logger.info() << "created " << threadPool.getWorkersCount() << " workers";
return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get(); logger.info() << "memory consumption is "
} << renderer->getMemoryConsumption() * threadPool.getWorkersCount()
glm::ivec2 key(chunk->x, chunk->z); << " B";
if (inwork.find(key) != inwork.end()) { }
return nullptr;
} ChunksRenderer::~ChunksRenderer() = default;
inwork[key] = true;
threadPool.enqueueJob(chunk); const Mesh<ChunkVertex>* ChunksRenderer::render(
return nullptr; const std::shared_ptr<Chunk>& chunk, bool important
} ) {
chunk->flags.modified = false;
void ChunksRenderer::unload(const Chunk* chunk) { if (important) {
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); auto mesh = renderer->render(chunk.get(), &chunks);
if (found != meshes.end()) { meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh {
meshes.erase(found); std::move(mesh.mesh), std::move(mesh.sortingMeshData)
} };
} // propagate local aabb from immediate path too
meshes[glm::ivec2(chunk->x, chunk->z)].localAabb = renderer->getLocalAabb();
void ChunksRenderer::clear() { return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get();
meshes.clear(); }
inwork.clear(); glm::ivec2 key(chunk->x, chunk->z);
threadPool.clearQueue(); if (inwork.find(key) != inwork.end()) {
} return nullptr;
}
const Mesh<ChunkVertex>* ChunksRenderer::getOrRender( inwork[key] = true;
const std::shared_ptr<Chunk>& chunk, bool important threadPool.enqueueJob(chunk);
) { return nullptr;
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); }
if (found == meshes.end()) {
return render(chunk, important); void ChunksRenderer::unload(const Chunk* chunk) {
} auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (chunk->flags.modified && chunk->flags.lighted) { if (found != meshes.end()) {
render(chunk, important); meshes.erase(found);
} }
return found->second.mesh.get(); }
}
void ChunksRenderer::clear() {
void ChunksRenderer::update() { meshes.clear();
threadPool.update(); inwork.clear();
} threadPool.clearQueue();
}
const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
size_t index, const Camera& camera, bool culling const Mesh<ChunkVertex>* ChunksRenderer::getOrRender(
) { const std::shared_ptr<Chunk>& chunk, bool important
auto chunk = chunks.getChunks()[index]; ) {
if (chunk == nullptr) { auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
return nullptr; if (found == meshes.end()) {
} return render(chunk, important);
if (!chunk->flags.lighted) { }
const auto& found = meshes.find({chunk->x, chunk->z}); if (chunk->flags.modified && chunk->flags.lighted) {
if (found == meshes.end()) { render(chunk, important);
return nullptr; }
} else { return found->second.mesh.get();
return found->second.mesh.get(); }
}
} void ChunksRenderer::update() {
float distance = glm::distance( threadPool.update();
camera.position, }
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W, const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk(
camera.position.y, size_t index, const Camera& camera, bool culling
(chunk->z + 0.5f) * CHUNK_D ) {
) auto chunk = chunks.getChunks()[index];
); if (chunk == nullptr) {
auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f); return nullptr;
if (mesh == nullptr) { }
return nullptr; if (!chunk->flags.lighted) {
} const auto& found = meshes.find({chunk->x, chunk->z});
if (chunk->flags.dirtyHeights) { if (found == meshes.end()) {
chunk->updateHeights(); return nullptr;
} } else {
if (culling) { return found->second.mesh.get();
const auto bounds = compute_chunk_culling_bounds(*chunk, meshes); }
if (!frustum.isBoxVisible(bounds.min, bounds.max)) return nullptr; }
} float distance = glm::distance(
return mesh; camera.position,
} glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
void ChunksRenderer::drawShadowsPass( camera.position.y,
const Camera& camera, Shader& shader, const Camera& playerCamera (chunk->z + 0.5f) * CHUNK_D
) { )
Frustum frustum; );
frustum.update(camera.getProjView()); auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f);
if (mesh == nullptr) {
const auto& atlas = assets.require<Atlas>("blocks"); return nullptr;
}
atlas.getTexture()->bind(); if (chunk->flags.dirtyHeights) {
chunk->updateHeights();
auto denseDistance = settings.graphics.denseRenderDistance.get(); }
auto denseDistance2 = denseDistance * denseDistance; if (culling) {
const auto bounds = compute_chunk_culling_bounds(*chunk, meshes);
for (const auto& chunk : chunks.getChunks()) { if (!frustum.isBoxVisible(bounds.min, bounds.max)) return nullptr;
if (chunk == nullptr) { }
continue; return mesh;
} }
glm::ivec2 pos {chunk->x, chunk->z};
const auto& found = meshes.find({chunk->x, chunk->z}); void ChunksRenderer::drawShadowsPass(
if (found == meshes.end()) { const Camera& camera, Shader& shader, const Camera& playerCamera
continue; ) {
} Frustum frustum;
frustum.update(camera.getProjView());
glm::vec3 coord(
pos.x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, pos.y * CHUNK_D + K_CHUNK_CENTER_BIAS const auto& atlas = assets.require<Atlas>("blocks");
);
atlas.getTexture()->bind();
const auto bounds = compute_chunk_culling_bounds(*chunk, meshes);
if (!frustum.isBoxVisible(bounds.min, bounds.max)) { auto denseDistance = settings.graphics.denseRenderDistance.get();
continue; auto denseDistance2 = denseDistance * denseDistance;
}
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); for (const auto& chunk : chunks.getChunks()) {
shader.uniformMatrix("u_model", model); if (chunk == nullptr) {
found->second.mesh->draw(GL_TRIANGLES, continue;
glm::distance2(playerCamera.position * glm::vec3(1, 0, 1), }
(bounds.min + bounds.max) * 0.5f * glm::vec3(1, 0, 1)) < denseDistance2); glm::ivec2 pos {chunk->x, chunk->z};
} const auto& found = meshes.find({chunk->x, chunk->z});
} if (found == meshes.end()) {
continue;
void ChunksRenderer::drawChunks( }
const Camera& camera, Shader& shader
) { glm::vec3 coord(
const auto& atlas = assets.require<Atlas>("blocks"); pos.x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, pos.y * CHUNK_D + K_CHUNK_CENTER_BIAS
);
atlas.getTexture()->bind();
const auto bounds = compute_chunk_culling_bounds(*chunk, meshes);
// [warning] this whole method is not thread-safe for chunks if (!frustum.isBoxVisible(bounds.min, bounds.max)) {
continue;
int chunksWidth = chunks.getWidth(); }
int chunksOffsetX = chunks.getOffsetX(); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
int chunksOffsetY = chunks.getOffsetY(); shader.uniformMatrix("u_model", model);
found->second.mesh->draw(GL_TRIANGLES,
if (indices.size() != chunks.getVolume()) { glm::distance2(playerCamera.position * glm::vec3(1, 0, 1),
indices.clear(); (bounds.min + bounds.max) * 0.5f * glm::vec3(1, 0, 1)) < denseDistance2);
for (int i = 0; i < chunks.getVolume(); i++) { }
indices.push_back(ChunksSortEntry {i, 0}); }
}
} void ChunksRenderer::drawChunks(
float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f; const Camera& camera, Shader& shader
float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f; ) {
for (auto& index : indices) { const auto& atlas = assets.require<Atlas>("blocks");
float x = index.index % chunksWidth + chunksOffsetX - px;
float z = index.index / chunksWidth + chunksOffsetY - pz; atlas.getTexture()->bind();
index.d = (x * x + z * z) * 1024;
} // [warning] this whole method is not thread-safe for chunks
util::insertion_sort(indices.begin(), indices.end());
int chunksWidth = chunks.getWidth();
bool culling = settings.graphics.frustumCulling.get(); int chunksOffsetX = chunks.getOffsetX();
int chunksOffsetY = chunks.getOffsetY();
visibleChunks = 0;
shader.uniform1i("u_alphaClip", true); if (indices.size() != chunks.getVolume()) {
indices.clear();
auto denseDistance = settings.graphics.denseRenderDistance.get(); for (int i = 0; i < chunks.getVolume(); i++) {
auto denseDistance2 = denseDistance * denseDistance; indices.push_back(ChunksSortEntry {i, 0});
}
// TODO: minimize draw calls number }
for (int i = indices.size()-1; i >= 0; i--) { float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f;
auto& chunk = chunks.getChunks()[indices[i].index]; float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f;
auto mesh = retrieveChunk(indices[i].index, camera, culling); for (auto& index : indices) {
float x = index.index % chunksWidth + chunksOffsetX - px;
if (mesh) { float z = index.index / chunksWidth + chunksOffsetY - pz;
glm::vec3 coord( index.d = (x * x + z * z) * 1024;
chunk->x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, chunk->z * CHUNK_D + K_CHUNK_CENTER_BIAS }
); util::insertion_sort(indices.begin(), indices.end());
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
shader.uniformMatrix("u_model", model); bool culling = settings.graphics.frustumCulling.get();
mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1),
(coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2); visibleChunks = 0;
visibleChunks++; shader.uniform1i("u_alphaClip", true);
}
} auto denseDistance = settings.graphics.denseRenderDistance.get();
} auto denseDistance2 = denseDistance * denseDistance;
static inline void write_sorting_mesh_entries( // TODO: minimize draw calls number
ChunkVertex* buffer, const std::vector<SortingMeshEntry>& chunkEntries for (int i = indices.size()-1; i >= 0; i--) {
) { auto& chunk = chunks.getChunks()[indices[i].index];
for (const auto& entry : chunkEntries) { auto mesh = retrieveChunk(indices[i].index, camera, culling);
const auto& vertexData = entry.vertexData;
std::memcpy( if (mesh) {
buffer, glm::vec3 coord(
vertexData.data(), chunk->x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, chunk->z * CHUNK_D + K_CHUNK_CENTER_BIAS
vertexData.size() * sizeof(ChunkVertex) );
); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
buffer += vertexData.size(); shader.uniformMatrix("u_model", model);
} mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1),
} (coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2);
visibleChunks++;
void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) { }
const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL; }
static int frameid = 0; }
frameid++;
static inline void write_sorting_mesh_entries(
const bool culling = settings.graphics.frustumCulling.get(); ChunkVertex* buffer, const std::vector<SortingMeshEntry>& chunkEntries
const auto& chunks = this->chunks.getChunks(); ) {
const auto& cameraPos = camera.position; for (const auto& entry : chunkEntries) {
const auto& atlas = assets.require<Atlas>("blocks"); const auto& vertexData = entry.vertexData;
std::memcpy(
shader.use(); buffer,
atlas.getTexture()->bind(); vertexData.data(),
shader.uniformMatrix("u_model", glm::mat4(1.0f)); vertexData.size() * sizeof(ChunkVertex)
shader.uniform1i("u_alphaClip", false); );
buffer += vertexData.size();
struct VisibleChunkTrans { }
glm::ivec2 key; }
const std::shared_ptr<Chunk>* chunkPtr;
long long nearestDist2; void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) {
}; const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL;
std::vector<VisibleChunkTrans> order; static int frameid = 0;
order.reserve(indices.size()); frameid++;
// Build per-chunk nearest distance for translucent entries const bool culling = settings.graphics.frustumCulling.get();
for (const auto& index : indices) { const auto& chunks = this->chunks.getChunks();
const auto& chunk = chunks[index.index]; const auto& cameraPos = camera.position;
if (chunk == nullptr || !chunk->flags.lighted) { const auto& atlas = assets.require<Atlas>("blocks");
continue;
} shader.use();
const auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); atlas.getTexture()->bind();
if (found == meshes.end()) { shader.uniformMatrix("u_model", glm::mat4(1.0f));
continue; shader.uniform1i("u_alphaClip", false);
}
const auto& entries = found->second.sortingMeshData.entries; struct VisibleChunkTrans {
if (entries.empty()) { glm::ivec2 key;
continue; const std::shared_ptr<Chunk>* chunkPtr;
} long long nearestDist2;
};
if (culling) { std::vector<VisibleChunkTrans> order;
const auto bounds = compute_chunk_culling_bounds(*chunk, meshes); order.reserve(indices.size());
if (!frustum.isBoxVisible(bounds.min, bounds.max)) continue;
} // Build per-chunk nearest distance for translucent entries
for (const auto& index : indices) {
long long nearest = LLONG_MAX; const auto& chunk = chunks[index.index];
for (const auto& e : entries) { if (chunk == nullptr || !chunk->flags.lighted) {
long long d2 = static_cast<long long>(glm::distance2(e.position, cameraPos)); continue;
if (d2 < nearest) nearest = d2; }
} const auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
order.push_back(VisibleChunkTrans{glm::ivec2(chunk->x, chunk->z), &chunks[index.index], nearest}); if (found == meshes.end()) {
} continue;
}
// Sort chunks by nearest translucent distance back-to-front (far to near) const auto& entries = found->second.sortingMeshData.entries;
std::sort(order.begin(), order.end(), [](const VisibleChunkTrans& a, const VisibleChunkTrans& b) { if (entries.empty()) {
return a.nearestDist2 > b.nearestDist2; continue;
}); }
// Draw per-chunk sorted mesh (keeps GPU buffers and avoids per-frame repack) if (culling) {
for (const auto& item : order) { const auto bounds = compute_chunk_culling_bounds(*chunk, meshes);
const auto& chunk = *item.chunkPtr; if (!frustum.isBoxVisible(bounds.min, bounds.max)) continue;
const auto found = meshes.find(item.key); }
if (found == meshes.end()) continue;
auto& chunkEntries = found->second.sortingMeshData.entries; long long nearest = LLONG_MAX;
if (chunkEntries.empty()) continue; for (const auto& e : entries) {
long long d2 = static_cast<long long>(glm::distance2(e.position, cameraPos));
// Keep per-chunk internal order up-to-date occasionally if (d2 < nearest) nearest = d2;
if (found->second.sortedMesh == nullptr || (frameid + chunk->x) % sortInterval == 0) { }
for (auto& entry : chunkEntries) { order.push_back(VisibleChunkTrans{glm::ivec2(chunk->x, chunk->z), &chunks[index.index], nearest});
entry.distance = static_cast<long long>( }
glm::distance2(entry.position, cameraPos)
); // Sort chunks by nearest translucent distance back-to-front (far to near)
} std::sort(order.begin(), order.end(), [](const VisibleChunkTrans& a, const VisibleChunkTrans& b) {
std::sort(chunkEntries.begin(), chunkEntries.end()); return a.nearestDist2 > b.nearestDist2;
size_t size = 0; });
for (const auto& entry : chunkEntries) {
size += entry.vertexData.size(); // Draw per-chunk sorted mesh (keeps GPU buffers and avoids per-frame repack)
} for (const auto& item : order) {
static util::Buffer<ChunkVertex> buffer; const auto& chunk = *item.chunkPtr;
if (buffer.size() < size) { const auto found = meshes.find(item.key);
buffer = util::Buffer<ChunkVertex>(size); if (found == meshes.end()) continue;
} auto& chunkEntries = found->second.sortingMeshData.entries;
write_sorting_mesh_entries(buffer.data(), chunkEntries); if (chunkEntries.empty()) continue;
found->second.sortedMesh = std::make_unique<Mesh<ChunkVertex>>(
buffer.data(), size // Keep per-chunk internal order up-to-date occasionally
); if (found->second.sortedMesh == nullptr || (frameid + chunk->x) % sortInterval == 0) {
} for (auto& entry : chunkEntries) {
found->second.sortedMesh->draw(); entry.distance = static_cast<long long>(
} glm::distance2(entry.position, cameraPos)
} );
}
std::sort(chunkEntries.begin(), chunkEntries.end());
size_t size = 0;
for (const auto& entry : chunkEntries) {
size += entry.vertexData.size();
}
static util::Buffer<ChunkVertex> buffer;
if (buffer.size() < size) {
buffer = util::Buffer<ChunkVertex>(size);
}
write_sorting_mesh_entries(buffer.data(), chunkEntries);
found->second.sortedMesh = std::make_unique<Mesh<ChunkVertex>>(
buffer.data(), size
);
}
found->second.sortedMesh->draw();
}
}