diff --git a/resources/shaders/fragment/uber.glsl b/resources/shaders/fragment/uber.glsl index 73db1e4b..146d165b 100644 --- a/resources/shaders/fragment/uber.glsl +++ b/resources/shaders/fragment/uber.glsl @@ -96,34 +96,43 @@ float calculateShadowFactor(int directionalLightIndex, LodBlend lodBlend) { } // Define PCF kernel size and bias for shadow comparison - float bias = 0.001; - float shadowLowLod = 0.0; - float shadowHighLod = 0.0; - int pcfKernelSize = - 2; // You can adjust this value for more or less smoothing - float texelSize = - 1.0 / 2048.0; // Assume a shadow map resolution of 2048x2048 + float bias = 0.01; + int pcfKernelSize = 1; + + // Set texel sizes for low and high LODs + float texelSizeLowLod = + 1.0 / 1024.0; // Low LOD texel size (assumed 1024x1024) + float texelSizeHighLod = + 1.0 * texelSizeLowLod; // High LOD texel size, twice as large - // Loop over the kernel to sample neighboring depths for the low LOD + // Calculate shadow factor for low LOD + float shadowLowLod = 0.0; for (int x = -pcfKernelSize; x <= pcfKernelSize; ++x) { for (int y = -pcfKernelSize; y <= pcfKernelSize; ++y) { - // Sample the shadow map for the low LOD + // Sample shadow map for low LOD float closestDepthLowLod = texture(directionalLights[directionalLightIndex] .shadowSampler[lodBlend.lowLod], - projCoordsLowLod.xy + vec2(x, y) * texelSize) + projCoordsLowLod.xy + vec2(x, y) * texelSizeLowLod) .r; closestDepthLowLod = closestDepthLowLod * 0.5 + 0.5; // Remap to [0, 1] range if (projCoordsLowLod.z - bias > closestDepthLowLod) { shadowLowLod += 1.0; } + } + } + shadowLowLod /= float((pcfKernelSize * 2 + 1) * (pcfKernelSize * 2 + 1)); - // Sample the shadow map for the high LOD + // Calculate shadow factor for high LOD + float shadowHighLod = 0.0; + for (int x = -pcfKernelSize; x <= pcfKernelSize; ++x) { + for (int y = -pcfKernelSize; y <= pcfKernelSize; ++y) { + // Sample shadow map for high LOD float closestDepthHighLod = texture(directionalLights[directionalLightIndex] .shadowSampler[lodBlend.highLod], - projCoordsHighLod.xy + vec2(2 * x, 2 * y) * texelSize) + projCoordsHighLod.xy + vec2(x, y) * texelSizeHighLod) .r; closestDepthHighLod = closestDepthHighLod * 0.5 + 0.5; // Remap to [0, 1] range @@ -132,9 +141,6 @@ float calculateShadowFactor(int directionalLightIndex, LodBlend lodBlend) { } } } - - // Normalize shadow values by the kernel size - shadowLowLod /= float((pcfKernelSize * 2 + 1) * (pcfKernelSize * 2 + 1)); shadowHighLod /= float((pcfKernelSize * 2 + 1) * (pcfKernelSize * 2 + 1)); // Blend the shadow factors between the low and high LOD based on highLodWeight @@ -142,28 +148,47 @@ float calculateShadowFactor(int directionalLightIndex, LodBlend lodBlend) { } LodBlend chooseLightLOD(mat4 lightSpaceMatrix[4], vec3 fragPos) { - // Transform fragment position to light space + const float blendRange = 0.2; // Range near cascade far edge to blend + for (int lod = 0; lod < 4; ++lod) { vec4 fragPosLightSpace = lightSpaceMatrix[lod] * vec4(fragPos, 1.0); vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; projCoords = projCoords * 0.5 + 0.5; + // Check if fragment is inside current LOD's view if (projCoords.x >= 0.0 && projCoords.y >= 0.0 && projCoords.x <= 1.0 && projCoords.y <= 1.0 && projCoords.z < 1.0) { + + // If we're at the last LOD, no blending with a higher LOD if (lod == 3) { return LodBlend(lod, lod, 1.0); - } else { - float distanceToEdgeX = min(projCoords.x, 1.0 - projCoords.x); - float distanceToEdgeY = min(projCoords.y, 1.0 - projCoords.y); + } + + // Transform to next LOD’s light space to calculate depth blending + vec4 fragPosLightSpaceNext = + lightSpaceMatrix[lod + 1] * vec4(fragPos, 1.0); + vec3 projCoordsNext = fragPosLightSpaceNext.xyz / fragPosLightSpaceNext.w; + projCoordsNext = projCoordsNext * 0.5 + 0.5; - float distanceToEdge = min(distanceToEdgeX, distanceToEdgeY); + // Verify that fragment is also within next LOD's frustum + if (projCoordsNext.x >= 0.0 && projCoordsNext.y >= 0.0 && + projCoordsNext.x <= 1.0 && projCoordsNext.y <= 1.0 && + projCoordsNext.z < 1.0) { + + // Compute blending weight based on fragment's depth in current LOD + float depthInCurrentLOD = projCoords.z; + float highLodWeight = + smoothstep(1.0 - blendRange, 1.0, depthInCurrentLOD); - float highLodWeight = distanceToEdge / 0.5; return LodBlend(lod, lod + 1, highLodWeight); } + + // If fragment not inside next LOD, return current LOD without blending + return LodBlend(lod, lod, 1.0); } } + // Default return if fragment is outside all cascades return LodBlend(-1, -1, 0.0); } diff --git a/resources/shaders/vertex/shadowmap.glsl b/resources/shaders/vertex/shadowmap.glsl index 21e7821b..21ddc47b 100644 --- a/resources/shaders/vertex/shadowmap.glsl +++ b/resources/shaders/vertex/shadowmap.glsl @@ -2,16 +2,14 @@ layout(location = 0) in vec3 aPos; // Vertex position -uniform mat4 modelMatrix; // Model matrix -uniform mat4 viewMatrix; // View matrix -uniform mat4 projectionMatrix; // Projection matrix +uniform mat4 lightSpaceMatrix; // Model matrix +uniform mat4 modelMatrix; // Projection matrix out float FragDepth; // Output the depth value to the fragment shader void main() { // Transform vertex position to world space, then to light space - vec4 lightSpacePosition = - projectionMatrix * viewMatrix * modelMatrix * vec4(aPos, 1.0); + vec4 lightSpacePosition = lightSpaceMatrix * modelMatrix * vec4(aPos, 1.0); // Set the final position gl_Position = lightSpacePosition; diff --git a/src/Components/CameraComponent.hpp b/src/Components/CameraComponent.hpp index ed309898..7db2b940 100644 --- a/src/Components/CameraComponent.hpp +++ b/src/Components/CameraComponent.hpp @@ -14,7 +14,7 @@ class CameraComponent { Orthographic }; - CameraComponent(float fov = 45.0f, float aspectRatio = 16.0f / 9.0f, float nearPlane = 0.1f, float farPlane = 10000.0f, ProjectionType projectionType = ProjectionType::Perspective) + CameraComponent(float fov = 45.0f, float aspectRatio = 16.0f / 9.0f, float nearPlane = 0.01f, float farPlane = 2000.0f, ProjectionType projectionType = ProjectionType::Perspective) : fov(fov), aspectRatio(aspectRatio), nearPlane(nearPlane), farPlane(farPlane), projectionType(projectionType) {} float fov; @@ -40,14 +40,7 @@ class CameraComponent { } glm::mat4 getProjectionMatrix() const { - if (projectionType == ProjectionType::Perspective) { - return glm::perspective(glm::radians(fov), aspectRatio, nearPlane, farPlane); - } else { - float orthoSize = 10.0f; // Can be configurable - float halfWidth = orthoSize * aspectRatio * 0.5f; - float halfHeight = orthoSize * 0.5f; - return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, nearPlane, farPlane); - } + return getProjectionMatrix(nearPlane, farPlane); } Ray getRayAt(const TransformComponent& transformComponent, float x, float y) const { @@ -74,6 +67,34 @@ class CameraComponent { return { rayOrigin, rayDirection }; } + std::vector getFrustumCornersWorldSpace(float localNearPlane, float localFarPlane, const TransformComponent& transformComponent) const { + glm::mat4 invViewProj = glm::inverse(getProjectionMatrix(localNearPlane, localFarPlane) * getViewMatrix(transformComponent)); + + std::vector frustumCorners; + for (int x = -1; x <= 1; x += 2) { + for (int y = -1; y <= 1; y += 2) { + for (int z = -1; z <= 1; z += 2) { + glm::vec4 corner = invViewProj * glm::vec4(x, y, z, 1.0f); + corner /= corner.w; + frustumCorners.push_back(glm::vec3(corner)); + } + } + } + return frustumCorners; + } + + private: + glm::mat4 getProjectionMatrix(float localNearPlane, float localFarPlane) const { + if (projectionType == ProjectionType::Perspective) { + return glm::perspective(glm::radians(fov), aspectRatio, localNearPlane, localFarPlane); + } else { + float orthoSize = 10.0f; // Can be configurable + float halfWidth = orthoSize * aspectRatio * 0.5f; + float halfHeight = orthoSize * 0.5f; + return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, localNearPlane, localFarPlane); + } + } + }; } // namespace shkyera diff --git a/src/Components/DirectionalLightComponent.hpp b/src/Components/DirectionalLightComponent.hpp index 945efa3a..1ba21a81 100644 --- a/src/Components/DirectionalLightComponent.hpp +++ b/src/Components/DirectionalLightComponent.hpp @@ -7,6 +7,7 @@ #include #include +#include namespace shkyera { @@ -15,33 +16,37 @@ class DirectionalLightComponent : public BaseComponent CascadePlanes = {0.01, 2.0, 5.0, 10.0, 100.0}; + + glm::mat4 getLightSpaceMatrix( + const TransformComponent& lightTransformComponent, + const TransformComponent& cameraTransformComponent, + const CameraComponent& cameraComponent, + uint8_t levelOfDetail) const + { + auto frustumCorners = cameraComponent.getFrustumCornersWorldSpace(CascadePlanes[levelOfDetail], CascadePlanes[levelOfDetail + 1], cameraTransformComponent); - glm::vec3 position = cameraTransformComponent.getPosition() - 100.0f * front; + glm::vec3 front = getDirection(lightTransformComponent); + glm::vec3 center = glm::vec3(0.0f); - glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 right = glm::normalize(glm::cross(front, up)); - up = -glm::normalize(glm::cross(right, front)); + for (const auto& corner : frustumCorners) + center += corner; + center /= frustumCorners.size(); - return glm::lookAt(position, position + front, up); - } + glm::mat4 lightView = glm::lookAt(center + front * 50.0f, center, glm::vec3(0.0f, 1.0f, 0.0f)); - glm::mat4 getProjectionMatrix(uint8_t levelOfDetail) const { - constexpr float NearPlane = 0.1f; - constexpr float FarPlane = 1000.0f; - constexpr float DefaultHalfSize = 10.0f; - constexpr float levelOfDetailExpansion = 2.0f; + glm::vec3 min = glm::vec3(std::numeric_limits::max()); + glm::vec3 max = glm::vec3(std::numeric_limits::lowest()); - float halfSize = powf(levelOfDetailExpansion, levelOfDetail) * DefaultHalfSize; + for (const auto& corner : frustumCorners) { + glm::vec3 cornerInLightSpace = glm::vec3(lightView * glm::vec4(corner, 1.0f)); + min = glm::min(min, cornerInLightSpace); + max = glm::max(max, cornerInLightSpace); + } - return glm::ortho(-halfSize, halfSize, -halfSize, halfSize, NearPlane, FarPlane); + glm::mat4 lightProjection = glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 100.0f); + return lightProjection * lightView; } static glm::vec3 getDirection(const TransformComponent& lightTransformComponent) diff --git a/src/Rendering/FrameBuffers/DepthFrameBuffer.cpp b/src/Rendering/FrameBuffers/DepthFrameBuffer.cpp index 94e2ad89..e79dd48f 100644 --- a/src/Rendering/FrameBuffers/DepthFrameBuffer.cpp +++ b/src/Rendering/FrameBuffers/DepthFrameBuffer.cpp @@ -76,7 +76,7 @@ void DepthFrameBuffer::setSize(uint32_t width, uint32_t height) { _width = width; _height = height; - _textureDepthBuffer.setData(GL_R16F, _width, _height, GL_RED, GL_FLOAT); + _textureDepthBuffer.setData(GL_R32F, _width, _height, GL_RED, GL_FLOAT); glBindRenderbuffer(GL_RENDERBUFFER, _rbo); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, _width, _height); @@ -94,13 +94,9 @@ void DepthFrameBuffer::setupFramebuffer() { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _rbo); glBindTexture(GL_TEXTURE_2D, _textureDepthBuffer.getID()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - GLfloat borderColor[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); - - // Verify the framebuffer completeness if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { std::cout << "ERROR::FRAMEBUFFER:: Depth Frame Buffer is not complete!" << std::endl; } diff --git a/src/Systems/RenderingSystem.cpp b/src/Systems/RenderingSystem.cpp index a164d4f5..9c80f9b8 100644 --- a/src/Systems/RenderingSystem.cpp +++ b/src/Systems/RenderingSystem.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,7 @@ void RenderingSystem::renderModels() glEnable(GL_DEPTH_TEST); const auto& cameraTransform = _registry->getComponent(_registry->getCamera()); + const auto& cameraComponent = _registry->getComponent(_registry->getCamera()); // ********* Rendering the shadow maps ********* std::unordered_set directionalLightEntities; @@ -162,7 +164,7 @@ void RenderingSystem::renderModels() directionalLightEntities.insert(entity); if(_directionalLightToShadowMaps.find(entity) == _directionalLightToShadowMaps.end()) { - _directionalLightToShadowMaps.emplace(entity, std::array{}); + _directionalLightToShadowMaps.emplace(entity, std::vector(DirectionalLightComponent::LevelsOfDetail)); } } @@ -183,20 +185,19 @@ void RenderingSystem::renderModels() for(auto& [lightEntity, lodBuffers] : _directionalLightToShadowMaps) { uint8_t levelOfDetail = 0; + lodBuffers.resize(DirectionalLightComponent::LevelsOfDetail); for(auto& buffer : lodBuffers) { buffer.bind(); - buffer.setSize(2048, 2048); + buffer.setSize(1600, 1600); buffer.clear(); _shadowMapShaderProgram.use(); const auto& directionalLightComponent = _registry->getComponent(lightEntity); const auto& lightTransform = _registry->getComponent(lightEntity); - const glm::mat4& viewMatrix = directionalLightComponent.getViewMatrix(lightTransform, cameraTransform); - const glm::mat4& projectionMatrix = directionalLightComponent.getProjectionMatrix(levelOfDetail++); + const glm::mat4& lightSpaceMatrix = directionalLightComponent.getLightSpaceMatrix(lightTransform, cameraTransform, cameraComponent, levelOfDetail); - _shadowMapShaderProgram.setUniform("viewMatrix", viewMatrix); - _shadowMapShaderProgram.setUniform("projectionMatrix", projectionMatrix); + _shadowMapShaderProgram.setUniform("lightSpaceMatrix", lightSpaceMatrix); for (const auto& [modelEntity, modelComponent] : _registry->getComponentSet()) { const auto& transformComponent = _registry->getComponent(modelEntity); _shadowMapShaderProgram.setUniform("modelMatrix", transformComponent.getTransformMatrix()); @@ -206,6 +207,7 @@ void RenderingSystem::renderModels() _shadowMapShaderProgram.stopUsing(); buffer.unbind(); + levelOfDetail++; } } @@ -240,15 +242,15 @@ void RenderingSystem::renderModels() int directionalLightIndex = 0; int textureIndex = 0; for (const auto& [entity, directionalLightComponent] : _registry->getComponentSet()) { - const auto& transformComponent = _registry->getComponent(entity); - const auto& orientation = transformComponent.getOrientation(); + const auto& lightTransform = _registry->getComponent(entity); + const auto& orientation = lightTransform.getOrientation(); const auto& depthFrameBuffers = _directionalLightToShadowMaps.at(entity); for(size_t levelOfDetail = 0; levelOfDetail < depthFrameBuffers.size(); levelOfDetail++) { depthFrameBuffers[levelOfDetail].getTexture().activate(GL_TEXTURE0 + textureIndex); - const glm::mat4 lightSpaceMatrix = directionalLightComponent.getProjectionMatrix(levelOfDetail) * directionalLightComponent.getViewMatrix(transformComponent, cameraTransform); + const glm::mat4 lightSpaceMatrix = directionalLightComponent.getLightSpaceMatrix(lightTransform, cameraTransform, cameraComponent, levelOfDetail); _modelShaderProgram.setUniform("directionalLights[" + std::to_string(directionalLightIndex) + "].shadowSampler[" + std::to_string(levelOfDetail) + "]", textureIndex); _modelShaderProgram.setUniform("directionalLights[" + std::to_string(directionalLightIndex) + "].lightSpaceMatrix[" + std::to_string(levelOfDetail) + "]", lightSpaceMatrix); @@ -256,7 +258,7 @@ void RenderingSystem::renderModels() } - glm::vec3 lightDirection = DirectionalLightComponent::getDirection(transformComponent); + glm::vec3 lightDirection = DirectionalLightComponent::getDirection(lightTransform); _modelShaderProgram.setUniform("directionalLights[" + std::to_string(directionalLightIndex) + "].direction", lightDirection); _modelShaderProgram.setUniform("directionalLights[" + std::to_string(directionalLightIndex) + "].color", directionalLightComponent.intensity * directionalLightComponent.color); ++directionalLightIndex; diff --git a/src/Systems/RenderingSystem.hpp b/src/Systems/RenderingSystem.hpp index ec89bdb8..297ff01f 100644 --- a/src/Systems/RenderingSystem.hpp +++ b/src/Systems/RenderingSystem.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -47,8 +48,7 @@ class RenderingSystem { ShaderProgram _skyboxShaderProgram; // Light rendering - constexpr static size_t DirectionalLightLOD = 4; - std::unordered_map> _directionalLightToShadowMaps; + std::unordered_map> _directionalLightToShadowMaps; ShaderProgram _shadowMapShaderProgram; }; diff --git a/src/main.cpp b/src/main.cpp index e630fc06..56c643cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,15 +108,15 @@ void loadScene(std::shared_ptr registry) { registry->getComponent(sky).intensity = 0.4; registry->addComponent(sky); - auto moon = registry->addEntity(); - registry->addComponent(moon); - registry->getComponent(moon).setOrientation({-M_PI_2 / 2, -M_PI_2 / 2, 0}); - registry->addComponent(moon); - registry->getComponent(moon).setName("Moon"); - registry->addComponent(moon); - registry->getComponent(moon).color = glm::vec3{0.85, 0.95, 0.95}; - registry->getComponent(moon).intensity = 0.15; - registry->addComponent(moon); + // auto moon = registry->addEntity(); + // registry->addComponent(moon); + // registry->getComponent(moon).setOrientation({-M_PI_2 / 2, -M_PI_2 / 2, 0}); + // registry->addComponent(moon); + // registry->getComponent(moon).setName("Moon"); + // registry->addComponent(moon); + // registry->getComponent(moon).color = glm::vec3{0.85, 0.95, 0.95}; + // registry->getComponent(moon).intensity = 0.15; + // registry->addComponent(moon); const auto skyboxUp = AssetManager::getInstance().getAsset("resources/skyboxes/day/py.png"); const auto skyboxDown = AssetManager::getInstance().getAsset("resources/skyboxes/day/ny.png");