feat: upgrade WMO group frustum culling from basic forward-check to proper frustum-AABB testing

Replace the basic forward-vector culling (which only culls when all AABB
corners are behind the camera) with proper frustum-AABB intersection testing
for more accurate and aggressive visibility culling. This reduces overdraw
and improves rendering performance in WMO-heavy scenes (dungeons, buildings).
This commit is contained in:
Kelsi 2026-03-11 12:43:22 -07:00
parent 508b7e839b
commit a10e3e86fb

View file

@ -1952,40 +1952,27 @@ VkDescriptorSet WMORenderer::allocateMaterialSet() {
bool WMORenderer::isGroupVisible(const GroupResources& group, const glm::mat4& modelMatrix, bool WMORenderer::isGroupVisible(const GroupResources& group, const glm::mat4& modelMatrix,
const Camera& camera) const { const Camera& camera) const {
// Simple frustum culling using bounding box // Proper frustum-AABB intersection test for accurate visibility culling
// Transform bounding box corners to world space // Transform bounding box min/max to world space
glm::vec3 corners[8] = { glm::vec3 localMin = group.boundingBoxMin;
glm::vec3(group.boundingBoxMin.x, group.boundingBoxMin.y, group.boundingBoxMin.z), glm::vec3 localMax = group.boundingBoxMax;
glm::vec3(group.boundingBoxMax.x, group.boundingBoxMin.y, group.boundingBoxMin.z),
glm::vec3(group.boundingBoxMin.x, group.boundingBoxMax.y, group.boundingBoxMin.z),
glm::vec3(group.boundingBoxMax.x, group.boundingBoxMax.y, group.boundingBoxMin.z),
glm::vec3(group.boundingBoxMin.x, group.boundingBoxMin.y, group.boundingBoxMax.z),
glm::vec3(group.boundingBoxMax.x, group.boundingBoxMin.y, group.boundingBoxMax.z),
glm::vec3(group.boundingBoxMin.x, group.boundingBoxMax.y, group.boundingBoxMax.z),
glm::vec3(group.boundingBoxMax.x, group.boundingBoxMax.y, group.boundingBoxMax.z)
};
// Transform corners to world space // Transform min and max to world space
for (int i = 0; i < 8; i++) { glm::vec4 worldMinH = modelMatrix * glm::vec4(localMin, 1.0f);
glm::vec4 worldPos = modelMatrix * glm::vec4(corners[i], 1.0f); glm::vec4 worldMaxH = modelMatrix * glm::vec4(localMax, 1.0f);
corners[i] = glm::vec3(worldPos); glm::vec3 worldMin = glm::vec3(worldMinH);
} glm::vec3 worldMax = glm::vec3(worldMaxH);
// Simple check: if all corners are behind camera, cull // Ensure min/max are correct after transformation (handles non-uniform scaling)
// (This is a very basic culling implementation - a full frustum test would be better) glm::vec3 boundsMin = glm::min(worldMin, worldMax);
glm::vec3 forward = camera.getForward(); glm::vec3 boundsMax = glm::max(worldMin, worldMax);
glm::vec3 camPos = camera.getPosition();
int behindCount = 0; // Extract frustum planes from view-projection matrix
for (int i = 0; i < 8; i++) { Frustum frustum;
glm::vec3 toCorner = corners[i] - camPos; frustum.extractFromMatrix(camera.getViewProjectionMatrix());
if (glm::dot(toCorner, forward) < 0.0f) {
behindCount++;
}
}
// If all corners are behind camera, cull // Test if AABB intersects view frustum
return behindCount < 8; return frustum.intersectsAABB(boundsMin, boundsMax);
} }
int WMORenderer::findContainingGroup(const ModelData& model, const glm::vec3& localPos) const { int WMORenderer::findContainingGroup(const ModelData& model, const glm::vec3& localPos) const {