2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/starfield.hpp"
|
|
|
|
|
#include "rendering/shader.hpp"
|
|
|
|
|
#include "rendering/camera.hpp"
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
#include <GL/glew.h>
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <random>
|
|
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace rendering {
|
|
|
|
|
|
|
|
|
|
StarField::StarField() = default;
|
|
|
|
|
|
|
|
|
|
StarField::~StarField() {
|
|
|
|
|
shutdown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool StarField::initialize() {
|
|
|
|
|
LOG_INFO("Initializing star field");
|
|
|
|
|
|
|
|
|
|
// Create star shader
|
|
|
|
|
starShader = std::make_unique<Shader>();
|
|
|
|
|
|
|
|
|
|
// Vertex shader - simple point rendering
|
|
|
|
|
const char* vertexShaderSource = R"(
|
|
|
|
|
#version 330 core
|
|
|
|
|
layout (location = 0) in vec3 aPos;
|
|
|
|
|
layout (location = 1) in float aBrightness;
|
|
|
|
|
layout (location = 2) in float aTwinklePhase;
|
|
|
|
|
|
|
|
|
|
uniform mat4 view;
|
|
|
|
|
uniform mat4 projection;
|
|
|
|
|
uniform float time;
|
|
|
|
|
uniform float intensity;
|
|
|
|
|
|
|
|
|
|
out float Brightness;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
// Remove translation from view matrix (stars are infinitely far)
|
|
|
|
|
mat4 viewNoTranslation = mat4(mat3(view));
|
|
|
|
|
|
|
|
|
|
gl_Position = projection * viewNoTranslation * vec4(aPos, 1.0);
|
|
|
|
|
|
|
|
|
|
// Twinkle effect (subtle brightness variation)
|
|
|
|
|
float twinkle = sin(time * 2.0 + aTwinklePhase) * 0.2 + 0.8; // 0.6 to 1.0
|
|
|
|
|
|
|
|
|
|
Brightness = aBrightness * twinkle * intensity;
|
|
|
|
|
|
|
|
|
|
// Point size based on brightness
|
|
|
|
|
gl_PointSize = 2.0 + aBrightness * 2.0; // 2-4 pixels
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
// Fragment shader - star color
|
|
|
|
|
const char* fragmentShaderSource = R"(
|
|
|
|
|
#version 330 core
|
|
|
|
|
in float Brightness;
|
|
|
|
|
|
|
|
|
|
out vec4 FragColor;
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
// Circular point (not square)
|
|
|
|
|
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
|
|
|
float dist = length(coord);
|
|
|
|
|
if (dist > 0.5) {
|
|
|
|
|
discard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Soften edges
|
|
|
|
|
float alpha = smoothstep(0.5, 0.3, dist);
|
|
|
|
|
|
|
|
|
|
// Star color (slightly blue-white)
|
|
|
|
|
vec3 starColor = vec3(0.9, 0.95, 1.0);
|
|
|
|
|
|
|
|
|
|
FragColor = vec4(starColor * Brightness, alpha * Brightness);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
if (!starShader->loadFromSource(vertexShaderSource, fragmentShaderSource)) {
|
|
|
|
|
LOG_ERROR("Failed to create star shader");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate random stars
|
|
|
|
|
generateStars();
|
|
|
|
|
|
|
|
|
|
// Create OpenGL buffers
|
|
|
|
|
createStarBuffers();
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Star field initialized: ", starCount, " stars");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StarField::shutdown() {
|
|
|
|
|
destroyStarBuffers();
|
|
|
|
|
starShader.reset();
|
|
|
|
|
stars.clear();
|
|
|
|
|
}
|
|
|
|
|
|
Implement WoW-accurate DBC-driven sky system with lore-faithful celestial bodies
Add SkySystem coordinator that follows WoW's actual architecture where skyboxes
are authoritative and procedural elements serve as fallbacks. Integrate lighting
system across all renderers (terrain, WMO, M2, character) with unified parameters.
Sky System:
- SkySystem coordinator manages skybox, celestial bodies, stars, clouds, lens flare
- Skybox is authoritative (baked stars from M2 models, procedural fallback only)
- skyboxHasStars flag gates procedural star rendering (prevents double-star bug)
Celestial Bodies (Lore-Accurate):
- Two moons: White Lady (30-day cycle, pale white) + Blue Child (27-day cycle, pale blue)
- Deterministic moon phases from server gameTime (not deltaTime toys)
- Sun positioning driven by LightingManager directionalDir (DBC-sourced)
- Camera-locked sky dome (translation ignored, rotation applied)
Lighting Integration:
- Apply LightingManager params to WMO, M2, character renderers
- Unified lighting: directional light, diffuse color, ambient color, fog
- Star occlusion by cloud density (70% weight) and fog density (30% weight)
Documentation:
- Add comprehensive SKY_SYSTEM.md technical guide
- Update MEMORY.md with sky system architecture and anti-patterns
- Update README.md with WoW-accurate descriptions
Critical design decisions:
- NO latitude-based star rotation (Azeroth not modeled as spherical planet)
- NO always-on procedural stars (skybox authority prevents zone identity loss)
- NO universal dual-moon setup (map-specific celestial configurations)
2026-02-10 14:36:17 -08:00
|
|
|
void StarField::render(const Camera& camera, float timeOfDay,
|
|
|
|
|
float cloudDensity, float fogDensity) {
|
2026-02-02 12:24:50 -08:00
|
|
|
if (!renderingEnabled || vao == 0 || !starShader || stars.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get star intensity based on time of day
|
|
|
|
|
float intensity = getStarIntensity(timeOfDay);
|
|
|
|
|
|
Implement WoW-accurate DBC-driven sky system with lore-faithful celestial bodies
Add SkySystem coordinator that follows WoW's actual architecture where skyboxes
are authoritative and procedural elements serve as fallbacks. Integrate lighting
system across all renderers (terrain, WMO, M2, character) with unified parameters.
Sky System:
- SkySystem coordinator manages skybox, celestial bodies, stars, clouds, lens flare
- Skybox is authoritative (baked stars from M2 models, procedural fallback only)
- skyboxHasStars flag gates procedural star rendering (prevents double-star bug)
Celestial Bodies (Lore-Accurate):
- Two moons: White Lady (30-day cycle, pale white) + Blue Child (27-day cycle, pale blue)
- Deterministic moon phases from server gameTime (not deltaTime toys)
- Sun positioning driven by LightingManager directionalDir (DBC-sourced)
- Camera-locked sky dome (translation ignored, rotation applied)
Lighting Integration:
- Apply LightingManager params to WMO, M2, character renderers
- Unified lighting: directional light, diffuse color, ambient color, fog
- Star occlusion by cloud density (70% weight) and fog density (30% weight)
Documentation:
- Add comprehensive SKY_SYSTEM.md technical guide
- Update MEMORY.md with sky system architecture and anti-patterns
- Update README.md with WoW-accurate descriptions
Critical design decisions:
- NO latitude-based star rotation (Azeroth not modeled as spherical planet)
- NO always-on procedural stars (skybox authority prevents zone identity loss)
- NO universal dual-moon setup (map-specific celestial configurations)
2026-02-10 14:36:17 -08:00
|
|
|
// Reduce intensity based on cloud density and fog (more clouds/fog = fewer visible stars)
|
|
|
|
|
intensity *= (1.0f - glm::clamp(cloudDensity * 0.7f, 0.0f, 1.0f));
|
|
|
|
|
intensity *= (1.0f - glm::clamp(fogDensity * 0.3f, 0.0f, 1.0f));
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Don't render if stars would be invisible
|
|
|
|
|
if (intensity <= 0.01f) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enable blending for star glow
|
|
|
|
|
glEnable(GL_BLEND);
|
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
|
|
|
|
|
|
// Enable point sprites
|
|
|
|
|
glEnable(GL_PROGRAM_POINT_SIZE);
|
|
|
|
|
|
|
|
|
|
// Disable depth writing (stars are background)
|
|
|
|
|
glDepthMask(GL_FALSE);
|
|
|
|
|
|
|
|
|
|
starShader->use();
|
|
|
|
|
|
|
|
|
|
// Set uniforms
|
|
|
|
|
glm::mat4 view = camera.getViewMatrix();
|
|
|
|
|
glm::mat4 projection = camera.getProjectionMatrix();
|
|
|
|
|
|
|
|
|
|
starShader->setUniform("view", view);
|
|
|
|
|
starShader->setUniform("projection", projection);
|
|
|
|
|
starShader->setUniform("time", twinkleTime);
|
|
|
|
|
starShader->setUniform("intensity", intensity);
|
|
|
|
|
|
|
|
|
|
// Render stars as points
|
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
|
glDrawArrays(GL_POINTS, 0, starCount);
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
|
|
|
|
|
|
// Restore state
|
|
|
|
|
glDepthMask(GL_TRUE);
|
|
|
|
|
glDisable(GL_PROGRAM_POINT_SIZE);
|
|
|
|
|
glDisable(GL_BLEND);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StarField::update(float deltaTime) {
|
|
|
|
|
// Update twinkle animation
|
|
|
|
|
twinkleTime += deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StarField::generateStars() {
|
|
|
|
|
stars.clear();
|
|
|
|
|
stars.reserve(starCount);
|
|
|
|
|
|
|
|
|
|
// Random number generator
|
|
|
|
|
std::random_device rd;
|
|
|
|
|
std::mt19937 gen(rd());
|
|
|
|
|
std::uniform_real_distribution<float> phiDist(0.0f, M_PI / 2.0f); // 0 to 90 degrees (hemisphere)
|
|
|
|
|
std::uniform_real_distribution<float> thetaDist(0.0f, 2.0f * M_PI); // 0 to 360 degrees
|
|
|
|
|
std::uniform_real_distribution<float> brightnessDist(0.3f, 1.0f); // Varying brightness
|
|
|
|
|
std::uniform_real_distribution<float> twinkleDist(0.0f, 2.0f * M_PI); // Random twinkle phase
|
|
|
|
|
|
|
|
|
|
const float radius = 900.0f; // Slightly larger than skybox
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < starCount; i++) {
|
|
|
|
|
Star star;
|
|
|
|
|
|
|
|
|
|
// Spherical coordinates (hemisphere)
|
|
|
|
|
float phi = phiDist(gen); // Elevation angle
|
|
|
|
|
float theta = thetaDist(gen); // Azimuth angle
|
|
|
|
|
|
|
|
|
|
// Convert to Cartesian coordinates
|
|
|
|
|
float x = radius * std::sin(phi) * std::cos(theta);
|
|
|
|
|
float y = radius * std::sin(phi) * std::sin(theta);
|
|
|
|
|
float z = radius * std::cos(phi);
|
|
|
|
|
|
|
|
|
|
star.position = glm::vec3(x, y, z);
|
|
|
|
|
star.brightness = brightnessDist(gen);
|
|
|
|
|
star.twinklePhase = twinkleDist(gen);
|
|
|
|
|
|
|
|
|
|
stars.push_back(star);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_DEBUG("Generated ", stars.size(), " stars");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StarField::createStarBuffers() {
|
|
|
|
|
// Prepare vertex data (position, brightness, twinkle phase)
|
|
|
|
|
std::vector<float> vertexData;
|
|
|
|
|
vertexData.reserve(stars.size() * 5); // 3 pos + 1 brightness + 1 phase
|
|
|
|
|
|
|
|
|
|
for (const auto& star : stars) {
|
|
|
|
|
vertexData.push_back(star.position.x);
|
|
|
|
|
vertexData.push_back(star.position.y);
|
|
|
|
|
vertexData.push_back(star.position.z);
|
|
|
|
|
vertexData.push_back(star.brightness);
|
|
|
|
|
vertexData.push_back(star.twinklePhase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create OpenGL buffers
|
|
|
|
|
glGenVertexArrays(1, &vao);
|
|
|
|
|
glGenBuffers(1, &vbo);
|
|
|
|
|
|
|
|
|
|
glBindVertexArray(vao);
|
|
|
|
|
|
|
|
|
|
// Upload vertex data
|
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
|
|
|
|
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW);
|
|
|
|
|
|
|
|
|
|
// Set vertex attributes
|
|
|
|
|
// Position
|
|
|
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
|
|
|
|
|
glEnableVertexAttribArray(0);
|
|
|
|
|
|
|
|
|
|
// Brightness
|
|
|
|
|
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
|
|
|
|
|
glEnableVertexAttribArray(1);
|
|
|
|
|
|
|
|
|
|
// Twinkle phase
|
|
|
|
|
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(4 * sizeof(float)));
|
|
|
|
|
glEnableVertexAttribArray(2);
|
|
|
|
|
|
|
|
|
|
glBindVertexArray(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StarField::destroyStarBuffers() {
|
|
|
|
|
if (vao != 0) {
|
|
|
|
|
glDeleteVertexArrays(1, &vao);
|
|
|
|
|
vao = 0;
|
|
|
|
|
}
|
|
|
|
|
if (vbo != 0) {
|
|
|
|
|
glDeleteBuffers(1, &vbo);
|
|
|
|
|
vbo = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float StarField::getStarIntensity(float timeOfDay) const {
|
|
|
|
|
// Stars visible at night (fade in/out at dusk/dawn)
|
|
|
|
|
|
|
|
|
|
// Full night: 20:00-4:00
|
|
|
|
|
if (timeOfDay >= 20.0f || timeOfDay < 4.0f) {
|
|
|
|
|
return 1.0f;
|
|
|
|
|
}
|
|
|
|
|
// Fade in at dusk: 18:00-20:00
|
|
|
|
|
else if (timeOfDay >= 18.0f && timeOfDay < 20.0f) {
|
|
|
|
|
return (timeOfDay - 18.0f) / 2.0f; // 0 to 1 over 2 hours
|
|
|
|
|
}
|
|
|
|
|
// Fade out at dawn: 4:00-6:00
|
|
|
|
|
else if (timeOfDay >= 4.0f && timeOfDay < 6.0f) {
|
|
|
|
|
return 1.0f - (timeOfDay - 4.0f) / 2.0f; // 1 to 0 over 2 hours
|
|
|
|
|
}
|
|
|
|
|
// Daytime: no stars
|
|
|
|
|
else {
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace rendering
|
|
|
|
|
} // namespace wowee
|