Skip to content

Commit

Permalink
Added shadow mapping for directional lights (#315)
Browse files Browse the repository at this point in the history
- Shadow casting and receiving is enabled on the `Default.ovmat` material by default.
- When a new material is created, these 2 settings are OFF by default.
- Directional lights (components) have a "Shadow casting" option that is OFF by default.
- Includes 2 implementations for shadows: hard-shadows, and soft-shadows/PCF (default).
- Introduces a `TextureHandle` class, which wraps an OpenGL texture ID, and can be passed as a uniform. This `TextureHandle` class is used by the framebuffer to expose its rendered image.
- `Texture` now inherits from `TextureHandle`.
- `ShadowRenderPass` added: handles the rendering of shadow casters in the shadow map
- `ShadowRenderFeature` added: ensures that the shadow map is properly bound to each entity set to receive shadows.
- Shadow area size is exposed through the `CDirectionaLight` component. Changing this setting will change the radius of the shadow (bigger radius = less precision)
- Shadow area size is exposed through the `CDirectionaLight` component.
- Shadow follow camera is a setting exposed through the `CDirectionaLight` component allowing the directional light to use the position of the camera as a source for its light space matrix (for shadow map rendering), making shadows "follow" the camera around, instead of being static.
- Added a `singleUse` parameter to `Material::Set`, for properties that shouldn't be stored after drawing (i.e. shadow maps texture handles)
  • Loading branch information
adriengivry authored Feb 7, 2025
1 parent 7408317 commit c4262a1
Show file tree
Hide file tree
Showing 43 changed files with 934 additions and 130 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ Here is a non-exhaustive list of Overload main features:

## 3.2. To implement
Again, a non-exhaustive list of Overload in-coming features:
- Shadow mapping
- Custom post-processing
- Renderer Hardware Interface (Multiple graphics backend support)
- More input device support (Gamepad)
- Prefab system
- Skeletal animation
Expand Down Expand Up @@ -130,7 +128,7 @@ Overload is licenced under an MIT licence.

## 4.6. More information
If you are interested in Overload, you can download our engine and the demo game we made with it on our website:<br>
http://overloadengine.org
https://overloadengine.org

Learn about Overload (Tutorials and Scripting API) by visiting our Wiki:<br>
https://github.com/adriengivry/Overload/wiki
Expand Down
2 changes: 2 additions & 0 deletions Resources/Engine/Materials/Default.ovmat
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<backface_culling>true</backface_culling>
<depth_test>true</depth_test>
<gpu_instances>1</gpu_instances>
<cast_shadows>1</cast_shadows>
<receive_shadows>1</receive_shadows>
</settings>
<uniforms>
<uniform>
Expand Down
18 changes: 14 additions & 4 deletions Resources/Engine/Shaders/Lighting/BlinnPhong.ovfxh
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,19 @@ vec3 ComputePointLight(mat4 light, vec3 fragPos, vec4 diffuseTexel, vec4 specula
return BlinnPhong(lightDirection, lightColor, intensity * luminosity, diffuseTexel, specularTexel, normal, viewDir, shininess);
}

vec3 ComputeDirectionalLight(mat4 light, vec4 diffuseTexel, vec4 specularTexel, vec3 normal, vec3 viewDir, float shininess)
vec3 ComputeDirectionalLight(mat4 light, vec3 fragPos, vec4 diffuseTexel, vec4 specularTexel, vec3 normal, vec3 viewDir, float shininess, sampler2D shadowMap, mat4 lightSpaceMatrix)
{
return BlinnPhong(-light[1].rgb, UnPack(light[2][0]), light[3][3], diffuseTexel, specularTexel, normal, viewDir, shininess);
vec3 lightDir = -light[1].rgb;
vec3 blinnPhong = BlinnPhong(lightDir, UnPack(light[2][0]), light[3][3], diffuseTexel, specularTexel, normal, viewDir, shininess);

if (light[2][1] > 0.0f)
{
vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);
float shadow = CalculateShadow(fragPosLightSpace, shadowMap, normal, lightDir);
blinnPhong *= 1.0 - shadow;
}

return blinnPhong;
}

vec3 ComputeSpotLight(mat4 light, vec3 fragPos, vec4 diffuseTexel, vec4 specularTexel, vec3 normal, vec3 viewDir, float shininess)
Expand Down Expand Up @@ -71,7 +81,7 @@ vec3 ComputeAmbientSphereLight(mat4 light, vec3 fragPos, vec4 diffuseTexel)
return IsPointInSphere(fragPos, lightPosition, radius) ? diffuseTexel.rgb * lightColor * intensity : vec3(0.0);
}

