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)
This commit is contained in:
Kelsi 2026-02-10 14:36:17 -08:00
parent 159a434c60
commit 8e60d0e781
16 changed files with 1036 additions and 47 deletions

View file

@ -27,8 +27,14 @@ public:
* Render celestial bodies (sun and moon)
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
* @param sunDir Optional sun direction from lighting system (normalized)
* @param sunColor Optional sun color from lighting system
* @param gameTime Optional server game time in seconds (for deterministic moon phases)
*/
void render(const Camera& camera, float timeOfDay);
void render(const Camera& camera, float timeOfDay,
const glm::vec3* sunDir = nullptr,
const glm::vec3* sunColor = nullptr,
float gameTime = -1.0f);
/**
* Enable/disable celestial rendering
@ -42,10 +48,16 @@ public:
void update(float deltaTime);
/**
* Set moon phase (0.0 = new moon, 0.25 = first quarter, 0.5 = full, 0.75 = last quarter, 1.0 = new)
* Set White Lady phase (primary moon, 0.0 = new, 0.5 = full, 1.0 = new)
*/
void setMoonPhase(float phase);
float getMoonPhase() const { return moonPhase; }
float getMoonPhase() const { return whiteLadyPhase_; }
/**
* Set Blue Child phase (secondary moon, 0.0 = new, 0.5 = full, 1.0 = new)
*/
void setBlueChildPhase(float phase);
float getBlueChildPhase() const { return blueChildPhase_; }
/**
* Enable/disable automatic moon phase cycling
@ -53,6 +65,12 @@ public:
void setMoonPhaseCycling(bool enabled) { moonPhaseCycling = enabled; }
bool isMoonPhaseCycling() const { return moonPhaseCycling; }
/**
* Enable/disable two-moon rendering (White Lady + Blue Child)
*/
void setDualMoonMode(bool enabled) { dualMoonMode_ = enabled; }
bool isDualMoonMode() const { return dualMoonMode_; }
/**
* Get sun position in world space
*/
@ -77,11 +95,27 @@ private:
void createCelestialQuad();
void destroyCelestialQuad();
void renderSun(const Camera& camera, float timeOfDay);
void renderMoon(const Camera& camera, float timeOfDay);
void renderSun(const Camera& camera, float timeOfDay,
const glm::vec3* sunDir = nullptr,
const glm::vec3* sunColor = nullptr);
void renderMoon(const Camera& camera, float timeOfDay); // White Lady (primary)
void renderBlueChild(const Camera& camera, float timeOfDay); // Blue Child (secondary)
float calculateCelestialAngle(float timeOfDay, float riseTime, float setTime) const;
/**
* Compute moon phase from game time (deterministic)
* @param gameTime Server game time in seconds
* @param cycleDays Lunar cycle length in game days
* @return Phase 0.0-1.0 (0=new, 0.5=full, 1.0=new)
*/
float computePhaseFromGameTime(float gameTime, float cycleDays) const;
/**
* Update moon phases from game time (server-driven)
*/
void updatePhasesFromGameTime(float gameTime);
std::unique_ptr<Shader> celestialShader;
uint32_t vao = 0;
@ -90,11 +124,18 @@ private:
bool renderingEnabled = true;
// Moon phase system
float moonPhase = 0.5f; // 0.0-1.0 (0=new, 0.5=full)
// Moon phase system (two moons in Azeroth lore)
float whiteLadyPhase_ = 0.5f; // 0.0-1.0 (0=new, 0.5=full) - primary moon
float blueChildPhase_ = 0.25f; // 0.0-1.0 (0=new, 0.5=full) - secondary moon
bool moonPhaseCycling = true;
float moonPhaseTimer = 0.0f;
static constexpr float MOON_CYCLE_DURATION = 240.0f; // 4 minutes for full cycle
float moonPhaseTimer = 0.0f; // Fallback for deltaTime mode (development)
bool dualMoonMode_ = true; // Default: render both moons (Azeroth-specific)
// WoW lunar cycle constants (in game days)
// WoW day = 24 real minutes, so these are ~realistic game-world cycles
static constexpr float WHITE_LADY_CYCLE_DAYS = 30.0f; // ~12 real hours for full cycle
static constexpr float BLUE_CHILD_CYCLE_DAYS = 27.0f; // ~10.8 real hours (slightly faster)
static constexpr float MOON_CYCLE_DURATION = 240.0f; // Fallback: 4 minutes (deltaTime mode)
};
} // namespace rendering

View file

@ -88,6 +88,13 @@ public:
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDirIn[3], const float lightColorIn[3],
const float ambientColorIn[3]) {
lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]);
lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]);
ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]);
}
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
@ -206,6 +213,11 @@ private:
float fogStart = 400.0f;
float fogEnd = 1200.0f;
// Lighting parameters
glm::vec3 lightDir = glm::vec3(0.0f, -1.0f, 0.3f);
glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
// Shadow mapping
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);

View file

