diff --git a/assets/shaders/clouds.frag.glsl b/assets/shaders/clouds.frag.glsl index 419e0b7b..fec28507 100644 --- a/assets/shaders/clouds.frag.glsl +++ b/assets/shaders/clouds.frag.glsl @@ -38,10 +38,10 @@ float fbm(vec2 p) { void main() { vec3 dir = normalize(vWorldDir); - float altitude = dir.y; + float altitude = dir.z; // Z is up in the Z-up world coordinate system if (altitude < 0.0) discard; - vec2 uv = dir.xz / (altitude + 0.001); + vec2 uv = dir.xy / (altitude + 0.001); // XY is the horizontal plane uv += push.windOffset; float cloud1 = fbm(uv * 0.8); diff --git a/assets/shaders/clouds.frag.spv b/assets/shaders/clouds.frag.spv index b1118d83..7d09eb46 100644 Binary files a/assets/shaders/clouds.frag.spv and b/assets/shaders/clouds.frag.spv differ diff --git a/assets/shaders/skybox.frag.glsl b/assets/shaders/skybox.frag.glsl index 179dd9c3..b2054e15 100644 --- a/assets/shaders/skybox.frag.glsl +++ b/assets/shaders/skybox.frag.glsl @@ -19,13 +19,30 @@ layout(push_constant) uniform Push { float timeOfDay; } push; -layout(location = 0) in vec3 WorldPos; -layout(location = 1) in float Altitude; +layout(location = 0) in vec2 TexCoord; layout(location = 0) out vec4 outColor; void main() { - float t = clamp(Altitude, 0.0, 1.0); + // Reconstruct world-space ray direction from screen position. + // TexCoord is [0,1]^2; convert to NDC [-1,1]^2. + float ndcX = TexCoord.x * 2.0 - 1.0; + float ndcY = -(TexCoord.y * 2.0 - 1.0); // flip Y: Vulkan NDC Y-down, but projection already flipped + + // Unproject to view space using focal lengths from projection matrix. + // projection[0][0] = 2*near/(right-left) = 1/tan(fovX/2) + // projection[1][1] = 2*near/(top-bottom) (already negated for Vulkan Y-flip) + // We want the original magnitude, so take abs to get the focal length. + vec3 viewDir = vec3(ndcX / projection[0][0], + ndcY / abs(projection[1][1]), + -1.0); + + // Rotate to world space: view = R*T, so R^-1 = R^T = transpose(mat3(view)) + mat3 invViewRot = transpose(mat3(view)); + vec3 worldDir = normalize(invViewRot * viewDir); + + // worldDir.z = sin(elevation); +1 = zenith, 0 = horizon, -1 = nadir + float t = clamp(worldDir.z, 0.0, 1.0); t = pow(t, 1.5); vec3 sky = mix(push.horizonColor.rgb, push.zenithColor.rgb, t); float scatter = max(0.0, 1.0 - t * 2.0) * 0.15; diff --git a/assets/shaders/skybox.frag.spv b/assets/shaders/skybox.frag.spv index 596f72d6..555b6c96 100644 Binary files a/assets/shaders/skybox.frag.spv and b/assets/shaders/skybox.frag.spv differ diff --git a/assets/shaders/skybox.vert.glsl b/assets/shaders/skybox.vert.glsl index 95462764..d1df7d5a 100644 --- a/assets/shaders/skybox.vert.glsl +++ b/assets/shaders/skybox.vert.glsl @@ -1,27 +1,12 @@ #version 450 -layout(set = 0, binding = 0) uniform PerFrame { - mat4 view; - mat4 projection; - mat4 lightSpaceMatrix; - vec4 lightDir; - vec4 lightColor; - vec4 ambientColor; - vec4 viewPos; - vec4 fogColor; - vec4 fogParams; // x=fogStart, y=fogEnd, z=time - vec4 shadowParams; // x=enabled, y=strength -}; +// Fullscreen triangle sky — no vertex buffer, no mesh. +// Draws 3 vertices covering the entire screen, depth forced to 1.0 (far plane). -layout(location = 0) in vec3 aPos; - -layout(location = 0) out vec3 WorldPos; -layout(location = 1) out float Altitude; +layout(location = 0) out vec2 TexCoord; void main() { - WorldPos = aPos; - Altitude = aPos.y; - mat4 rotView = mat4(mat3(view)); // strip translation - vec4 pos = projection * rotView * vec4(aPos, 1.0); - gl_Position = pos.xyww; // force far plane + // Produces triangle covering NDC [-1,1]² with depth = 1.0 (far) + TexCoord = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(TexCoord * 2.0 - 1.0, 1.0, 1.0); } diff --git a/assets/shaders/skybox.vert.spv b/assets/shaders/skybox.vert.spv index 8203b21f..bc7f0006 100644 Binary files a/assets/shaders/skybox.vert.spv and b/assets/shaders/skybox.vert.spv differ diff --git a/include/rendering/skybox.hpp b/include/rendering/skybox.hpp index 42ed67db..23a79db9 100644 --- a/include/rendering/skybox.hpp +++ b/include/rendering/skybox.hpp @@ -13,8 +13,9 @@ class VkContext; /** * Skybox renderer * - * Renders an atmospheric sky dome with gradient colors. - * The sky uses a dome/sphere approach for realistic appearance. + * Renders an atmospheric sky gradient using a fullscreen triangle. + * No vertex buffer: 3 vertices cover the entire screen via gl_VertexIndex. + * World-space ray direction is reconstructed from the inverse view+projection. */ class Skybox { public: @@ -62,9 +63,6 @@ public: glm::vec3 getHorizonColor(float time) const; private: - void createSkyDome(); - void destroySkyDome(); - glm::vec3 getSkyColor(float altitude, float time) const; glm::vec3 getZenithColor(float time) const; @@ -73,13 +71,6 @@ private: VkPipeline pipeline = VK_NULL_HANDLE; VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; - VkBuffer vertexBuffer = VK_NULL_HANDLE; - VmaAllocation vertexAlloc = VK_NULL_HANDLE; - VkBuffer indexBuffer = VK_NULL_HANDLE; - VmaAllocation indexAlloc = VK_NULL_HANDLE; - - int indexCount = 0; - float timeOfDay = 12.0f; // Default: noon float timeSpeed = 1.0f; // 1.0 = 1 hour per real second bool timeProgressionEnabled = false; diff --git a/src/rendering/clouds.cpp b/src/rendering/clouds.cpp index 11193217..9b90d4a0 100644 --- a/src/rendering/clouds.cpp +++ b/src/rendering/clouds.cpp @@ -202,17 +202,17 @@ void Clouds::generateMesh() { vertices_.clear(); indices_.clear(); - // Upper hemisphere + // Upper hemisphere — Z-up world: altitude goes into Z, horizontal spread in X/Y for (int ring = 0; ring <= RINGS; ++ring) { float phi = (ring / static_cast(RINGS)) * (static_cast(M_PI) * 0.5f); - float y = RADIUS * std::cos(phi); + float altZ = RADIUS * std::cos(phi); // altitude → world Z (up) float ringRadius = RADIUS * std::sin(phi); for (int seg = 0; seg <= SEGMENTS; ++seg) { float theta = (seg / static_cast(SEGMENTS)) * (2.0f * static_cast(M_PI)); float x = ringRadius * std::cos(theta); - float z = ringRadius * std::sin(theta); - vertices_.push_back(glm::vec3(x, y, z)); + float y = ringRadius * std::sin(theta); + vertices_.push_back(glm::vec3(x, y, altZ)); } } diff --git a/src/rendering/skybox.cpp b/src/rendering/skybox.cpp index 368c7cbe..a51e2706 100644 --- a/src/rendering/skybox.cpp +++ b/src/rendering/skybox.cpp @@ -3,11 +3,9 @@ #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 #include -#include namespace wowee { namespace rendering { @@ -54,18 +52,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { return false; } - // Vertex input: position only (vec3), stride = 3 * sizeof(float) - VkVertexInputBindingDescription binding{}; - binding.binding = 0; - binding.stride = 3 * sizeof(float); - binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; - - VkVertexInputAttributeDescription posAttr{}; - posAttr.location = 0; - posAttr.binding = 0; - posAttr.format = VK_FORMAT_R32G32B32_SFLOAT; - posAttr.offset = 0; - + // Fullscreen triangle — no vertex buffer, no vertex input. // Dynamic viewport and scissor std::vector dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, @@ -74,7 +61,7 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { pipeline = PipelineBuilder() .setShaders(vertStage, fragStage) - .setVertexInput({binding}, {posAttr}) + .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 @@ -93,16 +80,11 @@ bool Skybox::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLayout) { return false; } - // Create sky dome mesh and upload to GPU - createSkyDome(); - LOG_INFO("Skybox initialized"); return true; } void Skybox::shutdown() { - destroySkyDome(); - if (vkCtx) { VkDevice device = vkCtx->getDevice(); if (pipeline != VK_NULL_HANDLE) { @@ -149,15 +131,8 @@ void Skybox::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, float time VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(push), &push); - // Bind vertex buffer - VkDeviceSize offset = 0; - vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset); - - // Bind index buffer - vkCmdBindIndexBuffer(cmd, indexBuffer, 0, VK_INDEX_TYPE_UINT32); - - // Draw - vkCmdDrawIndexed(cmd, static_cast(indexCount), 1, 0, 0, 0); + // Draw fullscreen triangle — no vertex buffer needed + vkCmdDraw(cmd, 3, 1, 0, 0); } void Skybox::update(float deltaTime) { @@ -179,90 +154,6 @@ void Skybox::setTimeOfDay(float time) { timeOfDay = time; } -void Skybox::createSkyDome() { - // Create an extended dome that goes below horizon for better coverage - const int rings = 16; // Vertical resolution - const int sectors = 32; // Horizontal resolution - const float radius = 2000.0f; // Large enough to cover view without looking curved - - std::vector vertices; - std::vector indices; - - // Generate vertices - extend slightly below horizon - const float minPhi = -M_PI / 12.0f; // Start 15° below horizon - const float maxPhi = M_PI / 2.0f; // End at zenith - for (int ring = 0; ring <= rings; ring++) { - float phi = minPhi + (maxPhi - minPhi) * (static_cast(ring) / rings); - float y = radius * std::sin(phi); - float ringRadius = radius * std::cos(phi); - - for (int sector = 0; sector <= sectors; sector++) { - float theta = (2.0f * M_PI) * (static_cast(sector) / sectors); - float x = ringRadius * std::cos(theta); - float z = ringRadius * std::sin(theta); - - // Position - vertices.push_back(x); - vertices.push_back(z); // Z up in WoW coordinates - vertices.push_back(y); - } - } - - // Generate indices - for (int ring = 0; ring < rings; ring++) { - for (int sector = 0; sector < sectors; sector++) { - int current = ring * (sectors + 1) + sector; - int next = current + sectors + 1; - - // Two triangles per quad - 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(indices.size()); - - // Upload vertex buffer to GPU via staging - AllocatedBuffer vbuf = uploadBuffer(*vkCtx, - vertices.data(), - vertices.size() * sizeof(float), - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); - vertexBuffer = vbuf.buffer; - vertexAlloc = vbuf.allocation; - - // Upload index buffer to GPU via staging - AllocatedBuffer ibuf = uploadBuffer(*vkCtx, - indices.data(), - indices.size() * sizeof(uint32_t), - VK_BUFFER_USAGE_INDEX_BUFFER_BIT); - indexBuffer = ibuf.buffer; - indexAlloc = ibuf.allocation; - - LOG_DEBUG("Sky dome created: ", (rings + 1) * (sectors + 1), " vertices, ", indexCount / 3, " triangles"); -} - -void Skybox::destroySkyDome() { - 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; - } -} - glm::vec3 Skybox::getHorizonColor(float time) const { // Time-based horizon colors // 0-6: Night (dark blue)