Add player water ripples and separate 1x water pass for MSAA compatibility

Player interaction ripples: vertex shader adds radial damped-sine displacement
centered on player position, fragment shader adds matching normal perturbation
for specular highlights. Player XY packed into shadowParams.zw, ripple strength
into fogParams.w. Separate 1x render pass for water when MSAA is active to
avoid MSAA-induced darkening — water renders after main pass resolves, using
the resolved swapchain image and depth resolve target. Water 1x framebuffers
rebuilt on swapchain recreate (window resize).
This commit is contained in:
Kelsi 2026-02-22 22:34:48 -08:00
parent 67e63653a4
commit 03a62526e1
11 changed files with 1306 additions and 115 deletions

View file

@ -386,9 +386,17 @@ private:
GPUPerFrameData currentFrameData{};
float globalTime = 0.0f;
// Per-frame reflection UBO (mirrors camera for planar reflections)
VkBuffer reflPerFrameUBO = VK_NULL_HANDLE;
VmaAllocation reflPerFrameUBOAlloc = VK_NULL_HANDLE;
void* reflPerFrameUBOMapped = nullptr;
VkDescriptorSet reflPerFrameDescSet = VK_NULL_HANDLE;
bool createPerFrameResources();
void destroyPerFrameResources();
void updatePerFrameUBO();
void setupWater1xPass();
void renderReflectionPass();
// Active character previews for off-screen rendering
std::vector<CharacterPreview*> activePreviews_;

View file

@ -85,6 +85,8 @@ public:
return (depthResolveImage == VK_NULL_HANDLE) && (msaaSamples_ > VK_SAMPLE_COUNT_1_BIT);
}
VkFormat getDepthFormat() const { return depthFormat; }
VkImageView getDepthResolveImageView() const { return depthResolveImageView; }
VkImageView getDepthImageView() const { return depthImageView; }
// UI texture upload: creates a Vulkan texture from RGBA data and returns
// a VkDescriptorSet suitable for use as ImTextureID.

View file

@ -64,6 +64,7 @@ public:
// Multisampling
PipelineBuilder& setMultisample(VkSampleCountFlagBits samples);
PipelineBuilder& setAlphaToCoverage(bool enable);
// Pipeline layout
PipelineBuilder& setLayout(VkPipelineLayout layout);
@ -80,6 +81,7 @@ public:
// Common blend states
static VkPipelineColorBlendAttachmentState blendDisabled();
static VkPipelineColorBlendAttachmentState blendAlpha();
static VkPipelineColorBlendAttachmentState blendPremultiplied();
static VkPipelineColorBlendAttachmentState blendAdditive();
private:
@ -98,6 +100,7 @@ private:
float depthBiasConstant_ = 0.0f;
float depthBiasSlope_ = 0.0f;
VkSampleCountFlagBits msaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
bool alphaToCoverage_ = false;
std::vector<VkPipelineColorBlendAttachmentState> colorBlendAttachments_;
VkPipelineLayout pipelineLayout_ = VK_NULL_HANDLE;
VkRenderPass renderPass_ = VK_NULL_HANDLE;

View file

