Files
WoWee/include/rendering/overlay_system.hpp
Kelsi 0b51cfec91 feat(water): sweep a waterline across the view when the camera crosses the surface
Crossing the surface was a step change. The underwater tint did not start
until the eye was 1.5 units down, and when it did it covered the whole
screen at once — so there was a dead zone at the surface, then a pop, and
never a waterline. What the camera showed in between was the water plane
seen edge-on: a razor-straight horizontal cut.

The overlay now takes a boundary rather than covering everything. It runs
from off the bottom of the screen to off the top as the eye moves through
a band around the surface, so the waterline rises past the lens instead of
switching, and the edge carries a small ripple so it does not read as a
straight cut. Fully submerged puts the line past the top, which is the
previous behaviour exactly.

The tint also builds from the surface now rather than from 1.5 down, with
a floor held through the crossing so the submerged half reads as water
rather than clear air.
2026-07-31 11:26:59 -07:00

96 lines
3.7 KiB
C++

#pragma once
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <glm/mat4x4.hpp>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <functional>
#include <optional>
namespace wowee {
namespace rendering {
class VkContext;
/// Manages selection circle and fullscreen overlay Vulkan pipelines.
/// Extracted from Renderer to isolate overlay rendering resources.
class OverlaySystem {
public:
/// Height query callable: returns floor height at (x, y) or (x, y, probeZ).
using HeightQuery2D = std::function<std::optional<float>(float x, float y)>;
using HeightQuery3D = std::function<std::optional<float>(float x, float y, float probeZ)>;
explicit OverlaySystem(VkContext* ctx);
~OverlaySystem();
OverlaySystem(const OverlaySystem&) = delete;
OverlaySystem& operator=(const OverlaySystem&) = delete;
// Selection circle
void setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color);
void clearSelectionCircle();
void renderSelectionCircle(const glm::mat4& view, const glm::mat4& projection,
VkCommandBuffer cmd,
HeightQuery2D terrainHeight,
HeightQuery3D wmoHeight,
HeightQuery3D m2Height);
// Fullscreen color overlay (underwater tint, etc.)
void renderOverlay(const glm::vec4& color, VkCommandBuffer cmd);
/// Underwater tint bounded by a waterline that sweeps across the view as the
/// camera crosses the surface. lineNdc runs from +1 (line off the bottom, so
/// nothing is tinted) to -1 (off the top, so everything is), softness spans
/// the meniscus and rippleAmp bends the edge.
void renderWaterline(const glm::vec4& color, float lineNdc, float softness,
float rippleAmp, float time, VkCommandBuffer cmd);
// Fullscreen multiplicative brightness scale (scene.rgb *= scale). Uses a
// dst-color blend so brightness > 1 truly scales luminance instead of
// lerping toward white (which washed everything out). scale > 1 only.
void renderBrightnessScale(float scale, VkCommandBuffer cmd);
/// Destroy all Vulkan resources (called before VkContext teardown).
void cleanup();
/// Recreate pipelines after swapchain resize / MSAA change.
void recreatePipelines();
private:
void initSelectionCircle();
void initOverlayPipeline();
void initBrightnessPipeline();
VkContext* vkCtx_ = nullptr;
// Selection circle resources
VkPipeline selCirclePipeline_ = VK_NULL_HANDLE;
VkPipelineLayout selCirclePipelineLayout_ = VK_NULL_HANDLE;
::VkBuffer selCircleVertBuf_ = VK_NULL_HANDLE;
VmaAllocation selCircleVertAlloc_ = VK_NULL_HANDLE;
::VkBuffer selCircleIdxBuf_ = VK_NULL_HANDLE;
VmaAllocation selCircleIdxAlloc_ = VK_NULL_HANDLE;
int selCircleVertCount_ = 0;
glm::vec3 selCirclePos_{0.0f};
glm::vec3 selCircleColor_{1.0f, 0.0f, 0.0f};
float selCircleRadius_ = 1.5f;
bool selCircleVisible_ = false;
// Floor-snap cache: the terrain/WMO/M2 floor queries are collision
// raycasts, so reuse the result while the target stands still. Refreshed
// periodically in case world geometry streams in around the target.
glm::vec3 selCircleFloorCachePos_{0.0f};
float selCircleFloorCacheZ_ = 0.0f;
int selCircleFloorCacheAge_ = -1; // -1 = invalid; counts frames since query
// Fullscreen overlay resources
VkPipeline overlayPipeline_ = VK_NULL_HANDLE;
VkPipelineLayout overlayPipelineLayout_ = VK_NULL_HANDLE;
// Multiplicative brightness pipeline (shares overlayPipelineLayout_).
VkPipeline brightnessPipeline_ = VK_NULL_HANDLE;
};
} // namespace rendering
} // namespace wowee