@ -307,6 +307,13 @@ public:
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDirIn[3], const float lightColorIn[3],
const float ambientColorIn[3]) {
lightDir = glm::vec3(lightDirIn[0], lightDirIn[1], lightDirIn[2]);
lightColor = glm::vec3(lightColorIn[0], lightColorIn[1], lightColorIn[2]);
ambientColor = glm::vec3(ambientColorIn[0], ambientColorIn[1], ambientColorIn[2]);
}
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
@ -334,6 +341,7 @@ private:
// Lighting uniforms
glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f);
glm::vec3 lightColor = glm::vec3(1.5f, 1.4f, 1.3f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
// Fog parameters

View file

@ -0,0 +1,131 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Camera;
class Skybox;
class Celestial;
class StarField;
class Clouds;
class LensFlare;
class LightingManager;
/**
* Sky rendering parameters (extracted from LightingManager)
*/
struct SkyParams {
// Sun/moon positioning
glm::vec3 directionalDir{0.0f, -1.0f, 0.3f};
glm::vec3 sunColor{1.0f, 1.0f, 0.9f};
// Sky colors (for skybox tinting/blending)
glm::vec3 skyTopColor{0.5f, 0.7f, 1.0f};
glm::vec3 skyMiddleColor{0.7f, 0.85f, 1.0f};
glm::vec3 skyBand1Color{0.9f, 0.95f, 1.0f};
glm::vec3 skyBand2Color{1.0f, 0.98f, 0.9f};
// Atmospheric effects
float cloudDensity = 0.0f; // 0-1
float fogDensity = 0.0f; // 0-1
float horizonGlow = 0.3f; // 0-1
// Time
float timeOfDay = 12.0f; // 0-24 hours
float gameTime = -1.0f; // Server game time in seconds (-1 = use fallback)
// Skybox selection (future: from LightSkybox.dbc)
uint32_t skyboxModelId = 0;
bool skyboxHasStars = false; // Does loaded skybox include baked stars?
};
/**
* Unified sky rendering system
*
* Coordinates skybox (authoritative), celestial bodies (sun + 2 moons),
* and fallback procedural stars. Driven by lighting system data.
*
* Architecture:
* - Skybox is PRIMARY (includes baked stars from M2 models)
* - Celestial renders sun + White Lady + Blue Child
* - StarField is DEBUG/FALLBACK only (disabled when skybox has stars)
*/
class SkySystem {
public:
SkySystem();
~SkySystem();
/**
* Initialize sky system components
*/
bool initialize();
void shutdown();
/**
* Update sky system (time, moon phases, etc.)
*/
void update(float deltaTime);
/**
* Render complete sky
* @param camera Camera for view/projection
* @param params Sky parameters from lighting system
*/
void render(const Camera& camera, const SkyParams& params);
/**
* Enable/disable procedural stars (DEBUG/FALLBACK)
* Default: OFF (stars come from skybox)
*/
void setProceduralStarsEnabled(bool enabled) { proceduralStarsEnabled_ = enabled; }
bool isProceduralStarsEnabled() const { return proceduralStarsEnabled_; }
/**
* Enable/disable debug sky mode (forces procedural stars even with skybox)
*/
void setDebugSkyMode(bool enabled) { debugSkyMode_ = enabled; }
bool isDebugSkyMode() const { return debugSkyMode_; }
/**
* Get sun position in world space (for lens flare, shadows, etc.)
*/
glm::vec3 getSunPosition(const SkyParams& params) const;
/**
* Enable/disable moon phase cycling
*/
void setMoonPhaseCycling(bool enabled);
/**
* Set moon phases manually (0.0-1.0 each)
*/
void setWhiteLadyPhase(float phase);
void setBlueChildPhase(float phase);
float getWhiteLadyPhase() const;
float getBlueChildPhase() const;
// Component accessors (for direct control if needed)
Skybox* getSkybox() const { return skybox_.get(); }
Celestial* getCelestial() const { return celestial_.get(); }
StarField* getStarField() const { return starField_.get(); }
Clouds* getClouds() const { return clouds_.get(); }
LensFlare* getLensFlare() const { return lensFlare_.get(); }
private:
std::unique_ptr<Skybox> skybox_; // Authoritative sky (gradient now, M2 models later)
std::unique_ptr<Celestial> celestial_; // Sun + 2 moons
std::unique_ptr<StarField> starField_; // Fallback procedural stars
std::unique_ptr<Clouds> clouds_; // Cloud layer
std::unique_ptr<LensFlare> lensFlare_; // Sun lens flare
bool proceduralStarsEnabled_ = false; // Default: OFF (skybox is authoritative)
bool debugSkyMode_ = false; // Force procedural stars for debugging
bool initialized_ = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -28,8 +28,11 @@ public:
* Render the star field
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
* @param cloudDensity Optional cloud density from lighting (0-1, reduces star visibility)
* @param fogDensity Optional fog density from lighting (reduces star visibility)
*/
void render(const Camera& camera, float timeOfDay);
void render(const Camera& camera, float timeOfDay,
float cloudDensity = 0.0f, float fogDensity = 0.0f);
/**
* Update star twinkle animation

View file

@ -179,6 +179,9 @@ public:
fogColor = color; fogStart = start; fogEnd = end;
}
void setLighting(const float lightDir[3], const float lightColor[3],
const float ambientColor[3]);
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
}
@ -537,6 +540,11 @@ private:
float fogStart = 3000.0f; // Increased to allow clearer visibility at distance
float fogEnd = 4000.0f; // Increased to match extended view distance
// Lighting parameters
float lightDir[3] = {-0.3f, -0.7f, -0.6f};
float lightColor[3] = {1.5f, 1.4f, 1.3f};
float ambientColor[3] = {0.55f, 0.55f, 0.6f};
// Shadow mapping
GLuint shadowDepthTex = 0;
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);