Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cascaded shadow maps #1338

Merged
merged 8 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Orthographic Camera component (#1182, **@mkuritsu**).
- Importer plugin (#1299, **@Scarface1809**).
- Handle body rotation on penetration solving (#1272, **&fallenatlas**).
- Cascaded shadow maps (#1187, **@tomas7770**).

### Changed

Expand All @@ -33,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Spot light angle mismatch between light and shadows (#1310, **@tomas7770**).
- Spot shadows cause light range cutoff (#1312, **@tomas7770**).
- Precision error in split screen size calculations (**@mkuritsu**).
- Incorrect loop condition in createTexture2DArray (**@tomas7770**).
- Use glTexImage3D instead of glTexStorage3D in createTexture2DArray (**@tomas7770**).


## [v0.3.0] - 2024-08-02
Expand Down
9 changes: 9 additions & 0 deletions core/include/cubos/core/geom/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,13 @@ namespace cubos::core::geom
/// @param polygon List of points that compose the shape (edges are defined as a line-loop list of vertices).
/// @return Closest point that resides on any of the given edges of the polygon.
CUBOS_CORE_API glm::vec3 getClosestPointPolygon(const glm::vec3& point, const std::vector<glm::vec3>& polygon);

/// @brief Gets a camera's frustum corners in world space.
/// @param view Matrix that transforms world space to the camera's view space.
/// @param proj Matrix that transforms the camera's view space to its clip space.
/// @param zNear Near clipping plane.
/// @param zFar Far clipping plane.
/// @param corners Output vector where the corners will be stored.
CUBOS_CORE_API void getCameraFrustumCorners(const glm::mat4& view, const glm::mat4& proj, float zNear, float zFar,
std::vector<glm::vec4>& corners);
} // namespace cubos::core::geom
35 changes: 35 additions & 0 deletions core/src/geom/utils.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <algorithm>
#include <limits>

#include <glm/glm.hpp>
Expand Down Expand Up @@ -156,3 +157,37 @@

return finalClosestPoint;
}

void cubos::core::geom::getCameraFrustumCorners(const glm::mat4& view, const glm::mat4& proj, float zNear, float zFar,

Check warning on line 161 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L161

Added line #L161 was not covered by tests
std::vector<glm::vec4>& corners)
{
// Convert zNear and zFar to normalized coordinates.
// If they don't match the clipping planes of the projection matrix, the points returned
// won't be the actual corners of the frustum. This may be useful for splitting the
// frustum along clipping planes between near and far.

// Points are converted from view space to clip space
auto pointNear = proj * glm::vec4(0.0F, 0.0F, -zNear, 1.0F);
auto pointFar = proj * glm::vec4(0.0F, 0.0F, -zFar, 1.0F);
pointNear /= pointNear.w;
pointFar /= pointFar.w;

Check warning on line 173 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L170-L173

Added lines #L170 - L173 were not covered by tests

float zNearNDC = std::clamp(pointNear.z, -1.0F, 1.0F);
float zFarNDC = std::clamp(pointFar.z, -1.0F, 1.0F);

Check warning on line 176 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L175-L176

Added lines #L175 - L176 were not covered by tests

// Proceed to getting the corners

auto viewProjInv = glm::inverse(proj * view);

Check warning on line 180 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L180

Added line #L180 was not covered by tests

for (int x = -1; x <= 1; x += 2)

Check warning on line 182 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L182

Added line #L182 was not covered by tests
{
for (int y = -1; y <= 1; y += 2)

Check warning on line 184 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L184

Added line #L184 was not covered by tests
{
auto cornerNear = viewProjInv * glm::vec4(x, y, zNearNDC, 1.0F);
corners.push_back(cornerNear / cornerNear.w);

Check warning on line 187 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L186-L187

Added lines #L186 - L187 were not covered by tests

auto cornerFar = viewProjInv * glm::vec4(x, y, zFarNDC, 1.0F);
corners.push_back(cornerFar / cornerFar.w);

Check warning on line 190 in core/src/geom/utils.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L189-L190

Added lines #L189 - L190 were not covered by tests
}
}
}
11 changes: 7 additions & 4 deletions core/src/gl/ogl_render_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2009,12 +2009,15 @@
GLuint id;
glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D_ARRAY, id);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, static_cast<GLsizei>(desc.mipLevelCount), internalFormat,
static_cast<GLsizei>(desc.width), static_cast<GLsizei>(desc.height),
static_cast<GLsizei>(desc.size));
for (std::size_t j = 0; j < desc.mipLevelCount; ++j)

Check warning on line 2012 in core/src/gl/ogl_render_device.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/gl/ogl_render_device.cpp#L2012

Added line #L2012 was not covered by tests
{
glTexImage3D(GL_TEXTURE_2D_ARRAY, static_cast<GLint>(j), static_cast<GLint>(internalFormat),
static_cast<GLsizei>(desc.width), static_cast<GLsizei>(desc.height),
static_cast<GLsizei>(desc.size), 0, format, type, nullptr);

Check warning on line 2016 in core/src/gl/ogl_render_device.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/gl/ogl_render_device.cpp#L2014-L2016

Added lines #L2014 - L2016 were not covered by tests
}
for (std::size_t i = 0; i < desc.size; ++i)
{
for (std::size_t j = 0, div = 1; i < desc.mipLevelCount; ++j, div *= 2)
for (std::size_t j = 0, div = 1; j < desc.mipLevelCount; ++j, div *= 2)

Check warning on line 2020 in core/src/gl/ogl_render_device.cpp

View check run for this annotation

Codecov / codecov/patch

core/src/gl/ogl_render_device.cpp#L2020

Added line #L2020 was not covered by tests
{
if (desc.data[i][j] != nullptr)
{
Expand Down
2 changes: 2 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ set(CUBOS_ENGINE_SOURCE
"src/render/shadow_atlas/shadow_atlas.cpp"
"src/render/shadow_atlas_rasterizer/plugin.cpp"
"src/render/shadow_atlas_rasterizer/shadow_atlas_rasterizer.cpp"
"src/render/cascaded_shadow_maps/plugin.cpp"
"src/render/cascaded_shadow_maps_rasterizer/plugin.cpp"

"src/tools/settings_inspector/plugin.cpp"
"src/tools/selection/plugin.cpp"
Expand Down
6 changes: 6 additions & 0 deletions engine/assets/render/cascaded_shadow_maps_rasterizer.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#version 330 core

void main()
{
// Depth is set automatically, do nothing
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "186bc625-eb3a-4a69-8c63-0eab53e39735"
}
24 changes: 24 additions & 0 deletions engine/assets/render/cascaded_shadow_maps_rasterizer.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#version 330 core

layout(triangles) in;
layout(triangle_strip, max_vertices = 30) out; // max_vertices = 3*splits

layout(std140) uniform PerScene
{
mat4 lightViewProj[10]; // max number of splits hardcoded here
int numCascades;
};

void main()
{
for (int j = 0; j < numCascades; j++)
{
for (int i = 0; i < 3; i++) // triangles have 3 vertices
{
gl_Position = lightViewProj[j] * gl_in[i].gl_Position;
gl_Layer = j;
EmitVertex();
}
EndPrimitive();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "404e782e-b959-4120-b446-05ca8f2ecdf5"
}
13 changes: 13 additions & 0 deletions engine/assets/render/cascaded_shadow_maps_rasterizer.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#version 330 core

in uvec3 position;

uniform PerMesh
{
mat4 model;
};

void main(void)
{
gl_Position = model * vec4(position, 1.0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "852fcd11-700b-454f-8a70-ef2c5ebd3bbe"
}
75 changes: 70 additions & 5 deletions engine/assets/render/deferred_shading.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ uniform sampler2D albedoTexture;
uniform sampler2D ssaoTexture;
uniform sampler2D shadowAtlasTexture;

uniform sampler2DArray directionalShadowMap; // only one directional light with shadows is supported, for now

uniform vec2 viewportOffset;
uniform vec2 viewportSize;

struct DirectionalLight
{
vec4 direction;
vec4 color;
mat4 matrices[10]; // hardcoded max splits
float intensity;
float shadowBias;
float shadowBlurRadius;
float padding;
vec4 shadowFarSplitDistances[3]; // intended to be a float array, but std140 layout aligns array elements
// to vec4 size; number of vec4s = ceiling(MaxCascades / 4 components)
int numCascades;
};

struct PointLight
Expand Down Expand Up @@ -60,6 +69,8 @@ layout(std140) uniform PerScene
uint numDirectionalLights;
uint numPointLights;
uint numSpotLights;

int directionalLightWithShadowsId; // index of directional light that casts shadows, or -1 if none
};

layout(location = 0) out vec3 color;
Expand Down Expand Up @@ -90,10 +101,12 @@ vec3 spotLightCalc(vec3 fragPos, vec3 fragNormal, SpotLight light)
else
{
vec2 texelSize = vec2(1.0 / 1024.0); // largely arbitrary value, affects blur size
for(float x = -light.shadowBlurRadius; x <= light.shadowBlurRadius; x += light.shadowBlurRadius)
for(int xi = -1; xi <= 1; xi++)
{
for(float y = -light.shadowBlurRadius; y <= light.shadowBlurRadius; y += light.shadowBlurRadius)
for(int yi = -1; yi <= 1; yi++)
{
float x = light.shadowBlurRadius*float(xi);
float y = light.shadowBlurRadius*float(yi);
float pcfDepth = texture(shadowAtlasTexture, uv + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
Expand All @@ -120,9 +133,60 @@ vec3 spotLightCalc(vec3 fragPos, vec3 fragNormal, SpotLight light)
return vec3(0);
}

vec3 directionalLightCalc(vec3 fragNormal, DirectionalLight light)
vec3 directionalLightCalc(vec3 fragPos, vec3 fragNormal, DirectionalLight light, bool drawShadows)
{
return max(dot(fragNormal, -light.direction.xyz), 0) * light.intensity * vec3(light.color);
// Shadows
float shadow = 0.0;
if (drawShadows)
{
// Select split
vec4 positionCameraSpace = inverse(inverseView) * vec4(fragPos, 1.0);
float depthCameraSpace = abs(positionCameraSpace.z);
int split = light.numCascades - 1;
for (int i = 0; i < light.numCascades; i++)
{
float far = light.shadowFarSplitDistances[i / 4][i % 4];
if (depthCameraSpace < far)
{
split = i;
break;
}
}

// Sample shadow map
vec4 positionLightSpace = light.matrices[split] * vec4(fragPos, 1.0);
vec3 projCoords = positionLightSpace.xyz / positionLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.z < 1.0)
{
vec2 uv = projCoords.xy;
float currentDepth = projCoords.z;
float bias = light.shadowBias / positionLightSpace.w; // make the bias not depend on near/far planes
// PCF
if (light.shadowBlurRadius <= 0.001f)
{
float pcfDepth = texture(directionalShadowMap, vec3(uv.xy, split)).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
else
{
vec2 texelSize = vec2(1.0 / 1024.0); // largely arbitrary value, affects blur size
tomas7770 marked this conversation as resolved.
Show resolved Hide resolved
for(int xi = -1; xi <= 1; xi++)
{
for(int yi = -1; yi <= 1; yi++)
{
float x = light.shadowBlurRadius*float(xi);
float y = light.shadowBlurRadius*float(yi);
vec2 newUv = uv + vec2(x, y) * texelSize;
float pcfDepth = texture(directionalShadowMap, vec3(newUv.xy, split)).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
}
}
}
return max(dot(fragNormal, -light.direction.xyz), 0) * (1.0 - shadow) * light.intensity * vec3(light.color);
}

vec3 pointLightCalc(vec3 fragPos, vec3 fragNormal, PointLight light)
Expand Down Expand Up @@ -174,7 +238,8 @@ void main()
}
for (uint i = 0u; i < numDirectionalLights; i++)
{
lighting += directionalLightCalc(normal, directionalLights[i]);
lighting += directionalLightCalc(position, normal, directionalLights[i],
int(i) == directionalLightWithShadowsId);
}
for (uint i = 0u; i < numPointLights; i++)
{
Expand Down
6 changes: 6 additions & 0 deletions engine/include/cubos/engine/render/camera/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,11 @@ namespace cubos::engine

/// @brief Projection matrix of the camera.
glm::mat4 projection{};

/// @brief Near clipping plane.
float zNear = 0.1F;

/// @brief Far clipping plane.
float zFar = 1000.0F;
};
} // namespace cubos::engine
6 changes: 0 additions & 6 deletions engine/include/cubos/engine/render/camera/orthographic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ namespace cubos::engine

/// @brief The axis to be fixed for the projection.
Axis axis{Axis::Vertical};

/// @brief Near clipping plane.
float zNear{0.1F};

/// @brief Far clipping plane.
float zFar{1000.0F};
};
} // namespace cubos::engine

Expand Down
6 changes: 0 additions & 6 deletions engine/include/cubos/engine/render/camera/perspective.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,5 @@ namespace cubos::engine

/// @brief Vertical field of view in degrees.
float fovY = 60.0F;

/// @brief Near clipping plane.
float zNear = 0.1F;

/// @brief Far clipping plane.
float zFar = 1000.0F;
};
} // namespace cubos::engine
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// @dir
/// @brief @ref render-cascaded-shadow-maps-plugin plugin directory.

/// @file
/// @brief Plugin entry point.
/// @ingroup render-cascaded-shadow-maps-plugin

#pragma once

#include <cubos/engine/api.hpp>
#include <cubos/engine/prelude.hpp>

namespace cubos::engine
{
/// @defgroup render-cascaded-shadow-maps-plugin Cascaded shadow maps
/// @ingroup render-plugins
/// @brief Creates and manages shadow maps for directional shadow casters.
///
/// ## Dependencies
/// - @ref render-camera-plugin
/// - @ref render-shadows-plugin
/// - @ref window-plugin

/// @brief Creates the shadow maps.
/// @ingroup render-cascaded-shadow-maps-plugin
CUBOS_ENGINE_API extern Tag createCascadedShadowMapsTag;

/// @brief Systems which draw to the cascaded shadow maps should be tagged with this.
/// @ingroup render-cascaded-shadow-maps-plugin
CUBOS_ENGINE_API extern Tag drawToCascadedShadowMapsTag;

/// @brief Plugin entry function.
/// @param cubos @b Cubos main class.
/// @ingroup render-cascaded-shadow-maps-plugin
CUBOS_ENGINE_API void cascadedShadowMapsPlugin(Cubos& cubos);
} // namespace cubos::engine
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/// @dir
/// @brief @ref render-cascaded-shadow-maps-rasterizer-plugin plugin directory.

/// @file
/// @brief Plugin entry point.
/// @ingroup render-cascaded-shadow-maps-rasterizer-plugin

#pragma once

#include <cubos/engine/api.hpp>
#include <cubos/engine/prelude.hpp>

namespace cubos::engine
{
/// @defgroup render-cascaded-shadow-maps-rasterizer-plugin Cascaded shadow maps rasterizer
/// @ingroup render-plugins
/// @brief Draws all render meshes for each directional light to its shadow map.
///
/// ## Dependencies
/// - @ref window-plugin
/// - @ref assets-plugin
/// - @ref transform-plugin
/// - @ref lights-plugin
/// - @ref render-cascaded-shadow-maps-plugin
/// - @ref render-shadows-plugin
/// - @ref render-mesh-plugin
/// - @ref render-voxels-plugin
/// - @ref render-camera-plugin
/// - @ref render-g-buffer-plugin

/// @brief Plugin entry function.
/// @param cubos @b Cubos main class.
/// @ingroup render-cascaded-shadow-maps-rasterizer-plugin
CUBOS_ENGINE_API void cascadedShadowMapsRasterizerPlugin(Cubos& cubos);
} // namespace cubos::engine
Loading
Loading