diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index e56a6a2c..1b2cdbc0 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -77,6 +77,15 @@ public: size_t getInstanceCount() const { return instances.size(); } + void setFog(const glm::vec3& color, float start, float end) { + fogColor = color; fogStart = start; fogEnd = end; + } + + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { + shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; + } + void clearShadowMap() { shadowEnabled = false; } + private: // GPU representation of M2 model struct M2ModelGPU { @@ -168,6 +177,16 @@ private: std::unique_ptr characterShader; pipeline::AssetManager* assetManager = nullptr; + // Fog parameters + glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f); + float fogStart = 400.0f; + float fogEnd = 1200.0f; + + // Shadow mapping + GLuint shadowDepthTex = 0; + glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); + bool shadowEnabled = false; + // Texture cache std::unordered_map textureCache; GLuint whiteTexture = 0; diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 8c7f6550..62db8321 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -224,6 +224,15 @@ public: uint32_t getTotalTriangleCount() const; uint32_t getDrawCallCount() const { return lastDrawCallCount; } + void setFog(const glm::vec3& color, float start, float end) { + fogColor = color; fogStart = start; fogEnd = end; + } + + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { + shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; + } + void clearShadowMap() { shadowEnabled = false; } + private: pipeline::AssetManager* assetManager = nullptr; std::unique_ptr shader; @@ -242,6 +251,16 @@ private: glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f); glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f); + // Fog parameters + glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f); + float fogStart = 400.0f; + float fogEnd = 1200.0f; + + // Shadow mapping + GLuint shadowDepthTex = 0; + glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); + bool shadowEnabled = false; + // Optional query-space culling for collision/raycast hot paths. bool collisionFocusEnabled = false; glm::vec3 collisionFocusPos = glm::vec3(0.0f); diff --git a/include/rendering/texture.hpp b/include/rendering/texture.hpp index 9ea98d43..5baf32a4 100644 --- a/include/rendering/texture.hpp +++ b/include/rendering/texture.hpp @@ -27,5 +27,12 @@ private: int height = 0; }; +/** + * Apply anisotropic filtering to the currently bound GL_TEXTURE_2D. + * Queries the driver maximum once and caches it. No-op if the extension + * is not available. + */ +void applyAnisotropicFiltering(); + } // namespace rendering } // namespace wowee diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 800fde10..7e2fe66d 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -129,6 +129,20 @@ public: */ void setFrustumCulling(bool enabled) { frustumCulling = enabled; } + void setFog(const glm::vec3& color, float start, float end) { + fogColor = color; fogStart = start; fogEnd = end; + } + + void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) { + shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true; + } + void clearShadowMap() { shadowEnabled = false; } + + /** + * Render depth-only for shadow casting (reuses VAOs) + */ + void renderShadow(const glm::mat4& lightView, const glm::mat4& lightProj, Shader& shadowShader); + /** * Get floor height at a GL position via ray-triangle intersection */ @@ -308,6 +322,16 @@ private: bool frustumCulling = true; uint32_t lastDrawCalls = 0; + // Fog parameters + glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f); + float fogStart = 400.0f; + float fogEnd = 1200.0f; + + // Shadow mapping + GLuint shadowDepthTex = 0; + glm::mat4 lightSpaceMatrix = glm::mat4(1.0f); + bool shadowEnabled = false; + // Optional query-space culling for collision/raycast hot paths. bool collisionFocusEnabled = false; glm::vec3 collisionFocusPos = glm::vec3(0.0f); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 5174082f..8f31ec21 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -92,15 +92,46 @@ bool CharacterRenderer::initialize() { uniform vec3 uLightDir; uniform vec3 uViewPos; + uniform vec3 uFogColor; + uniform float uFogStart; + uniform float uFogEnd; + + uniform sampler2DShadow uShadowMap; + uniform mat4 uLightSpaceMatrix; + uniform int uShadowEnabled; + out vec4 FragColor; void main() { vec3 normal = normalize(Normal); vec3 lightDir = normalize(uLightDir); - // Simple diffuse lighting + // Diffuse lighting float diff = max(dot(normal, lightDir), 0.0); - vec3 diffuse = diff * vec3(1.0); + + // Blinn-Phong specular + vec3 viewDir = normalize(uViewPos - FragPos); + vec3 halfDir = normalize(lightDir + viewDir); + float spec = pow(max(dot(normal, halfDir), 0.0), 32.0); + vec3 specular = spec * vec3(0.2); + + // Shadow mapping + float shadow = 1.0; + if (uShadowEnabled != 0) { + vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0); + vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5; + if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) { + float bias = max(0.005 * (1.0 - abs(dot(normal, lightDir))), 0.001); + shadow = 0.0; + vec2 texelSize = vec2(1.0 / 2048.0); + for (int sx = -1; sx <= 1; sx++) { + for (int sy = -1; sy <= 1; sy++) { + shadow += texture(uShadowMap, vec3(proj.xy + vec2(sx, sy) * texelSize, proj.z - bias)); + } + } + shadow /= 9.0; + } + } // Ambient vec3 ambient = vec3(0.3); @@ -109,7 +140,13 @@ bool CharacterRenderer::initialize() { vec4 texColor = texture(uTexture0, TexCoord); // Combine - vec3 result = (ambient + diffuse) * texColor.rgb; + vec3 result = (ambient + (diff * vec3(1.0) + specular) * shadow) * texColor.rgb; + + // Fog + float fogDist = length(uViewPos - FragPos); + float fogFactor = clamp((uFogEnd - fogDist) / (uFogEnd - uFogStart), 0.0, 1.0); + result = mix(uFogColor, result, fogFactor); + FragColor = vec4(result, texColor.a); } )"; @@ -207,6 +244,7 @@ GLuint CharacterRenderer::loadTexture(const std::string& path) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); textureCache[path] = texId; @@ -417,6 +455,7 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); core::Logger::getInstance().info("Composite texture created: ", width, "x", height, " from ", layerPaths.size(), " layers"); @@ -542,6 +581,7 @@ GLuint CharacterRenderer::compositeWithRegions(const std::string& basePath, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); core::Logger::getInstance().info("compositeWithRegions: created ", width, "x", height, @@ -978,6 +1018,20 @@ void CharacterRenderer::render(const Camera& camera, const glm::mat4& view, cons characterShader->setUniform("uLightDir", glm::vec3(0.0f, -1.0f, 0.3f)); characterShader->setUniform("uViewPos", camera.getPosition()); + // Fog + characterShader->setUniform("uFogColor", fogColor); + characterShader->setUniform("uFogStart", fogStart); + characterShader->setUniform("uFogEnd", fogEnd); + + // Shadows + characterShader->setUniform("uShadowEnabled", shadowEnabled ? 1 : 0); + if (shadowEnabled) { + characterShader->setUniform("uLightSpaceMatrix", lightSpaceMatrix); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, shadowDepthTex); + characterShader->setUniform("uShadowMap", 7); + } + for (const auto& pair : instances) { const auto& instance = pair.second; diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index eba6940b..94623533 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1,4 +1,5 @@ #include "rendering/m2_renderer.hpp" +#include "rendering/texture.hpp" #include "rendering/shader.hpp" #include "rendering/camera.hpp" #include "rendering/frustum.hpp" @@ -253,11 +254,20 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) { uniform vec3 uLightDir; uniform vec3 uAmbientColor; + uniform vec3 uViewPos; uniform sampler2D uTexture; uniform bool uHasTexture; uniform bool uAlphaTest; uniform float uFadeAlpha; + uniform vec3 uFogColor; + uniform float uFogStart; + uniform float uFogEnd; + + uniform sampler2DShadow uShadowMap; + uniform mat4 uLightSpaceMatrix; + uniform bool uShadowEnabled; + out vec4 FragColor; void main() { @@ -285,10 +295,40 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) { // Two-sided lighting for foliage float diff = max(abs(dot(normal, lightDir)), 0.3); + // Blinn-Phong specular + vec3 viewDir = normalize(uViewPos - FragPos); + vec3 halfDir = normalize(lightDir + viewDir); + float spec = pow(max(dot(normal, halfDir), 0.0), 32.0); + vec3 specular = spec * vec3(0.1); + + // Shadow mapping + float shadow = 1.0; + if (uShadowEnabled) { + vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0); + vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5; + if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) { + float bias = max(0.005 * (1.0 - abs(dot(normal, lightDir))), 0.001); + shadow = 0.0; + vec2 texelSize = vec2(1.0 / 2048.0); + for (int sx = -1; sx <= 1; sx++) { + for (int sy = -1; sy <= 1; sy++) { + shadow += texture(uShadowMap, vec3(proj.xy + vec2(sx, sy) * texelSize, proj.z - bias)); + } + } + shadow /= 9.0; + } + } + vec3 ambient = uAmbientColor * texColor.rgb; vec3 diffuse = diff * texColor.rgb; - vec3 result = ambient + diffuse; + vec3 result = ambient + (diffuse + specular) * shadow; + + // Fog + float fogDist = length(uViewPos - FragPos); + float fogFactor = clamp((uFogEnd - fogDist) / (uFogEnd - uFogStart), 0.0, 1.0); + result = mix(uFogColor, result, fogFactor); + FragColor = vec4(result, finalAlpha); } )"; @@ -1051,8 +1091,6 @@ void M2Renderer::update(float deltaTime) { } void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection) { - (void)camera; // unused for now - if (instances.empty() || !shader) { return; } @@ -1080,6 +1118,17 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm:: shader->setUniform("uProjection", projection); shader->setUniform("uLightDir", lightDir); shader->setUniform("uAmbientColor", ambientColor); + shader->setUniform("uViewPos", camera.getPosition()); + shader->setUniform("uFogColor", fogColor); + shader->setUniform("uFogStart", fogStart); + shader->setUniform("uFogEnd", fogEnd); + shader->setUniform("uShadowEnabled", shadowEnabled ? 1 : 0); + if (shadowEnabled) { + shader->setUniform("uLightSpaceMatrix", lightSpaceMatrix); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, shadowDepthTex); + shader->setUniform("uShadowMap", 7); + } lastDrawCallCount = 0; @@ -1389,6 +1438,7 @@ GLuint M2Renderer::loadTexture(const std::string& path) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index fd05681c..ab65391d 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -921,6 +921,14 @@ void Renderer::renderWorld(game::World* world) { lensFlare->render(*camera, sunPosition, timeOfDay); } + // Update fog across all renderers based on time of day (match sky color) + if (skybox) { + glm::vec3 horizonColor = skybox->getHorizonColor(timeOfDay); + if (wmoRenderer) wmoRenderer->setFog(horizonColor, 100.0f, 600.0f); + if (m2Renderer) m2Renderer->setFog(horizonColor, 100.0f, 600.0f); + if (characterRenderer) characterRenderer->setFog(horizonColor, 100.0f, 600.0f); + } + // Render terrain if loaded and enabled if (terrainEnabled && terrainLoaded && terrainRenderer && camera) { // Check if camera/character is underwater for fog override @@ -951,7 +959,6 @@ void Renderer::renderWorld(game::World* world) { } if (skybox) { - // Update terrain fog based on time of day (match sky color) glm::vec3 horizonColor = skybox->getHorizonColor(timeOfDay); float fogColorArray[3] = {horizonColor.r, horizonColor.g, horizonColor.b}; terrainRenderer->setFog(fogColorArray, 400.0f, 1200.0f); diff --git a/src/rendering/terrain_renderer.cpp b/src/rendering/terrain_renderer.cpp index c6e656f6..c79e04ab 100644 --- a/src/rendering/terrain_renderer.cpp +++ b/src/rendering/terrain_renderer.cpp @@ -1,4 +1,5 @@ #include "rendering/terrain_renderer.hpp" +#include "rendering/texture.hpp" #include "rendering/frustum.hpp" #include "pipeline/asset_manager.hpp" #include "pipeline/blp_loader.hpp" @@ -239,6 +240,7 @@ GLuint TerrainRenderer::loadTexture(const std::string& path) { // Generate mipmaps glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); diff --git a/src/rendering/texture.cpp b/src/rendering/texture.cpp index 5dcc2e15..769ba36e 100644 --- a/src/rendering/texture.cpp +++ b/src/rendering/texture.cpp @@ -33,6 +33,7 @@ bool Texture::loadFromMemory(const unsigned char* data, int w, int h, int channe glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glBindTexture(GL_TEXTURE_2D, 0); return true; @@ -47,5 +48,22 @@ void Texture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); } +void applyAnisotropicFiltering() { + static float maxAniso = -1.0f; + if (maxAniso < 0.0f) { + if (GLEW_EXT_texture_filter_anisotropic) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + if (maxAniso < 1.0f) maxAniso = 1.0f; + } else { + maxAniso = 0.0f; // Extension not available + } + } + if (maxAniso > 0.0f) { + float desired = 16.0f; + float clamped = (desired < maxAniso) ? desired : maxAniso; + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, clamped); + } +} + } // namespace rendering } // namespace wowee diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 1dd02981..284673e5 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1,4 +1,5 @@ #include "rendering/wmo_renderer.hpp" +#include "rendering/texture.hpp" #include "rendering/shader.hpp" #include "rendering/camera.hpp" #include "rendering/frustum.hpp" @@ -79,6 +80,14 @@ bool WMORenderer::initialize(pipeline::AssetManager* assets) { uniform bool uHasTexture; uniform bool uAlphaTest; + uniform vec3 uFogColor; + uniform float uFogStart; + uniform float uFogEnd; + + uniform sampler2DShadow uShadowMap; + uniform mat4 uLightSpaceMatrix; + uniform bool uShadowEnabled; + out vec4 FragColor; void main() { @@ -92,6 +101,12 @@ bool WMORenderer::initialize(pipeline::AssetManager* assets) { // Ambient vec3 ambient = uAmbientColor; + // Blinn-Phong specular + vec3 viewDir = normalize(uViewPos - FragPos); + vec3 halfDir = normalize(lightDir + viewDir); + float spec = pow(max(dot(normal, halfDir), 0.0), 32.0); + vec3 specular = spec * vec3(0.15); + // Sample texture or use vertex color vec4 texColor; if (uHasTexture) { @@ -103,8 +118,32 @@ bool WMORenderer::initialize(pipeline::AssetManager* assets) { texColor = vec4(VertexColor.rgb, 1.0); } + // Shadow mapping + float shadow = 1.0; + if (uShadowEnabled) { + vec4 lsPos = uLightSpaceMatrix * vec4(FragPos, 1.0); + vec3 proj = lsPos.xyz / lsPos.w * 0.5 + 0.5; + if (proj.z <= 1.0 && proj.x >= 0.0 && proj.x <= 1.0 && proj.y >= 0.0 && proj.y <= 1.0) { + float bias = max(0.005 * (1.0 - dot(normal, lightDir)), 0.001); + shadow = 0.0; + vec2 texelSize = vec2(1.0 / 2048.0); + for (int sx = -1; sx <= 1; sx++) { + for (int sy = -1; sy <= 1; sy++) { + shadow += texture(uShadowMap, vec3(proj.xy + vec2(sx, sy) * texelSize, proj.z - bias)); + } + } + shadow /= 9.0; + } + } + // Combine lighting with texture - vec3 result = (ambient + diffuse) * texColor.rgb; + vec3 result = (ambient + (diffuse + specular) * shadow) * texColor.rgb; + + // Fog + float fogDist = length(uViewPos - FragPos); + float fogFactor = clamp((uFogEnd - fogDist) / (uFogEnd - uFogStart), 0.0, 1.0); + result = mix(uFogColor, result, fogFactor); + FragColor = vec4(result, 1.0); } )"; @@ -452,6 +491,16 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm: shader->setUniform("uViewPos", camera.getPosition()); shader->setUniform("uLightDir", glm::vec3(-0.3f, -0.7f, -0.6f)); // Default sun direction shader->setUniform("uAmbientColor", glm::vec3(0.4f, 0.4f, 0.5f)); + shader->setUniform("uFogColor", fogColor); + shader->setUniform("uFogStart", fogStart); + shader->setUniform("uFogEnd", fogEnd); + shader->setUniform("uShadowEnabled", shadowEnabled ? 1 : 0); + if (shadowEnabled) { + shader->setUniform("uLightSpaceMatrix", lightSpaceMatrix); + glActiveTexture(GL_TEXTURE7); + glBindTexture(GL_TEXTURE_2D, shadowDepthTex); + shader->setUniform("uShadowMap", 7); + } // Enable wireframe if requested if (wireframeMode) { @@ -512,6 +561,28 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm: glEnable(GL_CULL_FACE); } +void WMORenderer::renderShadow(const glm::mat4& lightView, const glm::mat4& lightProj, Shader& shadowShader) { + if (instances.empty()) return; + Frustum frustum; + frustum.extractFromMatrix(lightProj * lightView); + for (const auto& instance : instances) { + auto modelIt = loadedModels.find(instance.modelId); + if (modelIt == loadedModels.end()) continue; + if (frustumCulling) { + glm::vec3 instMin = instance.worldBoundsMin - glm::vec3(0.5f); + glm::vec3 instMax = instance.worldBoundsMax + glm::vec3(0.5f); + if (!frustum.intersectsAABB(instMin, instMax)) continue; + } + const ModelData& model = modelIt->second; + shadowShader.setUniform("uModel", instance.modelMatrix); + for (const auto& group : model.groups) { + glBindVertexArray(group.vao); + glDrawElements(GL_TRIANGLES, group.indexCount, GL_UNSIGNED_SHORT, 0); + glBindVertexArray(0); + } + } +} + uint32_t WMORenderer::getTotalTriangleCount() const { uint32_t total = 0; for (const auto& instance : instances) { @@ -769,6 +840,7 @@ GLuint WMORenderer::loadTexture(const std::string& path) { // Set texture parameters with mipmaps glGenerateMipmap(GL_TEXTURE_2D); + applyAnisotropicFiltering(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);