mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
- Skybox now uses DBC sky colors (skyTop/skyMiddle/skyBand1/skyBand2) instead of hardcoded C++ color curves, with 3-band gradient and Rayleigh/Mie scattering - Clouds receive sun direction for lit edges, self-shadowing, and silver lining - Fixed sun quad box artifact with proper edge fade in celestial shader - Lens flare attenuated by fog, cloud density, and weather intensity - Replaced garish green/purple lens flare ghosts with warm natural palette - Added zone-based weather system for single-player mode with per-zone rain/snow configuration, probability-based activation, and smooth intensity transitions - Server SMSG_WEATHER remains authoritative when connected to a server
213 lines
7 KiB
C++
213 lines
7 KiB
C++
#include "rendering/skybox.hpp"
|
|
#include "rendering/sky_system.hpp"
|
|
#include "rendering/vk_context.hpp"
|
|
#include "rendering/vk_shader.hpp"
|
|
#include "rendering/vk_pipeline.hpp"
|
|
#include "rendering/vk_frame_data.hpp"
|
|
#include "core/logger.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <cmath>
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
// Push constant struct — must match skybox.frag.glsl layout
|
|
struct SkyPushConstants {
|
|
glm::vec4 zenithColor; // DBC skyTopColor
|
|
glm::vec4 midColor; // DBC skyMiddleColor
|
|
glm::vec4 horizonColor; // DBC skyBand1Color
|
|
glm::vec4 fogColor; // DBC skyBand2Color / fogColor blend
|
|
glm::vec4 sunDirAndTime; // xyz = sun direction, w = timeOfDay
|
|
};
|
|
static_assert(sizeof(SkyPushConstants) == 80, "SkyPushConstants size mismatch");
|
|
|
|
Skybox::Skybox() = default;
|
|
|
|
Skybox::~Skybox() {
|
|
shutdown();
|
|
}
|
|
|
|
bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) {
|
|
LOG_INFO("Initializing skybox");
|
|
|
|
vkCtx = ctx;
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
// Load SPIR-V shaders
|
|
VkShaderModule vertModule;
|
|
if (!vertModule.loadFromFile(device, "assets/shaders/skybox.vert.spv")) {
|
|
LOG_ERROR("Failed to load skybox vertex shader");
|
|
return false;
|
|
}
|
|
|
|
VkShaderModule fragModule;
|
|
if (!fragModule.loadFromFile(device, "assets/shaders/skybox.frag.spv")) {
|
|
LOG_ERROR("Failed to load skybox fragment shader");
|
|
return false;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
// Push constant range: 5 x vec4 = 80 bytes
|
|
VkPushConstantRange pushRange{};
|
|
pushRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
pushRange.offset = 0;
|
|
pushRange.size = sizeof(SkyPushConstants); // 80 bytes
|
|
|
|
// Create pipeline layout with perFrameLayout (set 0) + push constants
|
|
pipelineLayout = createPipelineLayout(device, {perFrameLayout}, {pushRange});
|
|
if (pipelineLayout == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create skybox pipeline layout");
|
|
return false;
|
|
}
|
|
|
|
// Fullscreen triangle — no vertex buffer, no vertex input.
|
|
// Dynamic viewport and scissor
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = PipelineBuilder()
|
|
.setShaders(vertStage, fragStage)
|
|
.setVertexInput({}, {})
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL) // depth test on, write off, LEQUAL for far plane
|
|
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
|
.setLayout(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device);
|
|
|
|
// Shader modules can be freed after pipeline creation
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Failed to create skybox pipeline");
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("Skybox initialized");
|
|
return true;
|
|
}
|
|
|
|
void Skybox::recreatePipelines() {
|
|
if (!vkCtx) return;
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
if (pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, pipeline, nullptr); pipeline = VK_NULL_HANDLE; }
|
|
|
|
VkShaderModule vertModule;
|
|
if (!vertModule.loadFromFile(device, "assets/shaders/skybox.vert.spv")) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to load vertex shader");
|
|
return;
|
|
}
|
|
VkShaderModule fragModule;
|
|
if (!fragModule.loadFromFile(device, "assets/shaders/skybox.frag.spv")) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to load fragment shader");
|
|
vertModule.destroy();
|
|
return;
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo vertStage = vertModule.stageInfo(VK_SHADER_STAGE_VERTEX_BIT);
|
|
VkPipelineShaderStageCreateInfo fragStage = fragModule.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT);
|
|
|
|
std::vector<VkDynamicState> dynamicStates = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR
|
|
};
|
|
|
|
pipeline = PipelineBuilder()
|
|
.setShaders(vertStage, fragStage)
|
|
.setVertexInput({}, {})
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
.setDepthTest(true, false, VK_COMPARE_OP_LESS_OR_EQUAL)
|
|
.setColorBlendAttachment(PipelineBuilder::blendDisabled())
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
|
.setLayout(pipelineLayout)
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
.setDynamicStates(dynamicStates)
|
|
.build(device);
|
|
|
|
vertModule.destroy();
|
|
fragModule.destroy();
|
|
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
LOG_ERROR("Skybox::recreatePipelines: failed to create pipeline");
|
|
}
|
|
}
|
|
|
|
void Skybox::shutdown() {
|
|
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;
|
|
}
|
|
|
|
void Skybox::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const SkyParams& params) {
|
|
if (pipeline == VK_NULL_HANDLE || !renderingEnabled) {
|
|
return;
|
|
}
|
|
|
|
// Compute sun direction from directionalDir (light points toward scene, sun is opposite)
|
|
glm::vec3 sunDir = -glm::normalize(params.directionalDir);
|
|
|
|
SkyPushConstants push{};
|
|
push.zenithColor = glm::vec4(params.skyTopColor, 1.0f);
|
|
push.midColor = glm::vec4(params.skyMiddleColor, 1.0f);
|
|
push.horizonColor = glm::vec4(params.skyBand1Color, 1.0f);
|
|
push.fogColor = glm::vec4(params.skyBand2Color, 1.0f);
|
|
push.sunDirAndTime = glm::vec4(sunDir, params.timeOfDay);
|
|
|
|
// 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);
|
|
|
|
// Push constants
|
|
vkCmdPushConstants(cmd, pipelineLayout,
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
0, sizeof(push), &push);
|
|
|
|
// Draw fullscreen triangle — no vertex buffer needed
|
|
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
}
|
|
|
|
void Skybox::update(float deltaTime) {
|
|
if (timeProgressionEnabled) {
|
|
timeOfDay += deltaTime * timeSpeed;
|
|
|
|
// Wrap around 24 hours
|
|
if (timeOfDay >= 24.0f) {
|
|
timeOfDay -= 24.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Skybox::setTimeOfDay(float time) {
|
|
// Clamp to 0-24 range
|
|
while (time < 0.0f) time += 24.0f;
|
|
while (time >= 24.0f) time -= 24.0f;
|
|
|
|
timeOfDay = time;
|
|
}
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|