Kelsidavis-WoWee/src/rendering/clouds.cpp

282 lines
9.4 KiB
C++
Raw Normal View History

#include "rendering/clouds.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 <cmath>
namespace wowee {
namespace rendering {
Clouds::Clouds() = default;
Clouds::~Clouds() {
shutdown();
}
bool Clouds::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
LOG_INFO("Initializing cloud system (Vulkan)");
vkCtx_ = ctx;
VkDevice device = vkCtx_->getDevice();
// ------------------------------------------------------------------ shaders
VkShaderModule vertModule;
if (!vertModule.loadFromFile(device, "assets/shaders/clouds.vert.spv")) {
LOG_ERROR("Failed to load clouds vertex shader");
return false;
}
VkShaderModule fragModule;
if (!fragModule.loadFromFile(device, "assets/shaders/clouds.frag.spv")) {
LOG_ERROR("Failed to load clouds fragment shader");
return false;
}
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
// ------------------------------------------------------------------ push constants
// Fragment-only push: vec4 cloudColor + float density + float windOffset = 24 bytes
VkPushConstantRange pushRange{};
pushRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
pushRange.offset = 0;
pushRange.size = sizeof(CloudPush); // 24 bytes
// ------------------------------------------------------------------ pipeline layout
pipelineLayout_ = createPipelineLayout(device, {perFrameLayout}, {pushRange});
if (pipelineLayout_ == VK_NULL_HANDLE) {
LOG_ERROR("Failed to create clouds pipeline layout");
return false;
}
// ------------------------------------------------------------------ vertex input
// Vertex: vec3 pos only, stride = 12 bytes
VkVertexInputBindingDescription binding{};
binding.binding = 0;
binding.stride = sizeof(glm::vec3); // 12 bytes
binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription posAttr{};
posAttr.location = 0;
posAttr.binding = 0;
posAttr.format = VK_FORMAT_R32G32B32_SFLOAT;
posAttr.offset = 0;
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
// ------------------------------------------------------------------ pipeline
pipeline_ = PipelineBuilder()
.setShaders(vertStage, fragStage)
.setVertexInput({binding}, {posAttr})
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // test on, write off (sky layer)
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
.setLayout(pipelineLayout_)
.setRenderPass(vkCtx_->getImGuiRenderPass())
.setDynamicStates(dynamicStates)
.build(device);
vertModule.destroy();
fragModule.destroy();
if (pipeline_ == VK_NULL_HANDLE) {
LOG_ERROR("Failed to create clouds pipeline");
return false;
}
// ------------------------------------------------------------------ geometry
generateMesh();
createBuffers();
LOG_INFO("Cloud system initialized: ", indexCount_ / 3, " triangles");
return true;
}
void Clouds::shutdown() {
destroyBuffers();
if (vkCtx_) {
VkDevice device = vkCtx_->getDevice();
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;
}
}
vkCtx_ = nullptr;
}
// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------
void Clouds::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float timeOfDay) {
if (!enabled_ || pipeline_ == VK_NULL_HANDLE) {
return;
}
glm::vec3 color = getCloudColor(timeOfDay);
CloudPush push{};
push.cloudColor = glm::vec4(color, 1.0f);
push.density = density_;
push.windOffset = windOffset_;
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
// Bind per-frame UBO (set 0 — vertex shader reads view/projection from here)
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout_,
0, 1, &perFrameSet, 0, nullptr);
// Push cloud params to fragment shader
vkCmdPushConstants(cmd, pipelineLayout_,
VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(push), &push);
VkDeviceSize offset = 0;
vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer_, &offset);
vkCmdBindIndexBuffer(cmd, indexBuffer_, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(cmd, static_cast<uint32_t>(indexCount_), 1, 0, 0, 0);
}
// ---------------------------------------------------------------------------
// Update
// ---------------------------------------------------------------------------
void Clouds::update(float deltaTime) {
if (!enabled_) {
return;
}
windOffset_ += deltaTime * windSpeed_ * 0.05f; // Slow drift
}
// ---------------------------------------------------------------------------
// Cloud colour (unchanged logic from GL version)
// ---------------------------------------------------------------------------
glm::vec3 Clouds::getCloudColor(float timeOfDay) const {
glm::vec3 dayColor(0.95f, 0.95f, 1.0f);
if (timeOfDay >= 5.0f && timeOfDay < 7.0f) {
// Dawn — orange tint fading to day
float t = (timeOfDay - 5.0f) / 2.0f;
return glm::mix(glm::vec3(1.0f, 0.7f, 0.5f), dayColor, t);
} else if (timeOfDay >= 17.0f && timeOfDay < 19.0f) {
// Dusk — day fading to orange/pink
float t = (timeOfDay - 17.0f) / 2.0f;
return glm::mix(dayColor, glm::vec3(1.0f, 0.6f, 0.4f), t);
} else if (timeOfDay >= 20.0f || timeOfDay < 5.0f) {
// Night — dark blue-grey
return glm::vec3(0.15f, 0.15f, 0.25f);
}
return dayColor;
}
// ---------------------------------------------------------------------------
// Density setter
// ---------------------------------------------------------------------------
void Clouds::setDensity(float density) {
density_ = glm::clamp(density, 0.0f, 1.0f);
}
// ---------------------------------------------------------------------------
// Mesh generation — identical algorithm to GL version
// ---------------------------------------------------------------------------
void Clouds::generateMesh() {
vertices_.clear();
indices_.clear();
// Upper hemisphere
for (int ring = 0; ring <= RINGS; ++ring) {
float phi = (ring / static_cast<float>(RINGS)) * (static_cast<float>(M_PI) * 0.5f);
float y = RADIUS * std::cos(phi);
float ringRadius = RADIUS * std::sin(phi);
for (int seg = 0; seg <= SEGMENTS; ++seg) {
float theta = (seg / static_cast<float>(SEGMENTS)) * (2.0f * static_cast<float>(M_PI));
float x = ringRadius * std::cos(theta);
float z = ringRadius * std::sin(theta);
vertices_.push_back(glm::vec3(x, y, z));
}
}
for (int ring = 0; ring < RINGS; ++ring) {
for (int seg = 0; seg < SEGMENTS; ++seg) {
uint32_t current = static_cast<uint32_t>(ring * (SEGMENTS + 1) + seg);
uint32_t next = current + static_cast<uint32_t>(SEGMENTS + 1);
indices_.push_back(current);
indices_.push_back(next);
indices_.push_back(current + 1);
indices_.push_back(current + 1);
indices_.push_back(next);
indices_.push_back(next + 1);
}
}
indexCount_ = static_cast<int>(indices_.size());
}
// ---------------------------------------------------------------------------
// GPU buffer management
// ---------------------------------------------------------------------------
void Clouds::createBuffers() {
AllocatedBuffer vbuf = uploadBuffer(*vkCtx_,
vertices_.data(),
vertices_.size() * sizeof(glm::vec3),
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
vertexBuffer_ = vbuf.buffer;
vertexAlloc_ = vbuf.allocation;
AllocatedBuffer ibuf = uploadBuffer(*vkCtx_,
indices_.data(),
indices_.size() * sizeof(uint32_t),
VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
indexBuffer_ = ibuf.buffer;
indexAlloc_ = ibuf.allocation;
// CPU data no longer needed
vertices_.clear();
vertices_.shrink_to_fit();
indices_.clear();
indices_.shrink_to_fit();
}
void Clouds::destroyBuffers() {
if (!vkCtx_) return;
VmaAllocator allocator = vkCtx_->getAllocator();
if (vertexBuffer_ != VK_NULL_HANDLE) {
vmaDestroyBuffer(allocator, vertexBuffer_, vertexAlloc_);
vertexBuffer_ = VK_NULL_HANDLE;
vertexAlloc_ = VK_NULL_HANDLE;
}
if (indexBuffer_ != VK_NULL_HANDLE) {
vmaDestroyBuffer(allocator, indexBuffer_, indexAlloc_);
indexBuffer_ = VK_NULL_HANDLE;
indexAlloc_ = VK_NULL_HANDLE;
}
}
} // namespace rendering
} // namespace wowee