mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Activate WMO/char/M2 render loop, purge dead GL block, add underwater overlay
- renderWorld() now calls wmoRenderer, characterRenderer, m2Renderer (+smoke, particles) in the correct opaque→transparent order; water moved after all opaques; per-subsystem timing active in live path - Deleted the 310-line #if 0 GL stub block and removed #include <GL/glew.h> (last GL reference in renderer.cpp) - Full-screen overlay pipeline (postprocess.vert + overlay.frag, alpha blend, no depth test/write) for underwater tint; lazily initialized, renders a blue tint when camera is meaningfully below the water surface; canal vs open-water tint colours preserved from original design - overlay.frag.glsl / overlay.frag.spv added
This commit is contained in:
parent
dea52744a4
commit
69cf39ba02
4 changed files with 141 additions and 328 deletions
14
assets/shaders/overlay.frag.glsl
Normal file
14
assets/shaders/overlay.frag.glsl
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#version 450
|
||||||
|
|
||||||
|
// Full-screen color overlay (e.g. underwater tint).
|
||||||
|
// Uses postprocess.vert.glsl as vertex shader (fullscreen triangle, no vertex input).
|
||||||
|
|
||||||
|
layout(push_constant) uniform Push {
|
||||||
|
vec4 color; // rgb = tint color, a = opacity
|
||||||
|
} push;
|
||||||
|
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = push.color;
|
||||||
|
}
|
||||||
BIN
assets/shaders/overlay.frag.spv
Normal file
BIN
assets/shaders/overlay.frag.spv
Normal file
Binary file not shown.
|
|
@ -295,6 +295,12 @@ private:
|
||||||
float selCircleRadius = 1.5f;
|
float selCircleRadius = 1.5f;
|
||||||
bool selCircleVisible = false;
|
bool selCircleVisible = false;
|
||||||
|
|
||||||
|
// Fullscreen color overlay (underwater tint)
|
||||||
|
VkPipeline overlayPipeline = VK_NULL_HANDLE;
|
||||||
|
VkPipelineLayout overlayPipelineLayout = VK_NULL_HANDLE;
|
||||||
|
void initOverlayPipeline();
|
||||||
|
void renderOverlay(const glm::vec4& color);
|
||||||
|
|
||||||
// Footstep event tracking (animation-driven)
|
// Footstep event tracking (animation-driven)
|
||||||
uint32_t footstepLastAnimationId = 0;
|
uint32_t footstepLastAnimationId = 0;
|
||||||
float footstepLastNormTime = 0.0f;
|
float footstepLastNormTime = 0.0f;
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@
|
||||||
#include "audio/combat_sound_manager.hpp"
|
#include "audio/combat_sound_manager.hpp"
|
||||||
#include "audio/spell_sound_manager.hpp"
|
#include "audio/spell_sound_manager.hpp"
|
||||||
#include "audio/movement_sound_manager.hpp"
|
#include "audio/movement_sound_manager.hpp"
|
||||||
#include <GL/glew.h> // TODO: Remove in Phase 7 (unconverted sub-renderers still reference GL types)
|
|
||||||
#include "rendering/vk_context.hpp"
|
#include "rendering/vk_context.hpp"
|
||||||
#include "rendering/vk_frame_data.hpp"
|
#include "rendering/vk_frame_data.hpp"
|
||||||
#include "rendering/vk_shader.hpp"
|
#include "rendering/vk_shader.hpp"
|
||||||
|
|
@ -708,6 +707,8 @@ void Renderer::shutdown() {
|
||||||
if (selCirclePipelineLayout) { vkDestroyPipelineLayout(device, selCirclePipelineLayout, nullptr); selCirclePipelineLayout = VK_NULL_HANDLE; }
|
if (selCirclePipelineLayout) { vkDestroyPipelineLayout(device, selCirclePipelineLayout, nullptr); selCirclePipelineLayout = VK_NULL_HANDLE; }
|
||||||
if (selCircleVertBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleVertBuf, selCircleVertAlloc); selCircleVertBuf = VK_NULL_HANDLE; selCircleVertAlloc = VK_NULL_HANDLE; }
|
if (selCircleVertBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleVertBuf, selCircleVertAlloc); selCircleVertBuf = VK_NULL_HANDLE; selCircleVertAlloc = VK_NULL_HANDLE; }
|
||||||
if (selCircleIdxBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleIdxBuf, selCircleIdxAlloc); selCircleIdxBuf = VK_NULL_HANDLE; selCircleIdxAlloc = VK_NULL_HANDLE; }
|
if (selCircleIdxBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleIdxBuf, selCircleIdxAlloc); selCircleIdxBuf = VK_NULL_HANDLE; selCircleIdxAlloc = VK_NULL_HANDLE; }
|
||||||
|
if (overlayPipeline) { vkDestroyPipeline(device, overlayPipeline, nullptr); overlayPipeline = VK_NULL_HANDLE; }
|
||||||
|
if (overlayPipelineLayout) { vkDestroyPipelineLayout(device, overlayPipelineLayout, nullptr); overlayPipelineLayout = VK_NULL_HANDLE; }
|
||||||
}
|
}
|
||||||
|
|
||||||
destroyPerFrameResources();
|
destroyPerFrameResources();
|
||||||
|
|
@ -2857,8 +2858,63 @@ void Renderer::renderSelectionCircle(const glm::mat4& view, const glm::mat4& pro
|
||||||
vkCmdDrawIndexed(currentCmd, static_cast<uint32_t>(selCircleVertCount), 1, 0, 0, 0);
|
vkCmdDrawIndexed(currentCmd, static_cast<uint32_t>(selCircleVertCount), 1, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
// Fullscreen overlay pipeline (underwater tint, etc.)
|
||||||
|
// ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
void Renderer::initOverlayPipeline() {
|
||||||
|
if (!vkCtx) return;
|
||||||
|
VkDevice device = vkCtx->getDevice();
|
||||||
|
|
||||||
|
// Push constant: vec4 color (16 bytes), visible to both stages
|
||||||
|
VkPushConstantRange pc{};
|
||||||
|
pc.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||||
|
pc.offset = 0;
|
||||||
|
pc.size = 16;
|
||||||
|
|
||||||
|
VkPipelineLayoutCreateInfo plCI{};
|
||||||
|
plCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||||
|
plCI.pushConstantRangeCount = 1;
|
||||||
|
plCI.pPushConstantRanges = &pc;
|
||||||
|
vkCreatePipelineLayout(device, &plCI, nullptr, &overlayPipelineLayout);
|
||||||
|
|
||||||
|
VkShaderModule vertMod, fragMod;
|
||||||
|
if (!vertMod.loadFromFile(device, "assets/shaders/postprocess.vert.spv") ||
|
||||||
|
!fragMod.loadFromFile(device, "assets/shaders/overlay.frag.spv")) {
|
||||||
|
LOG_ERROR("Renderer: failed to load overlay shaders");
|
||||||
|
vertMod.destroy(); fragMod.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayPipeline = PipelineBuilder()
|
||||||
|
.setShaders(vertMod.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
||||||
|
fragMod.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
||||||
|
.setVertexInput({}, {}) // fullscreen triangle, no VBOs
|
||||||
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
||||||
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||||
|
.setNoDepthTest()
|
||||||
|
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||||
|
.setLayout(overlayPipelineLayout)
|
||||||
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||||
|
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
||||||
|
.build(device);
|
||||||
|
|
||||||
|
vertMod.destroy(); fragMod.destroy();
|
||||||
|
|
||||||
|
if (overlayPipeline) LOG_INFO("Renderer: overlay pipeline initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::renderOverlay(const glm::vec4& color) {
|
||||||
|
if (!overlayPipeline) initOverlayPipeline();
|
||||||
|
if (!overlayPipeline || currentCmd == VK_NULL_HANDLE) return;
|
||||||
|
vkCmdBindPipeline(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, overlayPipeline);
|
||||||
|
vkCmdPushConstants(currentCmd, overlayPipelineLayout,
|
||||||
|
VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, &color[0]);
|
||||||
|
vkCmdDraw(currentCmd, 3, 1, 0, 0); // fullscreen triangle
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||||
(void)world; // Used later in Phases 4-5
|
(void)world;
|
||||||
|
|
||||||
auto renderStart = std::chrono::steady_clock::now();
|
auto renderStart = std::chrono::steady_clock::now();
|
||||||
lastTerrainRenderMs = 0.0;
|
lastTerrainRenderMs = 0.0;
|
||||||
|
|
@ -2867,6 +2923,8 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||||
|
|
||||||
uint32_t frameIdx = vkCtx->getCurrentFrame();
|
uint32_t frameIdx = vkCtx->getCurrentFrame();
|
||||||
VkDescriptorSet perFrameSet = perFrameDescSets[frameIdx];
|
VkDescriptorSet perFrameSet = perFrameDescSets[frameIdx];
|
||||||
|
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
||||||
|
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
||||||
|
|
||||||
// Get time of day for sky-related rendering
|
// Get time of day for sky-related rendering
|
||||||
float timeOfDay = (skySystem && skySystem->getSkybox()) ? skySystem->getSkybox()->getTimeOfDay() : 12.0f;
|
float timeOfDay = (skySystem && skySystem->getSkybox()) ? skySystem->getSkybox()->getTimeOfDay() : 12.0f;
|
||||||
|
|
@ -2896,46 +2954,95 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||||
skySystem->render(currentCmd, perFrameSet, *camera, skyParams);
|
skySystem->render(currentCmd, perFrameSet, *camera, skyParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terrain rendering
|
// Terrain (opaque pass)
|
||||||
if (terrainRenderer && camera && terrainEnabled) {
|
if (terrainRenderer && camera && terrainEnabled) {
|
||||||
|
auto terrainStart = std::chrono::steady_clock::now();
|
||||||
terrainRenderer->render(currentCmd, perFrameSet, *camera);
|
terrainRenderer->render(currentCmd, perFrameSet, *camera);
|
||||||
|
lastTerrainRenderMs = std::chrono::duration<double, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - terrainStart).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Water rendering (after terrain, transparent)
|
// WMO buildings (opaque, drawn before characters so selection circle sits on top)
|
||||||
|
if (wmoRenderer && camera) {
|
||||||
|
auto wmoStart = std::chrono::steady_clock::now();
|
||||||
|
wmoRenderer->render(currentCmd, perFrameSet, *camera);
|
||||||
|
lastWMORenderMs = std::chrono::duration<double, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - wmoStart).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection circle (drawn after WMO, before characters)
|
||||||
|
renderSelectionCircle(view, projection);
|
||||||
|
|
||||||
|
// Characters (after selection circle so units draw over the ring)
|
||||||
|
if (characterRenderer && camera) {
|
||||||
|
characterRenderer->render(currentCmd, perFrameSet, *camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// M2 doodads, creatures, glow sprites, particles
|
||||||
|
if (m2Renderer && camera) {
|
||||||
|
if (cameraController) {
|
||||||
|
m2Renderer->setInsideInterior(cameraController->isInsideWMO());
|
||||||
|
m2Renderer->setOnTaxi(cameraController->isOnTaxi());
|
||||||
|
}
|
||||||
|
auto m2Start = std::chrono::steady_clock::now();
|
||||||
|
m2Renderer->render(currentCmd, perFrameSet, *camera);
|
||||||
|
m2Renderer->renderSmokeParticles(currentCmd, perFrameSet);
|
||||||
|
m2Renderer->renderM2Particles(currentCmd, perFrameSet);
|
||||||
|
lastM2RenderMs = std::chrono::duration<double, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - m2Start).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Water (transparent, after all opaques)
|
||||||
if (waterRenderer && camera) {
|
if (waterRenderer && camera) {
|
||||||
waterRenderer->render(currentCmd, perFrameSet, *camera, globalTime);
|
waterRenderer->render(currentCmd, perFrameSet, *camera, globalTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render weather particles (after terrain/water, before characters)
|
// Weather particles
|
||||||
if (weather && camera) {
|
if (weather && camera) {
|
||||||
weather->render(currentCmd, perFrameSet);
|
weather->render(currentCmd, perFrameSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render swim effects (ripples and bubbles)
|
// Swim effects (ripples, bubbles)
|
||||||
if (swimEffects && camera) {
|
if (swimEffects && camera) {
|
||||||
swimEffects->render(currentCmd, perFrameSet);
|
swimEffects->render(currentCmd, perFrameSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render mount dust effects
|
// Mount dust
|
||||||
if (mountDust && camera) {
|
if (mountDust && camera) {
|
||||||
mountDust->render(currentCmd, perFrameSet);
|
mountDust->render(currentCmd, perFrameSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render charge effect (red haze + dust)
|
// Charge effect
|
||||||
if (chargeEffect && camera) {
|
if (chargeEffect && camera) {
|
||||||
chargeEffect->render(currentCmd, perFrameSet);
|
chargeEffect->render(currentCmd, perFrameSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Phase 5: WMO rendering
|
// Quest markers (billboards above NPCs)
|
||||||
// TODO Phase 5: Character rendering
|
|
||||||
// TODO Phase 5: M2 rendering
|
|
||||||
|
|
||||||
// Render quest markers (billboards above NPCs)
|
|
||||||
if (questMarkerRenderer && camera) {
|
if (questMarkerRenderer && camera) {
|
||||||
questMarkerRenderer->render(currentCmd, perFrameSet, *camera);
|
questMarkerRenderer->render(currentCmd, perFrameSet, *camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minimap display overlay (screen-space quad with composite texture)
|
// Underwater tint overlay — detect camera position relative to water surface
|
||||||
|
if (overlayPipeline && cameraController && cameraController->isSwimming()
|
||||||
|
&& waterRenderer && camera) {
|
||||||
|
glm::vec3 camPos = camera->getPosition();
|
||||||
|
auto waterH = waterRenderer->getWaterHeightAt(camPos.x, camPos.y);
|
||||||
|
constexpr float UNDERWATER_EPS = 1.10f;
|
||||||
|
constexpr float MAX_DEPTH = 12.0f;
|
||||||
|
if (waterH && camPos.z < (*waterH - UNDERWATER_EPS)
|
||||||
|
&& (*waterH - camPos.z) <= MAX_DEPTH) {
|
||||||
|
// Check for canal (liquid type 5, 13, 17) vs open water
|
||||||
|
bool canal = false;
|
||||||
|
if (auto lt = waterRenderer->getWaterTypeAt(camPos.x, camPos.y))
|
||||||
|
canal = (*lt == 5 || *lt == 13 || *lt == 17);
|
||||||
|
glm::vec4 tint = canal
|
||||||
|
? glm::vec4(0.01f, 0.05f, 0.11f, 0.50f)
|
||||||
|
: glm::vec4(0.02f, 0.08f, 0.15f, 0.30f);
|
||||||
|
renderOverlay(tint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimap overlay
|
||||||
if (minimap && minimap->isEnabled() && camera && window) {
|
if (minimap && minimap->isEnabled() && camera && window) {
|
||||||
glm::vec3 minimapCenter = camera->getPosition();
|
glm::vec3 minimapCenter = camera->getPosition();
|
||||||
if (cameraController && cameraController->isThirdPerson())
|
if (cameraController && cameraController->isThirdPerson())
|
||||||
|
|
@ -2944,323 +3051,9 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||||
window->getWidth(), window->getHeight());
|
window->getWidth(), window->getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Phase 6: Post-process pipeline, shadow mapping, underwater overlay
|
|
||||||
|
|
||||||
auto renderEnd = std::chrono::steady_clock::now();
|
auto renderEnd = std::chrono::steady_clock::now();
|
||||||
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
||||||
|
|
||||||
// ===== STUBBED GL RENDERING (dead code — reference for Phases 4-6) =====
|
|
||||||
#if 0
|
|
||||||
auto renderStart = std::chrono::steady_clock::now();
|
|
||||||
lastTerrainRenderMs = 0.0;
|
|
||||||
lastWMORenderMs = 0.0;
|
|
||||||
lastM2RenderMs = 0.0;
|
|
||||||
|
|
||||||
// Shadow pass (before main scene) — update every frame to avoid temporal popping.
|
|
||||||
if (shadowsEnabled && shadowFBO && shadowShaderProgram && terrainLoaded) {
|
|
||||||
renderShadowPass();
|
|
||||||
} else {
|
|
||||||
// Clear shadow maps when disabled
|
|
||||||
if (terrainRenderer) terrainRenderer->clearShadowMap();
|
|
||||||
if (wmoRenderer) wmoRenderer->clearShadowMap();
|
|
||||||
if (m2Renderer) m2Renderer->clearShadowMap();
|
|
||||||
if (characterRenderer) characterRenderer->clearShadowMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind HDR scene framebuffer for world rendering
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, sceneFBO);
|
|
||||||
glViewport(0, 0, fbWidth, fbHeight);
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
(void)world; // Unused for now
|
|
||||||
|
|
||||||
// Get time of day for sky-related rendering
|
|
||||||
float timeOfDay = skybox ? skybox->getTimeOfDay() : 12.0f;
|
|
||||||
bool underwater = false;
|
|
||||||
bool canalUnderwater = false;
|
|
||||||
|
|
||||||
// Render sky system (unified coordinator for skybox, stars, celestial, clouds, lens flare)
|
|
||||||
if (skySystem && camera) {
|
|
||||||
// Populate SkyParams from lighting manager
|
|
||||||
rendering::SkyParams skyParams;
|
|
||||||
skyParams.timeOfDay = timeOfDay;
|
|
||||||
skyParams.gameTime = gameHandler ? gameHandler->getGameTime() : -1.0f;
|
|
||||||
|
|
||||||
if (lightingManager) {
|
|
||||||
const auto& lighting = lightingManager->getLightingParams();
|
|
||||||
skyParams.directionalDir = lighting.directionalDir;
|
|
||||||
skyParams.sunColor = lighting.diffuseColor;
|
|
||||||
skyParams.skyTopColor = lighting.skyTopColor;
|
|
||||||
skyParams.skyMiddleColor = lighting.skyMiddleColor;
|
|
||||||
skyParams.skyBand1Color = lighting.skyBand1Color;
|
|
||||||
skyParams.skyBand2Color = lighting.skyBand2Color;
|
|
||||||
skyParams.cloudDensity = lighting.cloudDensity;
|
|
||||||
skyParams.fogDensity = lighting.fogDensity;
|
|
||||||
skyParams.horizonGlow = lighting.horizonGlow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Set skyboxModelId from LightSkybox.dbc (future)
|
|
||||||
skyParams.skyboxModelId = 0;
|
|
||||||
skyParams.skyboxHasStars = false; // Gradient skybox has no baked stars
|
|
||||||
|
|
||||||
skySystem->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], *camera, skyParams);
|
|
||||||
} else {
|
|
||||||
// Fallback: render individual components (backwards compatibility)
|
|
||||||
if (skybox && camera) {
|
|
||||||
skybox->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], timeOfDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get lighting parameters for celestial rendering
|
|
||||||
const glm::vec3* sunDir = nullptr;
|
|
||||||
const glm::vec3* sunColor = nullptr;
|
|
||||||
float cloudDensity = 0.0f;
|
|
||||||
float fogDensity = 0.0f;
|
|
||||||
if (lightingManager) {
|
|
||||||
const auto& lighting = lightingManager->getLightingParams();
|
|
||||||
sunDir = &lighting.directionalDir;
|
|
||||||
sunColor = &lighting.diffuseColor;
|
|
||||||
cloudDensity = lighting.cloudDensity;
|
|
||||||
fogDensity = lighting.fogDensity;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (starField && camera) {
|
|
||||||
starField->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], timeOfDay, cloudDensity, fogDensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (celestial && camera) {
|
|
||||||
celestial->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], timeOfDay, sunDir, sunColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clouds && camera) {
|
|
||||||
clouds->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], timeOfDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lensFlare && camera && celestial) {
|
|
||||||
glm::vec3 sunPosition;
|
|
||||||
if (sunDir) {
|
|
||||||
const float sunDistance = 800.0f;
|
|
||||||
sunPosition = -*sunDir * sunDistance;
|
|
||||||
} else {
|
|
||||||
sunPosition = celestial->getSunPosition(timeOfDay);
|
|
||||||
}
|
|
||||||
lensFlare->render(*camera, sunPosition, timeOfDay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply lighting and fog to all renderers
|
|
||||||
if (lightingManager) {
|
|
||||||
const auto& lighting = lightingManager->getLightingParams();
|
|
||||||
|
|
||||||
float lightDir[3] = {lighting.directionalDir.x, lighting.directionalDir.y, lighting.directionalDir.z};
|
|
||||||
float lightColor[3] = {lighting.diffuseColor.r, lighting.diffuseColor.g, lighting.diffuseColor.b};
|
|
||||||
float ambientColor[3] = {lighting.ambientColor.r, lighting.ambientColor.g, lighting.ambientColor.b};
|
|
||||||
float fogColorArray[3] = {lighting.fogColor.r, lighting.fogColor.g, lighting.fogColor.b};
|
|
||||||
|
|
||||||
if (wmoRenderer) {
|
|
||||||
wmoRenderer->setLighting(lightDir, lightColor, ambientColor);
|
|
||||||
wmoRenderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]),
|
|
||||||
lighting.fogStart, lighting.fogEnd);
|
|
||||||
}
|
|
||||||
if (m2Renderer) {
|
|
||||||
m2Renderer->setLighting(lightDir, lightColor, ambientColor);
|
|
||||||
m2Renderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]),
|
|
||||||
lighting.fogStart, lighting.fogEnd);
|
|
||||||
}
|
|
||||||
if (characterRenderer) {
|
|
||||||
characterRenderer->setLighting(lightDir, lightColor, ambientColor);
|
|
||||||
characterRenderer->setFog(glm::vec3(fogColorArray[0], fogColorArray[1], fogColorArray[2]),
|
|
||||||
lighting.fogStart, lighting.fogEnd);
|
|
||||||
}
|
|
||||||
} else if (skybox) {
|
|
||||||
// Fallback to skybox-based fog if no lighting manager
|
|
||||||
glm::vec3 horizonColor = skybox->getHorizonColor(timeOfDay);
|
|
||||||
if (wmoRenderer) wmoRenderer->setFog(horizonColor, 100.0f, 600.0f);
|
|
||||||
if (m2Renderer) m2Renderer->setFog(horizonColor, 100.0f, 600.0f);
|
|
||||||
if (characterRenderer) characterRenderer->setFog(horizonColor, 100.0f, 600.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render terrain if loaded and enabled
|
|
||||||
if (terrainEnabled && terrainLoaded && terrainRenderer && camera) {
|
|
||||||
// Check if camera/character is underwater for fog override
|
|
||||||
if (cameraController && cameraController->isSwimming() && waterRenderer && camera) {
|
|
||||||
glm::vec3 camPos = camera->getPosition();
|
|
||||||
auto waterH = waterRenderer->getWaterHeightAt(camPos.x, camPos.y);
|
|
||||||
constexpr float MAX_UNDERWATER_DEPTH = 12.0f;
|
|
||||||
// Require camera to be meaningfully below the surface before
|
|
||||||
// underwater fog/tint kicks in (avoids "wrong plane" near surface).
|
|
||||||
constexpr float UNDERWATER_ENTER_EPS = 1.10f;
|
|
||||||
if (waterH &&
|
|
||||||
camPos.z < (*waterH - UNDERWATER_ENTER_EPS) &&
|
|
||||||
(*waterH - camPos.z) <= MAX_UNDERWATER_DEPTH) {
|
|
||||||
underwater = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underwater) {
|
|
||||||
glm::vec3 camPos = camera->getPosition();
|
|
||||||
std::optional<uint16_t> liquidType = waterRenderer ? waterRenderer->getWaterTypeAt(camPos.x, camPos.y) : std::nullopt;
|
|
||||||
if (!liquidType && cameraController) {
|
|
||||||
const glm::vec3* followTarget = cameraController->getFollowTarget();
|
|
||||||
if (followTarget && waterRenderer) {
|
|
||||||
liquidType = waterRenderer->getWaterTypeAt(followTarget->x, followTarget->y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canalUnderwater = liquidType && (*liquidType == 5 || *liquidType == 13 || *liquidType == 17);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply lighting from lighting manager
|
|
||||||
if (lightingManager) {
|
|
||||||
const auto& lighting = lightingManager->getLightingParams();
|
|
||||||
|
|
||||||
// Set lighting (direction, color, ambient)
|
|
||||||
float lightDir[3] = {lighting.directionalDir.x, lighting.directionalDir.y, lighting.directionalDir.z};
|
|
||||||
float lightColor[3] = {lighting.diffuseColor.r, lighting.diffuseColor.g, lighting.diffuseColor.b};
|
|
||||||
float ambientColor[3] = {lighting.ambientColor.r, lighting.ambientColor.g, lighting.ambientColor.b};
|
|
||||||
terrainRenderer->setLighting(lightDir, lightColor, ambientColor);
|
|
||||||
|
|
||||||
// Set fog
|
|
||||||
float fogColor[3] = {lighting.fogColor.r, lighting.fogColor.g, lighting.fogColor.b};
|
|
||||||
terrainRenderer->setFog(fogColor, lighting.fogStart, lighting.fogEnd);
|
|
||||||
} else if (skybox) {
|
|
||||||
// Fallback to skybox-based fog if no lighting manager
|
|
||||||
glm::vec3 horizonColor = skybox->getHorizonColor(timeOfDay);
|
|
||||||
float fogColorArray[3] = {horizonColor.r, horizonColor.g, horizonColor.b};
|
|
||||||
terrainRenderer->setFog(fogColorArray, 400.0f, 1200.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto terrainStart = std::chrono::steady_clock::now();
|
|
||||||
terrainRenderer->render(*camera);
|
|
||||||
auto terrainEnd = std::chrono::steady_clock::now();
|
|
||||||
lastTerrainRenderMs = std::chrono::duration<double, std::milli>(terrainEnd - terrainStart).count();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render weather particles (after terrain/water, before characters)
|
|
||||||
if (weather && camera) {
|
|
||||||
weather->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render swim effects (ripples and bubbles)
|
|
||||||
if (swimEffects && camera) {
|
|
||||||
swimEffects->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render mount dust effects
|
|
||||||
if (mountDust && camera) {
|
|
||||||
mountDust->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render charge effect (red haze + dust)
|
|
||||||
if (chargeEffect && camera) {
|
|
||||||
chargeEffect->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute view/projection once for all sub-renderers
|
|
||||||
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
|
||||||
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
|
||||||
|
|
||||||
// Render WMO buildings first so selection circle can be drawn above WMO depth.
|
|
||||||
if (wmoRenderer && camera) {
|
|
||||||
auto wmoStart = std::chrono::steady_clock::now();
|
|
||||||
wmoRenderer->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], *camera);
|
|
||||||
auto wmoEnd = std::chrono::steady_clock::now();
|
|
||||||
lastWMORenderMs = std::chrono::duration<double, std::milli>(wmoEnd - wmoStart).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render selection circle after WMO so interiors/shafts do not hide it.
|
|
||||||
// It remains before character/M2 passes so units still draw over the ring.
|
|
||||||
renderSelectionCircle(view, projection);
|
|
||||||
|
|
||||||
// Render characters (after selection circle)
|
|
||||||
if (characterRenderer && camera) {
|
|
||||||
characterRenderer->render(currentCmd, perFrameDescSets[vkCtx->getCurrentFrame()], *camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render M2 doodads (trees, rocks, etc.)
|
|
||||||
if (m2Renderer && camera) {
|
|
||||||
// Dim M2 lighting when player is inside a WMO
|
|
||||||
if (cameraController) {
|
|
||||||
m2Renderer->setInsideInterior(cameraController->isInsideWMO());
|
|
||||||
m2Renderer->setOnTaxi(cameraController->isOnTaxi());
|
|
||||||
}
|
|
||||||
auto m2Start = std::chrono::steady_clock::now();
|
|
||||||
uint32_t frame = vkCtx->getCurrentFrame();
|
|
||||||
VkDescriptorSet pfSet = perFrameDescSets[frame];
|
|
||||||
m2Renderer->render(currentCmd, pfSet, *camera);
|
|
||||||
m2Renderer->renderSmokeParticles(currentCmd, pfSet);
|
|
||||||
m2Renderer->renderM2Particles(currentCmd, pfSet);
|
|
||||||
auto m2End = std::chrono::steady_clock::now();
|
|
||||||
lastM2RenderMs = std::chrono::duration<double, std::milli>(m2End - m2Start).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render water after opaque terrain/WMO/M2 so transparent surfaces remain visible.
|
|
||||||
if (waterRenderer && camera) {
|
|
||||||
static float time = 0.0f;
|
|
||||||
time += 0.016f; // Approximate frame time
|
|
||||||
waterRenderer->render(*camera, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render quest markers (billboards above NPCs)
|
|
||||||
if (questMarkerRenderer && camera) {
|
|
||||||
questMarkerRenderer->render(*camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full-screen underwater tint so WMO/M2/characters also feel submerged.
|
|
||||||
if (false && underwater && underwaterOverlayShader && underwaterOverlayVAO) {
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
underwaterOverlayShader->use();
|
|
||||||
if (canalUnderwater) {
|
|
||||||
underwaterOverlayShader->setUniform("uTint", glm::vec4(0.01f, 0.05f, 0.11f, 0.50f));
|
|
||||||
} else {
|
|
||||||
underwaterOverlayShader->setUniform("uTint", glm::vec4(0.02f, 0.08f, 0.15f, 0.30f));
|
|
||||||
}
|
|
||||||
glBindVertexArray(underwaterOverlayVAO);
|
|
||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Resolve MSAA → non-MSAA texture ---
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, sceneFBO);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
|
|
||||||
glBlitFramebuffer(0, 0, fbWidth, fbHeight, 0, 0, fbWidth, fbHeight,
|
|
||||||
GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
|
||||||
|
|
||||||
// --- Post-process: tonemap via fullscreen quad ---
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glViewport(0, 0, window->getWidth(), window->getHeight());
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
if (postProcessShader && screenQuadVAO) {
|
|
||||||
postProcessShader->use();
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, resolveColorTex);
|
|
||||||
postProcessShader->setUniform("uScene", 0);
|
|
||||||
glBindVertexArray(screenQuadVAO);
|
|
||||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
postProcessShader->unuse();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render minimap overlay (after post-process so it's not overwritten)
|
|
||||||
if (minimap && camera && window) {
|
|
||||||
glm::vec3 minimapCenter = camera->getPosition();
|
|
||||||
if (cameraController && cameraController->isThirdPerson()) {
|
|
||||||
minimapCenter = characterPosition;
|
|
||||||
}
|
|
||||||
minimap->render(*camera, minimapCenter, window->getWidth(), window->getHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
|
|
||||||
auto renderEnd = std::chrono::steady_clock::now();
|
|
||||||
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
|
||||||
#endif // Stubbed GL rendering
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initPostProcess(), resizePostProcess(), shutdownPostProcess() removed —
|
// initPostProcess(), resizePostProcess(), shutdownPostProcess() removed —
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue