mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 08:00:14 +00:00
Add ambient insect particles near water vegetation, fix firefly particles, and improve water foam
- Spawn dark point-sprite insects buzzing around cattails/reeds/kelp/seaweed - Fix firefly M2 particles: exempt from alpha dampening and forced gravity - Make water shoreline/crest foam more irregular with UV warping and bluer tint
This commit is contained in:
parent
c35b40391f
commit
4db97e37b7
9 changed files with 332 additions and 20 deletions
|
|
@ -2,6 +2,7 @@
|
|||
#include "rendering/camera.hpp"
|
||||
#include "rendering/camera_controller.hpp"
|
||||
#include "rendering/water_renderer.hpp"
|
||||
#include "rendering/m2_renderer.hpp"
|
||||
#include "rendering/vk_context.hpp"
|
||||
#include "rendering/vk_shader.hpp"
|
||||
#include "rendering/vk_pipeline.hpp"
|
||||
|
|
@ -152,6 +153,50 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
}
|
||||
}
|
||||
|
||||
// ---- Insect pipeline (dark point sprites) ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
if (!vertModule.loadFromFile(device, "assets/shaders/swim_ripple.vert.spv")) {
|
||||
LOG_ERROR("Failed to load insect vertex shader");
|
||||
return false;
|
||||
}
|
||||
VkShaderModule fragModule;
|
||||
if (!fragModule.loadFromFile(device, "assets/shaders/swim_insect.frag.spv")) {
|
||||
LOG_ERROR("Failed to load insect fragment shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
insectPipelineLayout = createPipelineLayout(device, {perFrameLayout}, {});
|
||||
if (insectPipelineLayout == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Failed to create insect pipeline layout");
|
||||
return false;
|
||||
}
|
||||
|
||||
insectPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(insectPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
|
||||
if (insectPipeline == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Failed to create insect pipeline");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Create dynamic mapped vertex buffers ----
|
||||
rippleDynamicVBSize = MAX_RIPPLE_PARTICLES * 5 * sizeof(float);
|
||||
{
|
||||
|
|
@ -179,10 +224,25 @@ bool SwimEffects::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayou
|
|||
}
|
||||
}
|
||||
|
||||
insectDynamicVBSize = MAX_INSECT_PARTICLES * 5 * sizeof(float);
|
||||
{
|
||||
AllocatedBuffer buf = createBuffer(vkCtx->getAllocator(), insectDynamicVBSize,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
||||
insectDynamicVB = buf.buffer;
|
||||
insectDynamicVBAlloc = buf.allocation;
|
||||
insectDynamicVBAllocInfo = buf.info;
|
||||
if (insectDynamicVB == VK_NULL_HANDLE) {
|
||||
LOG_ERROR("Failed to create insect dynamic vertex buffer");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ripples.reserve(MAX_RIPPLE_PARTICLES);
|
||||
bubbles.reserve(MAX_BUBBLE_PARTICLES);
|
||||
insects.reserve(MAX_INSECT_PARTICLES);
|
||||
rippleVertexData.reserve(MAX_RIPPLE_PARTICLES * 5);
|
||||
bubbleVertexData.reserve(MAX_BUBBLE_PARTICLES * 5);
|
||||
insectVertexData.reserve(MAX_INSECT_PARTICLES * 5);
|
||||
|
||||
LOG_INFO("Swim effects initialized");
|
||||
return true;
|
||||
|
|
@ -220,11 +280,26 @@ void SwimEffects::shutdown() {
|
|||
bubbleDynamicVB = VK_NULL_HANDLE;
|
||||
bubbleDynamicVBAlloc = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (insectPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, insectPipeline, nullptr);
|
||||
insectPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (insectPipelineLayout != VK_NULL_HANDLE) {
|
||||
vkDestroyPipelineLayout(device, insectPipelineLayout, nullptr);
|
||||
insectPipelineLayout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (insectDynamicVB != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(allocator, insectDynamicVB, insectDynamicVBAlloc);
|
||||
insectDynamicVB = VK_NULL_HANDLE;
|
||||
insectDynamicVBAlloc = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
vkCtx = nullptr;
|
||||
ripples.clear();
|
||||
bubbles.clear();
|
||||
insects.clear();
|
||||
}
|
||||
|
||||
void SwimEffects::recreatePipelines() {
|
||||
|
|
@ -240,6 +315,10 @@ void SwimEffects::recreatePipelines() {
|
|||
vkDestroyPipeline(device, bubblePipeline, nullptr);
|
||||
bubblePipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
if (insectPipeline != VK_NULL_HANDLE) {
|
||||
vkDestroyPipeline(device, insectPipeline, nullptr);
|
||||
insectPipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
// Shared vertex input: pos(vec3) + size(float) + alpha(float) = 5 floats
|
||||
VkVertexInputBindingDescription binding{};
|
||||
|
|
@ -319,6 +398,33 @@ void SwimEffects::recreatePipelines() {
|
|||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
|
||||
// ---- Rebuild insect pipeline ----
|
||||
{
|
||||
VkShaderModule vertModule;
|
||||
vertModule.loadFromFile(device, "assets/shaders/swim_ripple.vert.spv");
|
||||
VkShaderModule fragModule;
|
||||
fragModule.loadFromFile(device, "assets/shaders/swim_insect.frag.spv");
|
||||
|
||||
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
||||
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
|
||||
insectPipeline = PipelineBuilder()
|
||||
.setShaders(vertStage, fragStage)
|
||||
.setVertexInput({binding}, attrs)
|
||||
.setTopology(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
|
||||
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
||||
.setDepthTest(true, false, VK_COMPARE_OP_LESS)
|
||||
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
||||
.setMultisample(vkCtx->getMsaaSamples())
|
||||
.setLayout(insectPipelineLayout)
|
||||
.setRenderPass(vkCtx->getImGuiRenderPass())
|
||||
.setDynamicStates(dynamicStates)
|
||||
.build(device);
|
||||
|
||||
vertModule.destroy();
|
||||
fragModule.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void SwimEffects::spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, float waterH) {
|
||||
|
|
@ -384,6 +490,31 @@ void SwimEffects::spawnBubble(const glm::vec3& pos, float /*waterH*/) {
|
|||
bubbles.push_back(p);
|
||||
}
|
||||
|
||||
void SwimEffects::spawnInsect(const glm::vec3& vegPos) {
|
||||
if (static_cast<int>(insects.size()) >= MAX_INSECT_PARTICLES) return;
|
||||
|
||||
InsectParticle p;
|
||||
p.orbitCenter = vegPos;
|
||||
p.phase = randFloat(0.0f, 6.2832f);
|
||||
p.orbitRadius = randFloat(0.5f, 2.0f);
|
||||
p.orbitSpeed = randFloat(1.5f, 4.0f);
|
||||
p.heightOffset = randFloat(0.5f, 3.0f);
|
||||
p.lifetime = 0.0f;
|
||||
p.maxLifetime = randFloat(3.0f, 8.0f);
|
||||
p.size = randFloat(2.0f, 3.0f);
|
||||
p.alpha = randFloat(0.6f, 0.9f);
|
||||
|
||||
// Start at orbit position
|
||||
float angle = p.phase;
|
||||
p.position = vegPos + glm::vec3(
|
||||
std::cos(angle) * p.orbitRadius,
|
||||
std::sin(angle) * p.orbitRadius,
|
||||
p.heightOffset
|
||||
);
|
||||
|
||||
insects.push_back(p);
|
||||
}
|
||||
|
||||
void SwimEffects::update(const Camera& camera, const CameraController& cc,
|
||||
const WaterRenderer& water, float deltaTime) {
|
||||
glm::vec3 camPos = camera.getPosition();
|
||||
|
|
@ -438,6 +569,23 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc,
|
|||
bubbles.clear();
|
||||
}
|
||||
|
||||
// --- Insect spawning near water vegetation ---
|
||||
if (m2Renderer) {
|
||||
auto vegPositions = m2Renderer->getWaterVegetationPositions(camPos, 60.0f);
|
||||
if (!vegPositions.empty()) {
|
||||
// Spawn rate: ~4/sec per nearby vegetation cluster (capped by MAX_INSECT_PARTICLES)
|
||||
float spawnRate = std::min(static_cast<float>(vegPositions.size()) * 4.0f, 20.0f);
|
||||
insectSpawnAccum += spawnRate * deltaTime;
|
||||
while (insectSpawnAccum >= 1.0f && static_cast<int>(insects.size()) < MAX_INSECT_PARTICLES) {
|
||||
// Pick a random vegetation position to spawn near
|
||||
int idx = static_cast<int>(randFloat(0.0f, static_cast<float>(vegPositions.size()) - 0.01f));
|
||||
spawnInsect(vegPositions[idx]);
|
||||
insectSpawnAccum -= 1.0f;
|
||||
}
|
||||
if (insectSpawnAccum > 2.0f) insectSpawnAccum = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Update ripples (splash droplets with gravity) ---
|
||||
for (int i = static_cast<int>(ripples.size()) - 1; i >= 0; --i) {
|
||||
auto& p = ripples[i];
|
||||
|
|
@ -487,6 +635,42 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc,
|
|||
}
|
||||
}
|
||||
|
||||
// --- Update insects (erratic orbiting flight) ---
|
||||
for (int i = static_cast<int>(insects.size()) - 1; i >= 0; --i) {
|
||||
auto& p = insects[i];
|
||||
p.lifetime += deltaTime;
|
||||
if (p.lifetime >= p.maxLifetime) {
|
||||
insects[i] = insects.back();
|
||||
insects.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
float t = p.lifetime / p.maxLifetime;
|
||||
float time = p.lifetime * p.orbitSpeed + p.phase;
|
||||
|
||||
// Erratic looping: primary orbit + secondary wobble
|
||||
float primaryAngle = time;
|
||||
float wobbleAngle = std::sin(time * 2.3f) * 0.8f;
|
||||
float radius = p.orbitRadius + std::sin(time * 1.7f) * 0.3f;
|
||||
|
||||
float heightWobble = std::sin(time * 1.1f + p.phase * 0.5f) * 0.5f;
|
||||
|
||||
p.position = p.orbitCenter + glm::vec3(
|
||||
std::cos(primaryAngle + wobbleAngle) * radius,
|
||||
std::sin(primaryAngle + wobbleAngle) * radius,
|
||||
p.heightOffset + heightWobble
|
||||
);
|
||||
|
||||
// Fade in/out
|
||||
if (t < 0.1f) {
|
||||
p.alpha = glm::mix(0.0f, 0.8f, t / 0.1f);
|
||||
} else if (t > 0.85f) {
|
||||
p.alpha = glm::mix(0.8f, 0.0f, (t - 0.85f) / 0.15f);
|
||||
} else {
|
||||
p.alpha = 0.8f;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Build vertex data ---
|
||||
rippleVertexData.clear();
|
||||
for (const auto& p : ripples) {
|
||||
|
|
@ -505,10 +689,19 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc,
|
|||
bubbleVertexData.push_back(p.size);
|
||||
bubbleVertexData.push_back(p.alpha);
|
||||
}
|
||||
|
||||
insectVertexData.clear();
|
||||
for (const auto& p : insects) {
|
||||
insectVertexData.push_back(p.position.x);
|
||||
insectVertexData.push_back(p.position.y);
|
||||
insectVertexData.push_back(p.position.z);
|
||||
insectVertexData.push_back(p.size);
|
||||
insectVertexData.push_back(p.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
void SwimEffects::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
||||
if (rippleVertexData.empty() && bubbleVertexData.empty()) return;
|
||||
if (rippleVertexData.empty() && bubbleVertexData.empty() && insectVertexData.empty()) return;
|
||||
|
||||
VkDeviceSize offset = 0;
|
||||
|
||||
|
|
@ -539,6 +732,20 @@ void SwimEffects::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
|||
vkCmdBindVertexBuffers(cmd, 0, 1, &bubbleDynamicVB, &offset);
|
||||
vkCmdDraw(cmd, static_cast<uint32_t>(bubbleVertexData.size() / 5), 1, 0, 0);
|
||||
}
|
||||
|
||||
// --- Render insects ---
|
||||
if (!insectVertexData.empty() && insectPipeline != VK_NULL_HANDLE) {
|
||||
VkDeviceSize uploadSize = insectVertexData.size() * sizeof(float);
|
||||
if (insectDynamicVBAllocInfo.pMappedData) {
|
||||
std::memcpy(insectDynamicVBAllocInfo.pMappedData, insectVertexData.data(), uploadSize);
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, insectPipeline);
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, insectPipelineLayout,
|
||||
0, 1, &perFrameSet, 0, nullptr);
|
||||
vkCmdBindVertexBuffers(cmd, 0, 1, &insectDynamicVB, &offset);
|
||||
vkCmdDraw(cmd, static_cast<uint32_t>(insectVertexData.size() / 5), 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue