mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 16:10:14 +00:00
- MSAA: conditional 2-att (off) vs 3-att (on) render pass with auto-resolve - MSAA: multisampled color+depth images, query max supported sample count - MSAA: .setMultisample() on all 25+ main-pass pipelines across 17 renderers - MSAA: recreatePipelines() on every sub-renderer for runtime MSAA changes - MSAA: Renderer::setMsaaSamples() orchestrates swapchain+pipeline+ImGui rebuild - MSAA: Anti-Aliasing combo (Off/2x/4x/8x) in Video settings, persisted - Update auth screen assets and terrain fragment shader
313 lines
10 KiB
C++
313 lines
10 KiB
C++
#include "rendering/mount_dust.hpp"
|
|
#include "rendering/camera.hpp"
|
|
#include "rendering/vk_context.hpp"
|
|
#include "rendering/vk_shader.hpp"
|
|
#include "rendering/vk_pipeline.hpp"
|
|
#include "rendering/vk_frame_data.hpp"
|
|
#include "rendering/vk_utils.hpp"
|
|
#include "core/logger.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <random>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
static std::mt19937& rng() {
|
|
static std::random_device rd;
|
|
static std::mt19937 gen(rd());
|
|
return gen;
|
|
}
|
|
|
|
static float randFloat(float lo, float hi) {
|
|
std::uniform_real_distribution<float> dist(lo, hi);
|
|
return dist(rng());
|
|
}
|
|
|
|
MountDust::MountDust() = default;
|
|
MountDust::~MountDust() { shutdown(); }
|
|
|
|
bool MountDust::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|
LOG_INFO("Initializing mount dust effects");
|
|
|
|
vkCtx = ctx;
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
// Load SPIR-V shaders
|
|
VkShaderModule vertModule;
|
|
if (!vertModule.loadFromFile(device, "assets/shaders/mount_dust.vert.spv")) {
|
|
LOG_ERROR("Failed to load mount_dust vertex shader");
|
|
return false;
|
|
}
|
|
VkShaderModule fragModule;
|
|
if (!fragModule.loadFromFile(device, "assets/shaders/mount_dust.frag.spv")) {
|
|
LOG_ERROR("Failed to load mount_dust fragment shader");
|
|
return false;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
// No push constants needed for mount dust (all data is per-vertex)
|
|
pipelineLayout = createPipelineLayout(device, {perFrameLayout}, {});
|
|
if (pipelineLayout == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create mount dust pipeline layout");
|
|
return false;
|
|
}
|
|
|
|
// Vertex input: pos(vec3) + size(float) + alpha(float) = 5 floats, stride = 20 bytes
|
|
VkVertexInputBindingDescription binding{};
|
|
binding.binding = 0;
|
|
binding.stride = 5 * sizeof(float);
|
|
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
|
|
|
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
|
attrs[0].location = 0;
|
|
attrs[0].binding = 0;
|
|
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
attrs[0].offset = 0;
|
|
attrs[1].location = 1;
|
|
attrs[1].binding = 0;
|
|
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
|
attrs[1].offset = 3 * sizeof(float);
|
|
attrs[2].location = 2;
|
|
attrs[2].binding = 0;
|
|
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
|
attrs[2].offset = 4 * sizeof(float);
|
|
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = 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(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device);
|
|
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create mount dust pipeline");
|
|
return false;
|
|
}
|
|
|
|
// Create dynamic mapped vertex buffer
|
|
dynamicVBSize = MAX_DUST_PARTICLES * 5 * sizeof(float);
|
|
AllocatedBuffer buf = createBuffer(vkCtx->getAllocator(), dynamicVBSize,
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
dynamicVB = buf.buffer;
|
|
dynamicVBAlloc = buf.allocation;
|
|
dynamicVBAllocInfo = buf.info;
|
|
|
|
if (dynamicVB == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create mount dust dynamic vertex buffer");
|
|
return false;
|
|
}
|
|
|
|
particles.reserve(MAX_DUST_PARTICLES);
|
|
vertexData.reserve(MAX_DUST_PARTICLES * 5);
|
|
|
|
LOG_INFO("Mount dust effects initialized");
|
|
return true;
|
|
}
|
|
|
|
void MountDust::shutdown() {
|
|
if (vkCtx) {
|
|
VkDevice device = vkCtx->getDevice();
|
|
VmaAllocator allocator = vkCtx->getAllocator();
|
|
|
|
if (pipeline != VK_NULL_HANDLE) {
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
pipeline = VK_NULL_HANDLE;
|
|
}
|
|
if (pipelineLayout != VK_NULL_HANDLE) {
|
|
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
|
pipelineLayout = VK_NULL_HANDLE;
|
|
}
|
|
if (dynamicVB != VK_NULL_HANDLE) {
|
|
vmaDestroyBuffer(allocator, dynamicVB, dynamicVBAlloc);
|
|
dynamicVB = VK_NULL_HANDLE;
|
|
dynamicVBAlloc = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
vkCtx = nullptr;
|
|
particles.clear();
|
|
}
|
|
|
|
void MountDust::recreatePipelines() {
|
|
if (!vkCtx) return;
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
// Destroy old pipeline (NOT layout)
|
|
if (pipeline != VK_NULL_HANDLE) {
|
|
vkDestroyPipeline(device, pipeline, nullptr);
|
|
pipeline = VK_NULL_HANDLE;
|
|
}
|
|
|
|
VkShaderModule vertModule;
|
|
vertModule.loadFromFile(device, "assets/shaders/mount_dust.vert.spv");
|
|
VkShaderModule fragModule;
|
|
fragModule.loadFromFile(device, "assets/shaders/mount_dust.frag.spv");
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
VkVertexInputBindingDescription binding{};
|
|
binding.binding = 0;
|
|
binding.stride = 5 * sizeof(float);
|
|
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
|
|
|
std::vector<VkVertexInputAttributeDescription> attrs(3);
|
|
attrs[0].location = 0;
|
|
attrs[0].binding = 0;
|
|
attrs[0].format = VK_FORMAT_R32G32B32_SFLOAT;
|
|
attrs[0].offset = 0;
|
|
attrs[1].location = 1;
|
|
attrs[1].binding = 0;
|
|
attrs[1].format = VK_FORMAT_R32_SFLOAT;
|
|
attrs[1].offset = 3 * sizeof(float);
|
|
attrs[2].location = 2;
|
|
attrs[2].binding = 0;
|
|
attrs[2].format = VK_FORMAT_R32_SFLOAT;
|
|
attrs[2].offset = 4 * sizeof(float);
|
|
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = 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(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device);
|
|
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
}
|
|
|
|
void MountDust::spawnDust(const glm::vec3& position, const glm::vec3& velocity, bool isMoving) {
|
|
if (!isMoving) {
|
|
spawnAccum = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// Spawn rate based on speed
|
|
float speed = glm::length(velocity);
|
|
if (speed < 0.1f) return;
|
|
|
|
// Spawn dust particles at a rate proportional to speed
|
|
float spawnRate = speed * 8.0f; // More dust at higher speeds
|
|
spawnAccum += spawnRate * 0.016f; // Assume ~60 FPS
|
|
|
|
while (spawnAccum >= 1.0f && particles.size() < MAX_DUST_PARTICLES) {
|
|
spawnAccum -= 1.0f;
|
|
|
|
Particle p;
|
|
// Spawn slightly behind and to the sides of the mount, at ground level
|
|
p.position = position + glm::vec3(
|
|
randFloat(-0.3f, 0.3f),
|
|
randFloat(-0.3f, 0.3f),
|
|
0.2f // Spawn slightly above ground to ensure visibility
|
|
);
|
|
|
|
// Dust rises up and spreads outward
|
|
// Only use horizontal velocity for drift, ignore vertical component
|
|
glm::vec3 horizontalVel = glm::vec3(velocity.x, velocity.y, 0.0f);
|
|
p.velocity = glm::vec3(
|
|
randFloat(-0.3f, 0.3f), // Random horizontal spread
|
|
randFloat(-0.3f, 0.3f),
|
|
randFloat(1.2f, 2.5f) // Strong upward movement for visibility
|
|
) - horizontalVel * 0.15f; // Drift backward slightly based on horizontal movement only
|
|
|
|
p.lifetime = 0.0f;
|
|
p.maxLifetime = randFloat(0.4f, 0.8f);
|
|
p.size = randFloat(8.0f, 16.0f);
|
|
p.alpha = 1.0f;
|
|
|
|
particles.push_back(p);
|
|
}
|
|
}
|
|
|
|
void MountDust::update(float deltaTime) {
|
|
// Update existing particles
|
|
for (auto it = particles.begin(); it != particles.end(); ) {
|
|
it->lifetime += deltaTime;
|
|
|
|
if (it->lifetime >= it->maxLifetime) {
|
|
it = particles.erase(it);
|
|
continue;
|
|
}
|
|
|
|
// Update position
|
|
it->position += it->velocity * deltaTime;
|
|
|
|
// Slow down velocity (friction)
|
|
it->velocity *= 0.96f;
|
|
|
|
// Fade out
|
|
float t = it->lifetime / it->maxLifetime;
|
|
it->alpha = 1.0f - t;
|
|
|
|
// Grow slightly as they fade
|
|
it->size += deltaTime * 12.0f;
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
void MountDust::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
|
if (particles.empty() || pipeline == VK_NULL_HANDLE) return;
|
|
|
|
// Build vertex data
|
|
vertexData.clear();
|
|
for (const auto& p : particles) {
|
|
vertexData.push_back(p.position.x);
|
|
vertexData.push_back(p.position.y);
|
|
vertexData.push_back(p.position.z);
|
|
vertexData.push_back(p.size);
|
|
vertexData.push_back(p.alpha);
|
|
}
|
|
|
|
// Upload to mapped buffer
|
|
VkDeviceSize uploadSize = vertexData.size() * sizeof(float);
|
|
if (uploadSize > 0 && dynamicVBAllocInfo.pMappedData) {
|
|
std::memcpy(dynamicVBAllocInfo.pMappedData, vertexData.data(), uploadSize);
|
|
}
|
|
|
|
// Bind pipeline
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
|
|
|
// Bind per-frame descriptor set (set 0 - camera UBO)
|
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout,
|
|
0, 1, &perFrameSet, 0, nullptr);
|
|
|
|
// Bind vertex buffer
|
|
VkDeviceSize offset = 0;
|
|
vkCmdBindVertexBuffers(cmd, 0, 1, &dynamicVB, &offset);
|
|
|
|
// Draw particles as points
|
|
vkCmdDraw(cmd, static_cast<uint32_t>(particles.size()), 1, 0, 0);
|
|
}
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|