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 point light shadows #1366

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the option to use Shadow Normal Offset Bias algorithm (#1308, **@GalaxyCrush**)
- UI text element using MSDF for text rendering (#1300, **@mkuritsu**).
- Added anti-aliasing using FXAA technique (#1334, **@kuukitenshi**).
- Point light shadows (#1188, **@tomas7770**).

### Changed

Expand Down
5 changes: 5 additions & 0 deletions core/include/cubos/core/geom/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ namespace cubos::core::geom
/// @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);

/// @brief Gets view matrices for rendering a cubemap.
/// @param inverseView Matrix that transforms the camera's view space to world space.
/// @param cubeViewMatrices Output vector where the view matrices will be stored.
CUBOS_CORE_API void getCubeViewMatrices(const glm::mat4& inverseView, std::vector<glm::mat4>& cubeViewMatrices);
} // namespace cubos::core::geom
16 changes: 16 additions & 0 deletions core/src/geom/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <limits>

#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/mat3x3.hpp>

#include <cubos/core/geom/utils.hpp>
Expand Down Expand Up @@ -191,3 +192,18 @@
}
}
}

void cubos::core::geom::getCubeViewMatrices(const glm::mat4& inverseView, std::vector<glm::mat4>& cubeViewMatrices)

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

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L196

Added line #L196 was not covered by tests
{
static const float ViewAngle[6] = {0.0F, 180.0F, 90.0F, -90.0F, 90.0F, -90.0F};
static const glm::vec3 ViewAxis[6] = {glm::vec3(0.0F, 1.0F, 0.0F), glm::vec3(0.0F, 1.0F, 0.0F),
glm::vec3(0.0F, 1.0F, 0.0F), glm::vec3(0.0F, 1.0F, 0.0F),
glm::vec3(1.0F, 0.0F, 0.0F), glm::vec3(1.0F, 0.0, 0.0F)};

cubeViewMatrices.resize(6);

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

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L203

Added line #L203 was not covered by tests

for (size_t i = 0; i < 6; i++)
{
cubeViewMatrices[i] = glm::inverse(glm::rotate(inverseView, glm::radians(ViewAngle[i]), ViewAxis[i]));

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

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L207

Added line #L207 was not covered by tests
tomas7770 marked this conversation as resolved.
Show resolved Hide resolved
}
}

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

View check run for this annotation

Codecov / codecov/patch

core/src/geom/utils.cpp#L209

