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