diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index e487f64a..916b0ff0 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -2058,10 +2058,15 @@ void M2Renderer::update(float deltaTime, const glm::vec3& cameraPos, const glm:: float paddedRadius = std::max(cullRadius * 1.5f, cullRadius + 3.0f); if (cullRadius > 0.0f && !updateFrustum.intersectsSphere(instance.position, paddedRadius)) continue; + // LOD 3 skip: models beyond 150 units use the lowest LOD mesh which has + // no visible skeletal animation. Keep their last-computed bone matrices + // (always valid — seeded on spawn) and avoid the expensive per-bone work. + constexpr float kLOD3DistSq = 150.0f * 150.0f; + if (distSq > kLOD3DistSq) continue; + // Distance-based frame skipping: update distant bones less frequently uint32_t boneInterval = 1; - if (distSq > 200.0f * 200.0f) boneInterval = 8; - else if (distSq > 100.0f * 100.0f) boneInterval = 4; + if (distSq > 100.0f * 100.0f) boneInterval = 4; else if (distSq > 50.0f * 50.0f) boneInterval = 2; instance.frameSkipCounter++; if ((instance.frameSkipCounter % boneInterval) != 0) continue; diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index 88c22879..6d9245f6 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -5,6 +5,7 @@ #include "rendering/vk_utils.hpp" #include "rendering/vk_frame_data.hpp" #include "rendering/camera.hpp" +#include "rendering/frustum.hpp" #include "pipeline/adt_loader.hpp" #include "pipeline/wmo_loader.hpp" #include "core/logger.hpp" @@ -1039,7 +1040,7 @@ void WaterRenderer::clear() { // ============================================================== void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, - const Camera& /*camera*/, float /*time*/, bool use1x, uint32_t frameIndex) { + const Camera& camera, float /*time*/, bool use1x, uint32_t frameIndex) { VkPipeline pipeline = (use1x && water1xPipeline) ? water1xPipeline : waterPipeline; if (!renderingEnabled || surfaces.empty() || !pipeline) { if (renderDiagCounter_++ % 300 == 0 && !surfaces.empty()) { @@ -1059,6 +1060,10 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, return; } + // Frustum culling setup + Frustum frustum; + frustum.extractFromMatrix(camera.getViewProjectionMatrix()); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, @@ -1070,6 +1075,27 @@ void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, if (surface.vertexBuffer == VK_NULL_HANDLE || surface.indexCount == 0) continue; if (!surface.materialSet) continue; + // Frustum cull: compute AABB from surface origin + step vectors + { + const glm::vec3 extentX = surface.stepX * static_cast(surface.width); + const glm::vec3 extentY = surface.stepY * static_cast(surface.height); + const glm::vec3 c0 = surface.origin; + const glm::vec3 c1 = surface.origin + extentX; + const glm::vec3 c2 = surface.origin + extentY; + const glm::vec3 c3 = surface.origin + extentX + extentY; + const glm::vec3 aabbMin( + std::min({c0.x, c1.x, c2.x, c3.x}), + std::min({c0.y, c1.y, c2.y, c3.y}), + surface.minHeight + ); + const glm::vec3 aabbMax( + std::max({c0.x, c1.x, c2.x, c3.x}), + std::max({c0.y, c1.y, c2.y, c3.y}), + surface.maxHeight + ); + if (!frustum.intersectsAABB(aabbMin, aabbMax)) continue; + } + bool isWmoWater = (surface.wmoId != 0); bool canalProfile = isWmoWater || (surface.liquidType == 5); uint8_t basicType = (surface.liquidType == 0) ? 0 : ((surface.liquidType - 1) % 4);