@ -4,6 +4,7 @@
#include <memory>
#include <optional>
#include <cstdint>
#include <functional>
#include <vulkan/vulkan.h>
#include <vk_mem_alloc.h>
#include <glm/glm.hpp>
@ -61,7 +62,8 @@ struct WaterSurface {
};
/**
* Water renderer (Vulkan)
* Water renderer (Vulkan) with planar reflections, Gerstner waves,
* GGX specular, shoreline foam, and subsurface scattering.
*/
class WaterRenderer {
public:
@ -81,13 +83,44 @@ public:
void recreatePipelines();
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time);
// Separate 1x pass for MSAA mode — water rendered after MSAA resolve
bool createWater1xPass(VkFormat colorFormat, VkFormat depthFormat);
void createWater1xFramebuffers(const std::vector<VkImageView>& swapViews,
VkImageView depthView, VkExtent2D extent);
void destroyWater1xResources();
bool beginWater1xPass(VkCommandBuffer cmd, uint32_t imageIndex, VkExtent2D extent);
void endWater1xPass(VkCommandBuffer cmd);
bool hasWater1xPass() const { return water1xRenderPass != VK_NULL_HANDLE; }
VkRenderPass getWater1xRenderPass() const { return water1xRenderPass; }
void render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& camera, float time, bool use1x = false);
void captureSceneHistory(VkCommandBuffer cmd,
VkImage srcColorImage,
VkImage srcDepthImage,
VkExtent2D srcExtent,
bool srcDepthIsMsaa);
// --- Planar reflection pass ---
// Call sequence: beginReflectionPass → [render scene] → endReflectionPass
bool beginReflectionPass(VkCommandBuffer cmd);
void endReflectionPass(VkCommandBuffer cmd);
// Get the dominant water height near a position (for reflection plane)
std::optional<float> getDominantWaterHeight(const glm::vec3& cameraPos) const;
// Compute reflected view matrix for a given water height
static glm::mat4 computeReflectedView(const Camera& camera, float waterHeight);
// Compute oblique clip projection to clip below-water geometry in reflection
static glm::mat4 computeObliqueProjection(const glm::mat4& proj, const glm::mat4& view, float waterHeight);
// Update the reflection UBO with reflected viewProj matrix
void updateReflectionUBO(const glm::mat4& reflViewProj);
VkRenderPass getReflectionRenderPass() const { return reflectionRenderPass; }
VkExtent2D getReflectionExtent() const { return {REFLECTION_WIDTH, REFLECTION_HEIGHT}; }
bool hasReflectionPass() const { return reflectionRenderPass != VK_NULL_HANDLE; }
bool hasSurfaces() const { return !surfaces.empty(); }
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
@ -108,6 +141,10 @@ private:
void createSceneHistoryResources(VkExtent2D extent, VkFormat colorFormat, VkFormat depthFormat);
void destroySceneHistoryResources();
// Reflection pass resources
void createReflectionResources();
void destroyReflectionResources();
VkContext* vkCtx = nullptr;
// Pipeline
@ -131,6 +168,30 @@ private:
VkExtent2D sceneHistoryExtent = {0, 0};
bool sceneHistoryReady = false;
// Planar reflection resources
static constexpr uint32_t REFLECTION_WIDTH = 512;
static constexpr uint32_t REFLECTION_HEIGHT = 512;
VkRenderPass reflectionRenderPass = VK_NULL_HANDLE;
VkFramebuffer reflectionFramebuffer = VK_NULL_HANDLE;
VkImage reflectionColorImage = VK_NULL_HANDLE;
VmaAllocation reflectionColorAlloc = VK_NULL_HANDLE;
VkImageView reflectionColorView = VK_NULL_HANDLE;
VkImage reflectionDepthImage = VK_NULL_HANDLE;
VmaAllocation reflectionDepthAlloc = VK_NULL_HANDLE;
VkImageView reflectionDepthView = VK_NULL_HANDLE;
VkSampler reflectionSampler = VK_NULL_HANDLE;
VkImageLayout reflectionColorLayout = VK_IMAGE_LAYOUT_UNDEFINED;
// Reflection UBO (mat4 reflViewProj)
::VkBuffer reflectionUBO = VK_NULL_HANDLE;
VmaAllocation reflectionUBOAlloc = VK_NULL_HANDLE;
void* reflectionUBOMapped = nullptr;
// Separate 1x water pass (used when MSAA is active)
VkRenderPass water1xRenderPass = VK_NULL_HANDLE;
VkPipeline water1xPipeline = VK_NULL_HANDLE;
std::vector<VkFramebuffer> water1xFramebuffers;
std::vector<WaterSurface> surfaces;
bool renderingEnabled = true;
};