vec4 ComputeBlinnPhongLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 fragPos, vec4 diffuse, vec3 specular, sampler2D diffuseMap, sampler2D specularMap, float shininess)
vec4 ComputeBlinnPhongLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 fragPos, vec4 diffuse, vec3 specular, sampler2D diffuseMap, sampler2D specularMap, float shininess, sampler2D shadowMap, mat4 lightSpaceMatrix)
{
vec3 viewDir = normalize(viewPos - fragPos);
vec4 diffuseTexel = texture(diffuseMap, texCoords) * diffuse;
Expand All @@ -87,7 +97,7 @@ vec4 ComputeBlinnPhongLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 f
switch(lightType)
{
case 0: lightAccumulation += ComputePointLight(light, fragPos, diffuseTexel, specularTexel, normal, viewDir, shininess); break;
case 1: lightAccumulation += ComputeDirectionalLight(light, diffuseTexel, specularTexel, normal, viewDir, shininess); break;
case 1: lightAccumulation += ComputeDirectionalLight(light, fragPos, diffuseTexel, specularTexel, normal, viewDir, shininess, shadowMap, lightSpaceMatrix); break;
case 2: lightAccumulation += ComputeSpotLight(light, fragPos, diffuseTexel, specularTexel, normal, viewDir, shininess); break;
case 3: lightAccumulation += ComputeAmbientBoxLight(light, fragPos, diffuseTexel); break;
case 4: lightAccumulation += ComputeAmbientSphereLight(light, fragPos, diffuseTexel); break;
Expand Down
11 changes: 10 additions & 1 deletion Resources/Engine/Shaders/Lighting/PBR.ovfxh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ vec3 ComputeAmbientSphereLight(mat4 light, vec3 fragPos)
return IsPointInSphere(fragPos, lightPosition, radius) ? lightColor * intensity : vec3(0.0);
}

vec4 ComputePBRLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 fragPos, vec4 inAlbedo, float inMetallic, float inRoughness, sampler2D albedoMap, sampler2D metallicMap, sampler2D roughnessMap, sampler2D aoMap)
vec4 ComputePBRLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 fragPos, vec4 inAlbedo, float inMetallic, float inRoughness, sampler2D albedoMap, sampler2D metallicMap, sampler2D roughnessMap, sampler2D aoMap, sampler2D shadowMap, mat4 lightSpaceMatrix)
{
vec4 albedoRGBA = texture(albedoMap, texCoords) * inAlbedo;
vec3 albedo = pow(albedoRGBA.rgb, vec3(2.2));
Expand Down Expand Up @@ -110,6 +110,15 @@ vec4 ComputePBRLighting(vec2 texCoords, vec3 normal, vec3 viewPos, vec3 fragPos,

case 1:
lightCoeff = light[3][3];

if (light[2][1] > 0.0f)
{
const vec3 lightDir = light[1].rgb;
vec4 fragPosLightSpace = lightSpaceMatrix * vec4(fragPos, 1.0);
float shadow = CalculateShadow(fragPosLightSpace, shadowMap, normal, lightDir);
lightCoeff *= 1.0 - shadow;
}

break;

case 2:
Expand Down
63 changes: 63 additions & 0 deletions Resources/Engine/Shaders/Lighting/Shared.ovfxh
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,66 @@ float LuminosityFromAttenuation(mat4 light, vec3 fragPos)

return 1.0 / attenuation;
}

float SampleShadow(sampler2D shadowMap, vec3 projCoords, float bias)
{
float depth = texture(shadowMap, projCoords.xy).r;
return 1.0 - step(projCoords.z - bias, depth);
}

// Distance fade for shadows to appear smoothly
float CalculateShadowFalloff(vec3 projCoords, float intensity)
{
projCoords.z = clamp(projCoords.z, 0.5, 1.0); // Prevents falloff when the light source is close to a shadow caster
const float dist = (0.5 - clamp(distance(projCoords, vec3(0.5)), 0.0, 0.5)) * 2.0;
return 1.0 - pow(1.0 - dist, intensity);
}

// PCF (Percentage-Closer Filtering) shadows => AKA Soft Shadows
float CalculateSoftShadow(sampler2D shadowMap, vec3 projCoords, float bias, float texelSize)
{
const int range = 1;
const float invSamples = 1.0 / pow((range * 2 + 1), 2);

float shadow = 0.0;

for (int x = -range; x <= range; ++x)
{
for (int y = -range; y <= range; ++y)
{
const vec2 offset = vec2(x, y) * texelSize;
const vec3 offsettedProjCoords = vec3(projCoords.xy + offset, projCoords.z);
shadow += SampleShadow(shadowMap, offsettedProjCoords, bias);
}
}

return shadow * invSamples;
}

// Default shadow calculation
float CalculateHardShadow(sampler2D shadowMap, vec3 projCoords, float bias)
{
return SampleShadow(shadowMap, projCoords, bias);
}

float CalculateShadowBias(vec3 normal, vec3 lightDir, float texelSize)
{
const float bias = 0.001;
const float k = bias * (texelSize * 8096);
return max(k * (1.0 - dot(normal, lightDir)), k);
}

float CalculateShadow(vec4 fragPosLightSpace, sampler2D shadowMap, vec3 normal, vec3 lightDir)
{
const float texelSize = 1.0 / vec2(textureSize(shadowMap, 0)).x;
const vec3 projCoords = (fragPosLightSpace.xyz / fragPosLightSpace.w) * 0.5 + 0.5;

if (projCoords.z > 1.0) return 0.0;

float bias = CalculateShadowBias(normal, lightDir, texelSize);
float shadow = CalculateSoftShadow(shadowMap, projCoords, bias, texelSize);

shadow *= CalculateShadowFalloff(projCoords, 8);

return shadow;
}
20 changes: 20 additions & 0 deletions Resources/Engine/Shaders/Shadow.ovfx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#shader vertex
#version 430 core

layout (location = 0) in vec3 geo_Pos;

uniform mat4 _LightSpaceMatrix;
uniform mat4 _ModelMatrix;

void main()
{
gl_Position = _LightSpaceMatrix * _ModelMatrix * vec4(geo_Pos, 1.0);
}

#shader fragment
#version 430 core

void main()
{
gl_FragDepth = gl_FragCoord.z;
}
17 changes: 16 additions & 1 deletion Resources/Engine/Shaders/Standard.ovfx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ uniform sampler2D u_NormalMap;
uniform sampler2D u_HeightMap;
uniform sampler2D u_MaskMap;

uniform sampler2D _ShadowMap;
uniform mat4 _LightSpaceMatrix;

out vec4 FRAGMENT_COLOR;

void main()
Expand All @@ -71,7 +74,19 @@ void main()
if (!IsMasked(u_MaskMap, texCoords))
{
vec3 normal = ComputeNormal(u_EnableNormalMapping, texCoords, fs_in.Normal, u_NormalMap, fs_in.TBN);
FRAGMENT_COLOR = ComputeBlinnPhongLighting(texCoords, normal, ubo_ViewPos, fs_in.FragPos, u_Diffuse, u_Specular, u_DiffuseMap, u_SpecularMap, u_Shininess);
FRAGMENT_COLOR = ComputeBlinnPhongLighting(
texCoords,
normal,
ubo_ViewPos,
fs_in.FragPos,
u_Diffuse,
u_Specular,
u_DiffuseMap,
u_SpecularMap,
u_Shininess,
_ShadowMap,
_LightSpaceMatrix
);
}
else
{
Expand Down
5 changes: 4 additions & 1 deletion Resources/Engine/Shaders/StandardPBR.ovfx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ uniform float u_Roughness = 1.0;
uniform sampler2D u_HeightMap;
uniform sampler2D u_MaskMap;

uniform sampler2D _ShadowMap;
uniform mat4 _LightSpaceMatrix;

out vec4 FRAGMENT_COLOR;

void main()
Expand All @@ -73,7 +76,7 @@ void main()
if (!IsMasked(u_MaskMap, texCoords))
{
vec3 normal = ComputeNormal(u_EnableNormalMapping, texCoords, fs_in.Normal, u_NormalMap, fs_in.TBN);
FRAGMENT_COLOR = ComputePBRLighting(texCoords, normal, ubo_ViewPos, fs_in.FragPos, u_Albedo, u_Metallic, u_Roughness, u_AlbedoMap, u_MetallicMap, u_RoughnessMap, u_AmbientOcclusionMap);
FRAGMENT_COLOR = ComputePBRLighting(texCoords, normal, ubo_ViewPos, fs_in.FragPos, u_Albedo, u_Metallic, u_Roughness, u_AlbedoMap, u_MetallicMap, u_RoughnessMap, u_AmbientOcclusionMap, _ShadowMap, _LightSpaceMatrix);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,51 @@ namespace OvCore::ECS::Components
*/
std::string GetName() override;

/**
* Set if the light should cast shadows
* @param p_enabled
*/
void SetCastShadows(bool p_enabled);

/**
* Returns true if the light should cast shadows
*/
bool GetCastShadows() const;

/**
* Defines the area size of the shadow
* @param p_shadowAreaSize
*/
void SetShadowAreaSize(float p_shadowAreaSize);

/**
* Returns the area size of the shadow
*/
float GetShadowAreaSize() const;

/**
* Defines whether or not the light position should snap to the camera position
* @param p_enabled
*/
void SetShadowFollowCamera(bool p_enabled);

/**
* Returns true if the light position should snap to the camera position
*/
bool GetShadowFollowCamera() const;

/**
* Sets the shadow map resolution
* @note The resolution should be a power of 2 for better results
* @param p_resolution
*/
void SetShadowMapResolution(uint32_t p_resolution);

/**
* Returns the shadow map resolution
*/
uint32_t GetShadowMapResolution() const;

/**
* Serialize the component
* @param p_doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace OvCore::ECS::Components
/**
* Returns light data
*/
const OvRendering::Entities::Light& GetData() const;
OvRendering::Entities::Light& GetData();

/**
* Returns light color
Expand Down
3 changes: 3 additions & 0 deletions Sources/Overload/OvCore/include/OvCore/Helpers/Serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ namespace OvCore::Helpers
static void SerializeVec2(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvMaths::FVector2& p_value);
static void SerializeVec3(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvMaths::FVector3& p_value);
static void SerializeVec4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvMaths::FVector4& p_value);
static void SerializeMat4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvMaths::FMatrix4& p_value);
static void SerializeQuat(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvMaths::FQuaternion& p_value);
static void SerializeColor(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, const OvUI::Types::Color& p_value);
static void SerializeModel(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvRendering::Resources::Model* p_value);
Expand All @@ -72,6 +73,7 @@ namespace OvCore::Helpers
static void DeserializeVec2(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvMaths::FVector2& p_out);
static void DeserializeVec3(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvMaths::FVector3& p_out);
static void DeserializeVec4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvMaths::FVector4& p_out);
static void DeserializeMat4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvMaths::FMatrix4& p_out);
static void DeserializeQuat(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvMaths::FQuaternion& p_out);
static void DeserializeColor(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvUI::Types::Color& p_out);
static void DeserializeModel(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name, OvRendering::Resources::Model*& p_out);
Expand All @@ -90,6 +92,7 @@ namespace OvCore::Helpers
static OvMaths::FVector2 DeserializeVec2(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvMaths::FVector3 DeserializeVec3(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvMaths::FVector4 DeserializeVec4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvMaths::FMatrix4 DeserializeMat4(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvMaths::FQuaternion DeserializeQuat(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvUI::Types::Color DeserializeColor(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
static OvRendering::Resources::Model* DeserializeModel(tinyxml2::XMLDocument& p_doc, tinyxml2::XMLNode* p_node, const std::string& p_name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ namespace OvCore::Rendering
protected:
std::chrono::high_resolution_clock::time_point m_startTime;
std::unique_ptr<OvRendering::Buffers::UniformBuffer> m_engineBuffer;
OvRendering::Data::FrameDescriptor m_cachedFrameDescriptor;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* @project: Overload
* @author: Overload Tech.
* @licence: MIT
*/

#pragma once

#include <OvRendering/Entities/Camera.h>
#include <OvRendering/Features/DebugShapeRenderFeature.h>

#include <OvCore/ECS/Actor.h>
#include <OvCore/SceneSystem/SceneManager.h>
#include <OvCore/ECS/Components/CModelRenderer.h>
#include <OvCore/Resources/Material.h>
#include <OvCore/ECS/Components/CAmbientBoxLight.h>
#include <OvCore/ECS/Components/CAmbientSphereLight.h>
#include <OvCore/Rendering/SceneRenderer.h>

namespace OvCore::Rendering
{
/**
* Draw the scene for actor picking
*/
class ShadowRenderFeature : public OvRendering::Features::ARenderFeature
{
public:
/**
* Constructor
* @param p_renderer
*/
ShadowRenderFeature(OvRendering::Core::CompositeRenderer& p_renderer);

protected:
virtual void OnBeforeDraw(OvRendering::Data::PipelineState& p_pso, const OvRendering::Entities::Drawable& p_drawable);
virtual void OnAfterDraw(OvRendering::Data::PipelineState& p_pso, const OvRendering::Entities::Drawable& p_drawable);
};
}
Loading

0 comments on commit c4262a1

Please sign in to comment.