mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
312 lines
8.5 KiB
C++
312 lines
8.5 KiB
C++
#include "rendering/clouds.hpp"
|
|
#include "rendering/camera.hpp"
|
|
#include "rendering/shader.hpp"
|
|
#include "core/logger.hpp"
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#include <cmath>
|
|
|
|
namespace wowee {
|
|
namespace rendering {
|
|
|
|
Clouds::Clouds() {
|
|
}
|
|
|
|
Clouds::~Clouds() {
|
|
cleanup();
|
|
}
|
|
|
|
bool Clouds::initialize() {
|
|
LOG_INFO("Initializing cloud system");
|
|
|
|
// Generate cloud dome mesh
|
|
generateMesh();
|
|
|
|
// Create VAO
|
|
glGenVertexArrays(1, &vao);
|
|
glGenBuffers(1, &vbo);
|
|
glGenBuffers(1, &ebo);
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
// Upload vertex data
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
vertices.size() * sizeof(glm::vec3),
|
|
vertices.data(),
|
|
GL_STATIC_DRAW);
|
|
|
|
// Upload index data
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
indices.size() * sizeof(unsigned int),
|
|
indices.data(),
|
|
GL_STATIC_DRAW);
|
|
|
|
// Position attribute
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), (void*)0);
|
|
glEnableVertexAttribArray(0);
|
|
|
|
glBindVertexArray(0);
|
|
|
|
// Create shader
|
|
shader = std::make_unique<Shader>();
|
|
|
|
// Cloud vertex shader
|
|
const char* vertexShaderSource = R"(
|
|
#version 330 core
|
|
layout (location = 0) in vec3 aPos;
|
|
|
|
uniform mat4 uView;
|
|
uniform mat4 uProjection;
|
|
|
|
out vec3 WorldPos;
|
|
out vec3 LocalPos;
|
|
|
|
void main() {
|
|
LocalPos = aPos;
|
|
WorldPos = aPos;
|
|
|
|
// Remove translation from view matrix (billboard effect)
|
|
mat4 viewNoTranslation = uView;
|
|
viewNoTranslation[3][0] = 0.0;
|
|
viewNoTranslation[3][1] = 0.0;
|
|
viewNoTranslation[3][2] = 0.0;
|
|
|
|
vec4 pos = uProjection * viewNoTranslation * vec4(aPos, 1.0);
|
|
gl_Position = pos.xyww; // Put at far plane
|
|
}
|
|
)";
|
|
|
|
// Cloud fragment shader with procedural noise
|
|
const char* fragmentShaderSource = R"(
|
|
#version 330 core
|
|
in vec3 WorldPos;
|
|
in vec3 LocalPos;
|
|
|
|
uniform vec3 uCloudColor;
|
|
uniform float uDensity;
|
|
uniform float uWindOffset;
|
|
|
|
out vec4 FragColor;
|
|
|
|
// Simple 3D noise function
|
|
float hash(vec3 p) {
|
|
p = fract(p * vec3(0.1031, 0.1030, 0.0973));
|
|
p += dot(p, p.yxz + 19.19);
|
|
return fract((p.x + p.y) * p.z);
|
|
}
|
|
|
|
float noise(vec3 p) {
|
|
vec3 i = floor(p);
|
|
vec3 f = fract(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
|
|
return mix(
|
|
mix(mix(hash(i + vec3(0,0,0)), hash(i + vec3(1,0,0)), f.x),
|
|
mix(hash(i + vec3(0,1,0)), hash(i + vec3(1,1,0)), f.x), f.y),
|
|
mix(mix(hash(i + vec3(0,0,1)), hash(i + vec3(1,0,1)), f.x),
|
|
mix(hash(i + vec3(0,1,1)), hash(i + vec3(1,1,1)), f.x), f.y),
|
|
f.z);
|
|
}
|
|
|
|
// Fractal Brownian Motion for cloud-like patterns
|
|
float fbm(vec3 p) {
|
|
float value = 0.0;
|
|
float amplitude = 0.5;
|
|
float frequency = 1.0;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
value += amplitude * noise(p * frequency);
|
|
frequency *= 2.0;
|
|
amplitude *= 0.5;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void main() {
|
|
// Normalize position for noise sampling
|
|
vec3 pos = normalize(LocalPos);
|
|
|
|
// Only render on upper hemisphere
|
|
if (pos.y < 0.1) {
|
|
discard;
|
|
}
|
|
|
|
// Apply wind offset to x coordinate
|
|
vec3 samplePos = vec3(pos.x + uWindOffset, pos.y, pos.z) * 3.0;
|
|
|
|
// Generate two cloud layers
|
|
float clouds1 = fbm(samplePos * 1.0);
|
|
float clouds2 = fbm(samplePos * 2.0 + vec3(100.0));
|
|
|
|
// Combine layers
|
|
float cloudPattern = clouds1 * 0.6 + clouds2 * 0.4;
|
|
|
|
// Apply density threshold to create cloud shapes
|
|
float cloudMask = smoothstep(0.4 + (1.0 - uDensity) * 0.3, 0.7, cloudPattern);
|
|
|
|
// Add some variation to cloud edges
|
|
float edgeNoise = noise(samplePos * 5.0);
|
|
cloudMask *= smoothstep(0.3, 0.7, edgeNoise);
|
|
|
|
// Fade clouds near horizon
|
|
float horizonFade = smoothstep(0.0, 0.3, pos.y);
|
|
cloudMask *= horizonFade;
|
|
|
|
// Final alpha
|
|
float alpha = cloudMask * 0.85;
|
|
|
|
if (alpha < 0.05) {
|
|
discard;
|
|
}
|
|
|
|
FragColor = vec4(uCloudColor, alpha);
|
|
}
|
|
)";
|
|
|
|
if (!shader->loadFromSource(vertexShaderSource, fragmentShaderSource)) {
|
|
LOG_ERROR("Failed to create cloud shader");
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("Cloud system initialized: ", triangleCount, " triangles");
|
|
return true;
|
|
}
|
|
|
|
void Clouds::generateMesh() {
|
|
vertices.clear();
|
|
indices.clear();
|
|
|
|
// Generate hemisphere mesh for clouds
|
|
for (int ring = 0; ring <= RINGS; ++ring) {
|
|
float phi = (ring / static_cast<float>(RINGS)) * (M_PI * 0.5f); // 0 to π/2
|
|
float y = RADIUS * cos(phi);
|
|
float ringRadius = RADIUS * sin(phi);
|
|
|
|
for (int segment = 0; segment <= SEGMENTS; ++segment) {
|
|
float theta = (segment / static_cast<float>(SEGMENTS)) * (2.0f * M_PI);
|
|
float x = ringRadius * cos(theta);
|
|
float z = ringRadius * sin(theta);
|
|
|
|
vertices.push_back(glm::vec3(x, y, z));
|
|
}
|
|
}
|
|
|
|
// Generate indices
|
|
for (int ring = 0; ring < RINGS; ++ring) {
|
|
for (int segment = 0; segment < SEGMENTS; ++segment) {
|
|
int current = ring * (SEGMENTS + 1) + segment;
|
|
int next = current + SEGMENTS + 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);
|
|
}
|
|
}
|
|
|
|
triangleCount = static_cast<int>(indices.size()) / 3;
|
|
}
|
|
|
|
void Clouds::update(float deltaTime) {
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
|
|
// Accumulate wind movement
|
|
windOffset += deltaTime * windSpeed * 0.05f; // Slow drift
|
|
}
|
|
|
|
glm::vec3 Clouds::getCloudColor(float timeOfDay) const {
|
|
// Base cloud color (white/light gray)
|
|
glm::vec3 dayColor(0.95f, 0.95f, 1.0f);
|
|
|
|
// Dawn clouds (orange tint)
|
|
if (timeOfDay >= 5.0f && timeOfDay < 7.0f) {
|
|
float t = (timeOfDay - 5.0f) / 2.0f;
|
|
glm::vec3 dawnColor(1.0f, 0.7f, 0.5f);
|
|
return glm::mix(dawnColor, dayColor, t);
|
|
}
|
|
// Dusk clouds (orange/pink tint)
|
|
else if (timeOfDay >= 17.0f && timeOfDay < 19.0f) {
|
|
float t = (timeOfDay - 17.0f) / 2.0f;
|
|
glm::vec3 duskColor(1.0f, 0.6f, 0.4f);
|
|
return glm::mix(dayColor, duskColor, t);
|
|
}
|
|
// Night clouds (dark blue-gray)
|
|
else if (timeOfDay >= 20.0f || timeOfDay < 5.0f) {
|
|
return glm::vec3(0.15f, 0.15f, 0.25f);
|
|
}
|
|
|
|
return dayColor;
|
|
}
|
|
|
|
void Clouds::render(const Camera& camera, float timeOfDay) {
|
|
if (!enabled || !shader) {
|
|
return;
|
|
}
|
|
|
|
// Enable blending for transparent clouds
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
// Disable depth write (clouds are in sky)
|
|
glDepthMask(GL_FALSE);
|
|
|
|
// Enable depth test so clouds are behind skybox
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_LEQUAL);
|
|
|
|
shader->use();
|
|
|
|
// Set matrices
|
|
glm::mat4 view = camera.getViewMatrix();
|
|
glm::mat4 projection = camera.getProjectionMatrix();
|
|
|
|
shader->setUniform("uView", view);
|
|
shader->setUniform("uProjection", projection);
|
|
|
|
// Set cloud parameters
|
|
glm::vec3 cloudColor = getCloudColor(timeOfDay);
|
|
shader->setUniform("uCloudColor", cloudColor);
|
|
shader->setUniform("uDensity", density);
|
|
shader->setUniform("uWindOffset", windOffset);
|
|
|
|
// Render
|
|
glBindVertexArray(vao);
|
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indices.size()), GL_UNSIGNED_INT, 0);
|
|
glBindVertexArray(0);
|
|
|
|
// Restore state
|
|
glDisable(GL_BLEND);
|
|
glDepthMask(GL_TRUE);
|
|
glDepthFunc(GL_LESS);
|
|
}
|
|
|
|
void Clouds::setDensity(float density) {
|
|
this->density = glm::clamp(density, 0.0f, 1.0f);
|
|
}
|
|
|
|
void Clouds::cleanup() {
|
|
if (vao) {
|
|
glDeleteVertexArrays(1, &vao);
|
|
vao = 0;
|
|
}
|
|
if (vbo) {
|
|
glDeleteBuffers(1, &vbo);
|
|
vbo = 0;
|
|
}
|
|
if (ebo) {
|
|
glDeleteBuffers(1, &ebo);
|
|
ebo = 0;
|
|
}
|
|
}
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|