Added line #L209 was not covered by tests
23 changes: 12 additions & 11 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,18 @@ set(CUBOS_ENGINE_SOURCE
"src/render/split_screen/split_screen.cpp"
"src/render/bloom/plugin.cpp"
"src/render/bloom/bloom.cpp"
"src/render/shadows/plugin.cpp"
"src/render/shadows/caster.cpp"
"src/render/shadows/spot_caster.cpp"
"src/render/shadows/directional_caster.cpp"
"src/render/shadows/point_caster.cpp"
"src/render/shadow_atlas/plugin.cpp"
"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/render/shadows/casters/plugin.cpp"
"src/render/shadows/casters/caster.cpp"
"src/render/shadows/casters/spot_caster.cpp"
"src/render/shadows/casters/directional_caster.cpp"
"src/render/shadows/casters/point_caster.cpp"
"src/render/shadows/atlas/plugin.cpp"
"src/render/shadows/atlas/spot_atlas.cpp"
"src/render/shadows/atlas/point_atlas.cpp"
"src/render/shadows/atlas_rasterizer/plugin.cpp"
"src/render/shadows/atlas_rasterizer/atlas_rasterizer.cpp"
"src/render/shadows/cascaded/plugin.cpp"
"src/render/shadows/cascaded_rasterizer/plugin.cpp"

"src/tools/settings_inspector/plugin.cpp"
"src/tools/selection/plugin.cpp"
Expand Down
82 changes: 77 additions & 5 deletions engine/assets/render/deferred_shading.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ uniform sampler2D positionTexture;
uniform sampler2D normalTexture;
uniform sampler2D albedoTexture;
uniform sampler2D ssaoTexture;
uniform sampler2D shadowAtlasTexture;
uniform sampler2D spotShadowAtlasTexture;
uniform sampler2DArray pointShadowAtlasTexture;

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

Expand All @@ -32,8 +33,14 @@ struct PointLight
{
vec4 position;
vec4 color;
mat4 matrices[6];
float intensity;
float range;
float shadowBias;
float shadowBlurRadius;
vec2 shadowMapOffset;
vec2 shadowMapSize;
float normalOffsetScale;
};

struct SpotLight
Expand Down Expand Up @@ -65,7 +72,7 @@ layout(std140) uniform PerScene

// Lights data.
DirectionalLight directionalLights[16];
PointLight pointLights[128];
PointLight pointLights[64];
SpotLight spotLights[128];

uint numDirectionalLights;
Expand Down Expand Up @@ -111,7 +118,7 @@ vec3 spotLightCalc(vec3 fragPos, vec3 fragNormal, uint lightI)
// PCF
if (spotLights[lightI].shadowBlurRadius <= 0.001f)
{
float pcfDepth = texture(shadowAtlasTexture, uv).r;
float pcfDepth = texture(spotShadowAtlasTexture, uv).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
else
Expand All @@ -123,7 +130,7 @@ vec3 spotLightCalc(vec3 fragPos, vec3 fragNormal, uint lightI)
{
float x = spotLights[lightI].shadowBlurRadius*float(xi);
float y = spotLights[lightI].shadowBlurRadius*float(yi);
float pcfDepth = texture(shadowAtlasTexture, uv + vec2(x, y) * texelSize).r;
float pcfDepth = texture(spotShadowAtlasTexture, uv + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
Expand Down Expand Up @@ -212,12 +219,77 @@ vec3 directionalLightCalc(vec3 fragPos, vec3 fragNormal, uint lightI, bool drawS
vec3 pointLightCalc(vec3 fragPos, vec3 fragNormal, uint lightI)
{
vec3 toLight = vec3(pointLights[lightI].position) - fragPos;
// Shadows
float shadow = 0.0;
if (pointLights[lightI].shadowMapSize.x > 0.0)
{
// Select cube face
int face = 0;
float rx = -toLight.x;
float ry = -toLight.y;
float rz = -toLight.z;
if (abs(rz) >= abs(rx) && abs(rz) >= abs(ry))
{
if (rz <= 0) // z-
face = 0;
else // z+
face = 1;
}
else if (abs(rx) >= abs(ry) && abs(rx) >= abs(rz))
{
if (rx <= 0) // x-
face = 2;
else // x+
face = 3;
}
else if (abs(ry) >= abs(rx) && abs(ry) >= abs(rz))
{
if (ry <= 0) // y-
face = 5;
else // y+
face = 4;
}

float normalOffsetScale = pointLights[lightI].normalOffsetScale;
vec3 offsetFragPos = normalOffsetScale > 0.0 ? applyNormalOffset(fragNormal, toLight, fragPos, normalOffsetScale) : fragPos;
vec4 positionLightSpace = pointLights[lightI].matrices[face] * vec4(offsetFragPos, 1.0);
vec3 projCoords = positionLightSpace.xyz / positionLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
vec2 uv = projCoords.xy * pointLights[lightI].shadowMapSize + pointLights[lightI].shadowMapOffset;
float currentDepth = projCoords.z;
float bias = pointLights[lightI].shadowBias / positionLightSpace.w; // make the bias not depend on near/far planes
// PCF
if (pointLights[lightI].shadowBlurRadius <= 0.001f)
{
float pcfDepth = texture(pointShadowAtlasTexture, vec3(uv.xy, face)).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
else
{
vec2 texelSize = vec2(1.0 / 1024.0); // largely arbitrary value, affects blur size
for(int xi = -1; xi <= 1; xi++)
{
for(int yi = -1; yi <= 1; yi++)
{
float x = pointLights[lightI].shadowBlurRadius*float(xi);
float y = pointLights[lightI].shadowBlurRadius*float(yi);
vec2 newUv = uv + vec2(x, y) * texelSize;
float pcfDepth = texture(pointShadowAtlasTexture, vec3(newUv.xy, face)).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
}
}

// Lighting
float r = length(toLight) / pointLights[lightI].range;
if (r < 1)
{
float attenuation = clamp(1.0 / (1.0 + 25.0 * r * r) * clamp((1 - r) * 5.0, 0, 1), 0, 1);
float diffuse = max(dot(fragNormal, vec3(normalize(toLight))), 0);
return attenuation * diffuse * pointLights[lightI].intensity * vec3(pointLights[lightI].color);
return attenuation * diffuse * (1.0 - shadow) * pointLights[lightI].intensity
* vec3(pointLights[lightI].color);
}
return vec3(0);
}
Expand Down
23 changes: 23 additions & 0 deletions engine/assets/render/shadow_cube_atlas_rasterizer.gs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#version 330 core

layout(triangles) in;
layout(triangle_strip, max_vertices = 18) out; // max_vertices = 3*6 (6 faces in a cube)

layout(std140) uniform PerScene
{
mat4 lightViewProj[6]; // 6 faces in a cube
};

void main()
{
for (int j = 0; j < 6; 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();
}
}
3 changes: 3 additions & 0 deletions engine/assets/render/shadow_cube_atlas_rasterizer.gs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "e0c5f304-fccf-496e-b181-08a3007f15b0"
}
13 changes: 13 additions & 0 deletions engine/assets/render/shadow_cube_atlas_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);
}
3 changes: 3 additions & 0 deletions engine/assets/render/shadow_cube_atlas_rasterizer.vs.meta
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "b9ac4697-c0d3-4e2d-9607-3da50f071d2d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@
namespace cubos::engine
{
/// @defgroup render-shadow-atlas-plugin Shadow atlas
/// @ingroup render-plugins
/// @brief Creates and manages a shadow map atlas.
/// @ingroup render-shadows-plugins
/// @brief Creates and manages shadow map atlases.
///
/// ## Dependencies
/// - @ref render-shadows-plugin
/// - @ref render-shadow-casters-plugin
/// - @ref window-plugin

/// @brief Creates the shadow atlas.
/// @brief Creates the shadow atlases.
/// @ingroup render-shadow-atlas-plugin
CUBOS_ENGINE_API extern Tag createShadowAtlasTag;

/// @brief Reserves space for shadow casters.
/// @ingroup render-shadow-atlas-plugin
CUBOS_ENGINE_API extern Tag reserveShadowCastersTag;

/// @brief Systems which draw to the shadow atlas texture should be tagged with this.
/// @brief Systems which draw to the shadow atlas textures should be tagged with this.
/// @ingroup render-shadow-atlas-plugin
CUBOS_ENGINE_API extern Tag drawToShadowAtlasTag;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/// @file
/// @brief Resource @ref cubos::engine::PointShadowAtlas.
/// @ingroup render-shadow-atlas-plugin

#pragma once

#include <map>

#include <glm/vec2.hpp>

#include <cubos/core/gl/render_device.hpp>
#include <cubos/core/reflection/reflect.hpp>

#include <cubos/engine/api.hpp>
#include <cubos/engine/render/shadows/atlas/slot.hpp>

namespace cubos::engine
{
/// @brief Resource which stores the shadow map atlas for point lights,
/// a large texture that holds the shadow maps for each shadow caster
/// in a quadtree structure, reducing texture switching.
/// @ingroup render-shadow-atlas-plugin
class CUBOS_ENGINE_API PointShadowAtlas
{
public:
CUBOS_REFLECT;

/// @brief Gets the size of the shadow atlas texture.
/// @return Size of the shadow atlas texture, in pixels.
glm::uvec2 getSize() const;

/// @brief Recreates the shadow atlas texture.
/// @param rd Render device used to create the texture.
void resize(cubos::core::gl::RenderDevice& rd);

/// @brief Configured size of the shadow atlas texture, in pixels.
/// Use this to change the resolution of the atlas. Note that the
/// texture isn't immediately resized; use @ref getSize() to get the
/// actual texture size.
glm::uvec2 configSize = {1024, 1024};

/// @brief Whether the shadow atlas texture has already been cleared this frame.
bool cleared = false;

/// @brief Stores shadow maps for each point shadow caster component.
/// Each texture of the array corresponds to a face of a cubemap.
core::gl::Texture2DArray atlas{nullptr};

/// @brief Stores the sizes, offsets, and caster ids of the shadow maps
/// in the atlas.
std::vector<std::shared_ptr<cubos::engine::ShadowMapSlot>> slots;

/// @brief Maps shadow caster ids to their corresponding slots.
std::map<int, std::shared_ptr<cubos::engine::ShadowMapSlot>> slotsMap;

private:
/// @brief Size of the shadow atlas texture, in pixels.
glm::uvec2 mSize = {0, 0};
};
} // namespace cubos::engine
29 changes: 29 additions & 0 deletions engine/include/cubos/engine/render/shadows/atlas/slot.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// @file
/// @brief Resource @ref cubos::engine::ShadowMapSlot.
/// @ingroup render-shadow-atlas-plugin

#pragma once

#include <glm/vec2.hpp>

#include <cubos/engine/api.hpp>

namespace cubos::engine
{
/// @brief Slot for a shadow map in the shadow atlas.
struct ShadowMapSlot
{
glm::vec2 size; ///< Shadow map size, in normalized coordinates.
glm::vec2 offset; ///< Shadow map offset, in normalized coordinates.
int casterId; ///< Id of the shadow caster (-1 if none).

/// @brief Constructs.
/// @param size Shadow map size, in normalized coordinates.
/// @param offset Shadow map offset, in normalized coordinates.
/// @param casterId Id of the shadow caster (-1 if none).
ShadowMapSlot(glm::vec2 size, glm::vec2 offset, int casterId)
: size(size)
, offset(offset)
, casterId(casterId) {};

Check warning on line 27 in engine/include/cubos/engine/render/shadows/atlas/slot.hpp

View check run for this annotation

Codecov / codecov/patch

engine/include/cubos/engine/render/shadows/atlas/slot.hpp#L24-L27

Added lines #L24 - L27 were not covered by tests
};
} // namespace cubos::engine
Loading
Loading