diff --git a/Engine/BuiltInShaders/UniformDefines/U_Light.sh b/Engine/BuiltInShaders/UniformDefines/U_Light.sh index 22009ebb..94824b79 100644 --- a/Engine/BuiltInShaders/UniformDefines/U_Light.sh +++ b/Engine/BuiltInShaders/UniformDefines/U_Light.sh @@ -7,10 +7,15 @@ #define RECTANGLE_LIGHT 5 #define TUBE_LIGHT 6 -#define LIGHT_LENGTH 320 +#define LIGHT_LENGTH 21 +#define LIGHT_TRANSFORM_LENGTH 12 +/* +LIGHT_LENGTH = num of lights(3) * num of total vec4 in one light(7) +LIGHT_TRANSFORM_LENGTH = num of lights(3) * max num of total mat4 of light transform for different kind of light(4) +*/ struct U_Light { - // vec4 * 5 + // vec4 * 7 float type; vec3 position; // 1 float intensity; @@ -23,4 +28,9 @@ struct U_Light { float height; float lightAngleScale; float lightAngleOffeset; // 5 + int shadowType; + int lightViewProjOffset; + int cascadeNum; + float shadowBias; // 6 + vec4 frustumClips; // 7 }; diff --git a/Engine/BuiltInShaders/UniformDefines/U_Shadow.sh b/Engine/BuiltInShaders/UniformDefines/U_Shadow.sh new file mode 100644 index 00000000..683b6ec6 --- /dev/null +++ b/Engine/BuiltInShaders/UniformDefines/U_Shadow.sh @@ -0,0 +1,3 @@ +#define SHADOW_MAP_CUBE_FIRST_SLOT 11 +#define SHADOW_MAP_CUBE_SECOND_SLOT 12 +#define SHADOW_MAP_CUBE_THIRD_SLOT 13 diff --git a/Engine/BuiltInShaders/common/LightSource.sh b/Engine/BuiltInShaders/common/LightSource.sh index 5856ebec..b2d7696d 100644 --- a/Engine/BuiltInShaders/common/LightSource.sh +++ b/Engine/BuiltInShaders/common/LightSource.sh @@ -5,9 +5,17 @@ //-----------------------------------------------------------------------------------------// #include "../UniformDefines/U_Light.sh" +#include "../UniformDefines/U_Shadow.sh" uniform vec4 u_lightCountAndStride; uniform vec4 u_lightParams[LIGHT_LENGTH]; +uniform mat4 u_lightViewProjs[4*3]; +uniform vec4 u_clipFrustumDepth; +uniform vec4 u_bias[3];//[LIGHT_NUM] + +SAMPLERCUBE(s_texCubeShadowMap_1, SHADOW_MAP_CUBE_FIRST_SLOT); +SAMPLERCUBE(s_texCubeShadowMap_2, SHADOW_MAP_CUBE_SECOND_SLOT); +SAMPLERCUBE(s_texCubeShadowMap_3, SHADOW_MAP_CUBE_THIRD_SLOT); U_Light GetLightParams(int pointer) { // struct { @@ -16,21 +24,28 @@ U_Light GetLightParams(int pointer) { // /*2*/ struct { float range; vec3 direction; }; // /*3*/ struct { float radius; vec3 up; }; // /*4*/ struct { float width, height, lightAngleScale, lightAngleOffeset; }; + // /*5*/ struct { int shadowType, lightViewProjOffset, cascadeNum; float shadowBias; }; + // /*6*/ struct { vec4 frustumClips; }; // } U_Light light; - light.type = u_lightParams[pointer + 0].x; - light.position = u_lightParams[pointer + 0].yzw; - light.intensity = u_lightParams[pointer + 1].x; - light.color = u_lightParams[pointer + 1].yzw; - light.range = u_lightParams[pointer + 2].x; - light.direction = u_lightParams[pointer + 2].yzw; - light.radius = u_lightParams[pointer + 3].x; - light.up = u_lightParams[pointer + 3].yzw; - light.width = u_lightParams[pointer + 4].x; - light.height = u_lightParams[pointer + 4].y; - light.lightAngleScale = u_lightParams[pointer + 4].z; - light.lightAngleOffeset = u_lightParams[pointer + 4].w; + light.type = u_lightParams[pointer + 0].x; + light.position = u_lightParams[pointer + 0].yzw; + light.intensity = u_lightParams[pointer + 1].x; + light.color = u_lightParams[pointer + 1].yzw; + light.range = u_lightParams[pointer + 2].x; + light.direction = u_lightParams[pointer + 2].yzw; + light.radius = u_lightParams[pointer + 3].x; + light.up = u_lightParams[pointer + 3].yzw; + light.width = u_lightParams[pointer + 4].x; + light.height = u_lightParams[pointer + 4].y; + light.lightAngleScale = u_lightParams[pointer + 4].z; + light.lightAngleOffeset = u_lightParams[pointer + 4].w; + light.shadowType = asint(u_lightParams[pointer + 5].x); + light.lightViewProjOffset = asint(u_lightParams[pointer + 5].y); + light.cascadeNum = asint(u_lightParams[pointer + 5].z); + light.shadowBias = u_lightParams[pointer + 5].z; + light.frustumClips = u_lightParams[pointer + 6]; return light; } @@ -98,9 +113,50 @@ vec3 closestPointOnSegment(vec3 a, vec3 b, vec3 c) { return a + saturate(t) * ab; } +// Return shadow map sampler according to light index +float textureIndex(int lightIndex, vec3 sampleVec){ + float closestDepth = 1.0; + if(0 == lightIndex){ + closestDepth = textureCube(s_texCubeShadowMap_1, sampleVec).r; + }else if(1 == lightIndex){ + closestDepth = textureCube(s_texCubeShadowMap_2, sampleVec).r; + }else if(2 == lightIndex){ + closestDepth = textureCube(s_texCubeShadowMap_3, sampleVec).r; + } + return closestDepth; +} + + // -------------------- Point -------------------- // -vec3 CalculatePointLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF) { +float CalculatePointShadow(vec3 fragPosWorldSpace, vec3 lightPosWorldSpace, float far_plane, int lightIndex) { + vec3 lightToFrag = fragPosWorldSpace - lightPosWorldSpace; + float currentDepth = length(lightToFrag); + float bias = 0.05; + + // PCF shadow | Filter Size : 3x3 + float shadow = 0.0; + float samples = 3.0; + float totalOffset = 0.03; + float stepOffset = totalOffset / ((samples-1) * 0.5); + for(float x = -totalOffset; x <= totalOffset; x += stepOffset) + { + for(float y = -totalOffset; y <= totalOffset; y += stepOffset) + { + for(float z = -totalOffset; z <= totalOffset; z += stepOffset) + { + float closestDepth = textureIndex(lightIndex, lightToFrag + vec3(x, y, z)); + closestDepth *= far_plane; + shadow += step(closestDepth, currentDepth - bias); + } + } + } + shadow /= (samples * samples * samples); + + return shadow; +} + +vec3 CalculatePointLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF, int lightIndex) { vec3 lightDir = normalize(light.position - worldPos); vec3 harfDir = normalize(lightDir + viewDir); @@ -119,12 +175,38 @@ vec3 CalculatePointLight(U_Light light, Material material, vec3 worldPos, vec3 v vec3 specularBRDF = Fre * NDF * Vis; vec3 KD = mix(1.0 - Fre, vec3_splat(0.0), material.metallic); - return (KD * diffuseBRDF + specularBRDF) * radiance * NdotL; + float shadow = CalculatePointShadow(worldPos, light.position, light.range, lightIndex); + return (1 - shadow) * (KD * diffuseBRDF + specularBRDF) * radiance * NdotL; } // -------------------- Spot -------------------- // -vec3 CalculateSpotLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF) { +float CalculateSpotShadow(vec3 fragPosWorldSpace, vec3 normal, vec3 lightDir,int lightViewProjOffset, int lightIndex){ + vec4 fragPosLightSpace = mul(u_lightViewProjs[lightViewProjOffset], vec4(fragPosWorldSpace, 1.0)); + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + vec3 sampleVec = vec3(1.0, projCoords.y, -projCoords.x); + float fragDepth = projCoords.z; + + // Calculate bias (based on depth map resolution and slope) + float bias = max(0.001 * (1.0 - dot(normal, lightDir)), 0.00001); + + // PCF Filter Size : 3x3 + float shadow = 0.0; + float samples = 3.0; + float totalOffset = 0.0015; + float stepOffset = totalOffset / ((samples-1) * 0.5); + for(float x = -totalOffset; x <= totalOffset; x += stepOffset){ + for(float y = -totalOffset; y <= totalOffset; y += stepOffset){ + float closestDepth = textureIndex(lightIndex, sampleVec + vec3(0, y, x)); + shadow += step(closestDepth, fragDepth - bias); + } + } + shadow /= (samples * samples); + + return shadow; +} + +vec3 CalculateSpotLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF, int lightIndex) { vec3 lightDir = normalize(light.position - worldPos); vec3 harfDir = normalize(lightDir + viewDir); @@ -145,12 +227,63 @@ vec3 CalculateSpotLight(U_Light light, Material material, vec3 worldPos, vec3 vi vec3 specularBRDF = Fre * NDF * Vis; vec3 KD = mix(1.0 - Fre, vec3_splat(0.0), material.metallic); - return (KD * diffuseBRDF + specularBRDF) * radiance * NdotL; + float shadow = CalculateSpotShadow(worldPos, material.normal, lightDir, light.lightViewProjOffset, lightIndex); + return (1.0 - shadow) * (KD * diffuseBRDF + specularBRDF) * radiance * NdotL; } // -------------------- Directional -------------------- // -vec3 CalculateDirectionalLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF) { +float CalculateDirectionalShadow(vec3 fragPosWorldSpace, vec3 normal, vec3 lightDir, mat4 lightViewProj, int lightIndex, float num){ + vec4 fragPosLightSpace = mul(lightViewProj, vec4(fragPosWorldSpace, 1.0)); + vec3 projCoords = fragPosLightSpace.xyz/fragPosLightSpace.w; + float currentDepth = projCoords.z; + float bias = max(0.02 * (1.0 - dot(normal, lightDir)), 0.002); + + // PCF shadow | Filter Size : 3x3 + vec3 sampleVec; + if(num < 0.5) sampleVec = vec3(1.0, projCoords.y, -projCoords.x); + else if(num > 0.5 && num < 1.5) sampleVec = vec3(-1.0, projCoords.y, projCoords.x); + else if(num > 1.5 && num < 2.5) sampleVec = vec3(projCoords.x, 1.0, projCoords.y); + else if(num > 2.5 && num < 3.5) sampleVec = vec3(projCoords.x, -1.0, projCoords.y); + float shadow = 0.0; + float samples = 3.0; + float totalOffset = 0.0015; + float stepOffset = totalOffset / ((samples-1) * 0.5); + if(num < 1.5){ + for(float x = -totalOffset; x <= totalOffset; x += stepOffset){ + for(float y = -totalOffset; y <= totalOffset; y += stepOffset){ + float closestDepth = textureIndex(lightIndex, sampleVec + vec3(0, y, x)); + shadow += step(closestDepth, currentDepth - bias); + } + } + } + else{ + for(float x = -totalOffset; x <= totalOffset; x += stepOffset){ + for(float y = -totalOffset; y <= totalOffset; y += stepOffset){ + float closestDepth = textureIndex(lightIndex, sampleVec + vec3(x, 0, y)); + shadow += step(closestDepth, currentDepth - bias); + } + } + } + shadow /= (samples * samples); + + return shadow; +} + +float CalculateCascadedDirectionalShadow(vec3 fragPosWorldSpace, vec3 normal, vec3 lightDir, float csmDepth, int lightViewProjOffset, int lightIndex){ + if(csmDepth > 0 && csmDepth <= u_clipFrustumDepth.x) + return CalculateDirectionalShadow(fragPosWorldSpace, normal, lightDir, u_lightViewProjs[lightViewProjOffset], lightIndex, 0.0); + else if(csmDepth > u_clipFrustumDepth.x && csmDepth <= u_clipFrustumDepth.y) + return CalculateDirectionalShadow(fragPosWorldSpace, normal, lightDir, u_lightViewProjs[lightViewProjOffset+1], lightIndex, 1.0); + else if(csmDepth > u_clipFrustumDepth.y && csmDepth <= u_clipFrustumDepth.z) + return CalculateDirectionalShadow(fragPosWorldSpace, normal, lightDir, u_lightViewProjs[lightViewProjOffset+2], lightIndex, 2.0); + else if(csmDepth > u_clipFrustumDepth.z && csmDepth <= 1) + return CalculateDirectionalShadow(fragPosWorldSpace, normal, lightDir, u_lightViewProjs[lightViewProjOffset+3], lightIndex, 3.0); + else + return 1.0; +} + +vec3 CalculateDirectionalLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF, float csmDepth, int lightIndex) { // TODO : Remove this normalize in the future. vec3 lightDir = normalize(-light.direction); vec3 harfDir = normalize(lightDir + viewDir); @@ -167,7 +300,8 @@ vec3 CalculateDirectionalLight(U_Light light, Material material, vec3 worldPos, vec3 KD = mix(1.0 - Fre, vec3_splat(0.0), material.metallic); vec3 irradiance = light.color * light.intensity; - return (KD * diffuseBRDF + specularBRDF) * irradiance * NdotL; + float shadow = CalculateCascadedDirectionalShadow(worldPos, material.normal, lightDir, csmDepth, light.lightViewProjOffset, lightIndex); + return (1.0 - shadow) * (KD * diffuseBRDF + specularBRDF) * irradiance * NdotL; } // -------------------- Sphere -------------------- // @@ -505,19 +639,19 @@ vec3 CalculateTubeLight(U_Light light, Material material, vec3 worldPos, vec3 vi // -------------------- Calculate each light -------------------- // -vec3 CalculateLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF) { +vec3 CalculateLight(U_Light light, Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF, float csmDepth, int lightIndex) { vec3 color = vec3_splat(0.0); if (light.type == POINT_LIGHT) { - color = CalculatePointLight(light, material, worldPos, viewDir, diffuseBRDF); + color = CalculatePointLight(light, material, worldPos, viewDir, diffuseBRDF, lightIndex); } else if (light.type == SPOT_LIGHT) { - color = CalculateSpotLight(light, material, worldPos, viewDir, diffuseBRDF); + color = CalculateSpotLight(light, material, worldPos, viewDir, diffuseBRDF, lightIndex); } else if (light.type == DIRECTIONAL_LIGHT) { - color = CalculateDirectionalLight(light, material, worldPos, viewDir, diffuseBRDF); + color = CalculateDirectionalLight(light, material, worldPos, viewDir, diffuseBRDF, csmDepth, lightIndex); } else if (light.type == SPHERE_LIGHT) { @@ -542,12 +676,12 @@ vec3 CalculateLight(U_Light light, Material material, vec3 worldPos, vec3 viewDi return color; } -vec3 CalculateLights(Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF) { +vec3 CalculateLights(Material material, vec3 worldPos, vec3 viewDir, vec3 diffuseBRDF, float csmDepth) { vec3 color = vec3_splat(0.0); for(int lightIndex = 0; lightIndex < int(u_lightCountAndStride.x); ++lightIndex) { int pointer = int(lightIndex * u_lightCountAndStride.y); U_Light light = GetLightParams(pointer); - color += CalculateLight(light, material, worldPos, viewDir, diffuseBRDF); + color += CalculateLight(light, material, worldPos, viewDir, diffuseBRDF, csmDepth, lightIndex); } return color; } diff --git a/Engine/BuiltInShaders/shaders/fs_PBR.sc b/Engine/BuiltInShaders/shaders/fs_PBR.sc index 90a5d984..507c4f5b 100644 --- a/Engine/BuiltInShaders/shaders/fs_PBR.sc +++ b/Engine/BuiltInShaders/shaders/fs_PBR.sc @@ -1,4 +1,4 @@ -$input v_worldPos, v_normal, v_texcoord0, v_TBN +$input v_worldPos, v_normal, v_texcoord0, v_TBN, v_color0 #include "../common/common.sh" #include "../common/BRDF.sh" @@ -9,10 +9,11 @@ $input v_worldPos, v_normal, v_texcoord0, v_TBN #include "../common/Envirnoment.sh" uniform vec4 u_emissiveColorAndFactor; +uniform vec4 u_cameraNearFarPlane; -vec3 GetDirectional(Material material, vec3 worldPos, vec3 viewDir) { +vec3 GetDirectional(Material material, vec3 worldPos, vec3 viewDir, float csmDepth) { vec3 diffuseBRDF = material.albedo * CD_INV_PI; - return CalculateLights(material, worldPos, viewDir, diffuseBRDF); + return CalculateLights(material, worldPos, viewDir, diffuseBRDF, csmDepth); } vec3 GetEnvironment(Material material, vec3 worldPos, vec3 viewDir, vec3 normal) { @@ -29,8 +30,9 @@ void main() vec3 cameraPos = GetCamera().position.xyz; vec3 viewDir = normalize(cameraPos - v_worldPos); - // Directional Light - vec3 dirColor = GetDirectional(material, v_worldPos, viewDir); + // Directional Light + float csmDepth = (v_color0.z - u_cameraNearFarPlane.x)/(u_cameraNearFarPlane.y - u_cameraNearFarPlane.x); + vec3 dirColor = GetDirectional(material, v_worldPos, viewDir, csmDepth); // Environment Light vec3 envColor = GetEnvironment(material, v_worldPos, viewDir, v_normal); diff --git a/Engine/BuiltInShaders/shaders/fs_shadowMap.sc b/Engine/BuiltInShaders/shaders/fs_shadowMap.sc new file mode 100644 index 00000000..b38e4784 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_shadowMap.sc @@ -0,0 +1,8 @@ +$input v_worldPos + +#include "../common/common.sh" + +void main() +{ + +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/fs_shadowMap_linear.sc b/Engine/BuiltInShaders/shaders/fs_shadowMap_linear.sc new file mode 100644 index 00000000..04bfa194 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_shadowMap_linear.sc @@ -0,0 +1,11 @@ +$input v_worldPos + +#include "../common/common.sh" + +uniform vec4 u_lightWorldPos_farPlane; + +void main() +{ + float diatance = length(v_worldPos - u_lightWorldPos_farPlane.xyz); + gl_FragColor= vec4(diatance/u_lightWorldPos_farPlane.w, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/vs_PBR.sc b/Engine/BuiltInShaders/shaders/vs_PBR.sc index 5105462f..3e903c7b 100644 --- a/Engine/BuiltInShaders/shaders/vs_PBR.sc +++ b/Engine/BuiltInShaders/shaders/vs_PBR.sc @@ -1,13 +1,13 @@ $input a_position, a_normal, a_tangent, a_texcoord0 -$output v_worldPos, v_normal, v_texcoord0, v_TBN +$output v_worldPos, v_normal, v_texcoord0, v_TBN, v_color0 #include "../common/common.sh" void main() { gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_worldPos = mul(u_model[0], vec4(a_position, 1.0)).xyz; + v_color0 = mul(u_modelView, vec4(a_position, 1.0)); v_normal = normalize(mul(u_modelInvTrans, vec4(a_normal, 0.0)).xyz); vec3 tangent = normalize(mul(u_modelInvTrans, vec4(a_tangent, 0.0)).xyz); diff --git a/Engine/BuiltInShaders/shaders/vs_shadowMap.sc b/Engine/BuiltInShaders/shaders/vs_shadowMap.sc new file mode 100644 index 00000000..13579218 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/vs_shadowMap.sc @@ -0,0 +1,10 @@ +$input a_position +$output v_worldPos + +#include "../common/common.sh" + +void main() +{ + v_worldPos = mul(u_model[0], vec4(a_position, 1.0)).xyz; + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); +} diff --git a/Engine/Source/Editor/EditorApp.cpp b/Engine/Source/Editor/EditorApp.cpp index d405a717..5e93072a 100644 --- a/Engine/Source/Editor/EditorApp.cpp +++ b/Engine/Source/Editor/EditorApp.cpp @@ -28,6 +28,7 @@ #include "Rendering/SkeletonRenderer.h" #include "Rendering/SkyboxRenderer.h" #include "Rendering/ShaderCollections.h" +#include "Rendering/ShadowMapRenderer.h" #include "Rendering/TerrainRenderer.h" #include "Rendering/WorldRenderer.h" #include "Rendering/ParticleRenderer.h" @@ -496,6 +497,11 @@ void EditorApp::InitEngineRenderers() // The init size doesn't make sense. It will resize by SceneView. engine::RenderTarget* pSceneRenderTarget = m_pRenderContext->CreateRenderTarget(sceneViewRenderTargetName, 1, 1, std::move(attachmentDesc)); + auto pShadowMapRenderer = std::make_unique(m_pRenderContext->CreateView(), pSceneRenderTarget); + m_pShadowMapRenderer = pShadowMapRenderer.get(); + pShadowMapRenderer->SetSceneWorld(m_pSceneWorld.get()); + AddEngineRenderer(cd::MoveTemp(pShadowMapRenderer)); + auto pSkyboxRenderer = std::make_unique(m_pRenderContext->CreateView(), pSceneRenderTarget); m_pIBLSkyRenderer = pSkyboxRenderer.get(); pSkyboxRenderer->SetSceneWorld(m_pSceneWorld.get()); diff --git a/Engine/Source/Editor/EditorApp.h b/Engine/Source/Editor/EditorApp.h index b9cc3ec2..03bb1cef 100644 --- a/Engine/Source/Editor/EditorApp.h +++ b/Engine/Source/Editor/EditorApp.h @@ -115,6 +115,7 @@ class EditorApp final : public engine::IApplication engine::Renderer* m_pIBLSkyRenderer = nullptr; engine::Renderer* m_pTerrainRenderer = nullptr; engine::Renderer* m_pAABBRenderer = nullptr; + engine::Renderer* m_pShadowMapRenderer = nullptr; // Rendering std::unique_ptr m_pRenderContext; diff --git a/Engine/Source/Editor/UILayers/EntityList.cpp b/Engine/Source/Editor/UILayers/EntityList.cpp index 38406316..93a5edfd 100644 --- a/Engine/Source/Editor/UILayers/EntityList.cpp +++ b/Engine/Source/Editor/UILayers/EntityList.cpp @@ -71,12 +71,15 @@ void EntityList::AddEntity(engine::SceneWorld* pSceneWorld) transformComponent.Build(); }; - auto CreateLightComponents = [&pWorld](engine::Entity entity, cd::LightType lightType, float intensity, cd::Vec3f color) -> engine::LightComponent& + auto CreateLightComponents = [&pWorld](engine::Entity entity, cd::LightType lightType, float intensity, cd::Vec3f color, bool isCastShadow = false) -> engine::LightComponent& { auto& lightComponent = pWorld->CreateComponent(entity); lightComponent.SetType(lightType); lightComponent.SetIntensity(intensity); lightComponent.SetColor(color); + lightComponent.SetShadowMapSize(1024U); + lightComponent.SetIsCastShadow(isCastShadow); + lightComponent.SetShadowBias(0.0f); auto& transformComponent = pWorld->CreateComponent(entity); transformComponent.SetTransform(cd::Transform::Identity()); @@ -158,24 +161,29 @@ void EntityList::AddEntity(engine::SceneWorld* pSceneWorld) else if (ImGui::MenuItem("Add Point Light")) { engine::Entity entity = AddNamedEntity("PointLight"); - auto& lightComponent = CreateLightComponents(entity, cd::LightType::Point, 1024.0f, cd::Vec3f(1.0f, 0.0f, 0.0f)); + auto& lightComponent = CreateLightComponents(entity, cd::LightType::Point, 1024.0f, cd::Vec3f(1.0f, 0.0f, 0.0f), true); lightComponent.SetPosition(cd::Point(0.0f, 0.0f, -16.0f)); lightComponent.SetRange(1024.0f); + lightComponent.SetShadowMapTexture(BGFX_INVALID_HANDLE); } else if (ImGui::MenuItem("Add Spot Light")) { engine::Entity entity = AddNamedEntity("SpotLight"); - auto& lightComponent = CreateLightComponents(entity, cd::LightType::Spot, 1024.0f, cd::Vec3f(0.0f, 1.0f, 0.0f)); + auto& lightComponent = CreateLightComponents(entity, cd::LightType::Spot, 1024.0f, cd::Vec3f(0.0f, 1.0f, 0.0f), true); lightComponent.SetPosition(cd::Point(0.0f, 0.0f, -16.0f)); lightComponent.SetDirection(cd::Direction(0.0f, 0.0f, 1.0f)); lightComponent.SetRange(1024.0f); lightComponent.SetInnerAndOuter(24.0f, 40.0f); + lightComponent.SetShadowMapTexture(BGFX_INVALID_HANDLE); } else if (ImGui::MenuItem("Add Directional Light")) { engine::Entity entity = AddNamedEntity("DirectionalLight"); - auto& lightComponent = CreateLightComponents(entity, cd::LightType::Directional, 4.0f, cd::Vec3f(1.0f, 1.0f, 1.0f)); + auto& lightComponent = CreateLightComponents(entity, cd::LightType::Directional, 4.0f, cd::Vec3f(1.0f, 1.0f, 1.0f), true); lightComponent.SetDirection(cd::Direction(0.0f, 0.0f, 1.0f)); + lightComponent.SetCascadeNum(4); + lightComponent.SetFrustumClips(cd::Vec4f(0.0f, 0.0f, 0.0f, 0.0f)); + lightComponent.SetShadowMapTexture(BGFX_INVALID_HANDLE); } // ---------------------------------------- Add Area Light ---------------------------------------- // diff --git a/Engine/Source/Editor/UILayers/Inspector.cpp b/Engine/Source/Editor/UILayers/Inspector.cpp index fffd9947..613f6225 100644 --- a/Engine/Source/Editor/UILayers/Inspector.cpp +++ b/Engine/Source/Editor/UILayers/Inspector.cpp @@ -438,6 +438,7 @@ void UpdateComponentWidget(engine::SceneWorld* pSceneWor std::string lightTypeName(nameof::nameof_enum(lightType)); ImGuiUtils::ImGuiStringProperty("Type", lightTypeName); + ImGuiUtils::ImGuiBoolProperty("Cast Shadow", pLightComponent->IsCastShadow()); ImGuiUtils::ColorPickerProperty("Color", pLightComponent->GetColor()); float s_spotInnerAngle = 8.0f; @@ -467,8 +468,8 @@ void UpdateComponentWidget(engine::SceneWorld* pSceneWor s_spotInnerAngle = innerAndOuter.x(); s_spotOuterAngle = innerAndOuter.y(); - spotInnerDirty = ImGuiUtils::ImGuiFloatProperty("InnerAngle", s_spotInnerAngle, cd::Unit::Degree, 0.1f, 90.0f); - spotOuterDirty = ImGuiUtils::ImGuiFloatProperty("OuterAngle", s_spotOuterAngle, cd::Unit::Degree, 0.1f, 90.0f); + spotInnerDirty = ImGuiUtils::ImGuiFloatProperty("Inner Angle", s_spotInnerAngle, cd::Unit::Degree, 0.1f, 90.0f); + spotOuterDirty = ImGuiUtils::ImGuiFloatProperty("Outer Angle", s_spotOuterAngle, cd::Unit::Degree, 0.1f, 90.0f); if (spotInnerDirty || spotOuterDirty) { pLightComponent->SetInnerAndOuter(s_spotInnerAngle, s_spotOuterAngle); diff --git a/Engine/Source/Runtime/ECWorld/LightComponent.cpp b/Engine/Source/Runtime/ECWorld/LightComponent.cpp index 85bca003..2278ea5b 100644 --- a/Engine/Source/Runtime/ECWorld/LightComponent.cpp +++ b/Engine/Source/Runtime/ECWorld/LightComponent.cpp @@ -1,5 +1,7 @@ #include "LightComponent.h" +#include + namespace engine { @@ -26,4 +28,38 @@ void LightComponent::SetInnerAndOuter(float inner, float outer) m_lightUniformData.lightAngleOffeset = -outerCos * scale; } +bool LightComponent::IsShadowMapFBsValid() +{ + // Include empty FBs + if ((cd::LightType::Spot == GetType() && m_shadowMapFBs.size() != 1U) || + (cd::LightType::Point == GetType() && m_shadowMapFBs.size() != 6U) || + (cd::LightType::Directional == GetType() && m_shadowMapFBs.size() != GetCascadeNum())) + { + return false; + } + + for (const auto& fb : m_shadowMapFBs) + { + if (!bgfx::isValid(static_cast(fb))) return false; + } + return true; +} + +void LightComponent::ClearShadowMapTexture() +{ + bgfx::destroy(static_cast(m_shadowMapTexture)); + m_shadowMapTexture = BGFX_INVALID_HANDLE; +} + +void LightComponent::ClearShadowMapFBs() +{ + for (auto shadowMapFB : m_shadowMapFBs) bgfx::destroy(static_cast(shadowMapFB)); + m_shadowMapFBs.clear(); +} + +bool LightComponent::IsShadowMapTextureValid() +{ + return bgfx::isValid(static_cast(m_shadowMapTexture)); +} + } diff --git a/Engine/Source/Runtime/ECWorld/LightComponent.h b/Engine/Source/Runtime/ECWorld/LightComponent.h index 52e90caf..59651203 100644 --- a/Engine/Source/Runtime/ECWorld/LightComponent.h +++ b/Engine/Source/Runtime/ECWorld/LightComponent.h @@ -7,6 +7,15 @@ namespace engine { +enum class CascadePartitionMode +{ + Manual, + Logarithmic, + PSSM, + + Count +}; + class LightComponent final { public: @@ -76,8 +85,79 @@ class LightComponent final cd::Direction& GetUp() { return m_lightUniformData.up; } const cd::Direction& GetUp() const { return m_lightUniformData.up; } + void SetShadowType(int shadowType) { m_lightUniformData.shadowType = shadowType; } + int& GetShadowType() { return m_lightUniformData.shadowType; } + int GetShadowType() const { return m_lightUniformData.shadowType; } + + void SetLightViewProjOffset(int lightViewProjOffset) { m_lightUniformData.lightViewProjOffset = lightViewProjOffset; } + int GetLightViewProjOffset() const { return m_lightUniformData.lightViewProjOffset; } + + void SetCascadeNum(int cascadeNum) { m_lightUniformData.cascadeNum = cascadeNum; } + int& GetCascadeNum() { return m_lightUniformData.cascadeNum; } + int GetCascadeNum() const { return m_lightUniformData.cascadeNum; } + + void SetShadowBias(float shadowBias) { m_lightUniformData.shadowBias = shadowBias; } + float& GetShadowBias() { return m_lightUniformData.shadowBias; } + float GetShadowBias() const { return m_lightUniformData.shadowBias; } + + void SetFrustumClips(cd::Vec4f frustumClips) { m_lightUniformData.frustumClips = frustumClips; } + cd::Vec4f& GetFrustumClips() { return m_lightUniformData.frustumClips; } + cd::Vec4f GetFrustumClips() const { return m_lightUniformData.frustumClips; } + + U_Light* GetLightUniformData() { return &m_lightUniformData; } + + void SetIsCastShadow(bool isCastShadow) { m_isCastShadow = isCastShadow; }; + bool& IsCastShadow() { return m_isCastShadow; } + bool IsCastShadow() const { return m_isCastShadow; } + + bool& GetIsCastVolume() { return m_isCastVolume; } + bool IsCastVolume() const { return m_isCastVolume; } + + void SetShadowMapSize(uint16_t shadowMapSize) { m_shadowMapSize = shadowMapSize; } + uint16_t& GetShadowMapSize() { return m_shadowMapSize; } + uint16_t GetShadowMapSize() const { return m_shadowMapSize; } + + void SetCascadePartitionMode(CascadePartitionMode cascadePartitionMode) { m_cascadePartitionMode = cascadePartitionMode; } + CascadePartitionMode& GetCascadePartitionMode() { return m_cascadePartitionMode; } + CascadePartitionMode GetCascadePartitionMode() const { return m_cascadePartitionMode; } + + void SetComputedCascadeSplit(float* cascadeSplit) { std::memcpy(&m_computedCascadeSplit[0], cascadeSplit, 16); } + const float* GetComputedCascadeSplit() { return &m_computedCascadeSplit[0]; } + + float& GetManualCascadeSplitAt(uint16_t idx) { return m_manualCascadeSplit[idx]; } + const float* GetManualCascadeSplit() { return &m_manualCascadeSplit[0]; } + + void AddLightViewProjMatrix(cd::Matrix4x4 lightViewProjMatrix) { m_lightViewProjMatrices.push_back(lightViewProjMatrix); } + std::vector& GetLightViewProjMatrix() { return m_lightViewProjMatrices; } + const std::vector& GetLightViewProjMatrix() const { return m_lightViewProjMatrices; } + void ClearLightViewProjMatrix() { m_lightViewProjMatrices.clear(); } + + bool IsShadowMapFBsValid(); + void AddShadowMapFB(uint16_t& shadowMapFB) { m_shadowMapFBs.push_back(std::move(shadowMapFB)); } + const std::vector& GetShadowMapFBs() const { return m_shadowMapFBs; } + void ClearShadowMapFBs(); + + bool IsShadowMapTextureValid(); + void SetShadowMapTexture(uint16_t shadowMapTexture) { m_shadowMapTexture = shadowMapTexture; } + const uint16_t& GetShadowMapTexture() { return m_shadowMapTexture; } + void ClearShadowMapTexture(); + private: U_Light m_lightUniformData; + + bool m_isCastShadow; + bool m_isCastVolume; + uint16_t m_shadowMapSize; + + CascadePartitionMode m_cascadePartitionMode = CascadePartitionMode::PSSM; + float m_manualCascadeSplit[4] = { 0.0 }; // manual set split + float m_computedCascadeSplit[4] = { 0.0 }; // computed split + + // uniform + uint16_t m_shadowMapTexture; // Texture Handle + std::vector m_lightViewProjMatrices; + std::vector m_shadowMapFBs; // Framebuffer Handle + // Warning : We treat multiple light components as a complete and contiguous memory. // any non-U_Light member of LightComponent will destroy this layout. --2023/6/21 }; diff --git a/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp b/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp index 19c9144e..1604910e 100644 --- a/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp +++ b/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp @@ -276,28 +276,41 @@ static bool ImGuiTransformProperty(const char* pName, cd::Transform& value) return dirty; } -static void ColorPickerProperty(const char* Name, cd::Vec3f& veccolor) +template +static void ColorPickerProperty(const char* pName, T& color) { static std::map showMap; - if (!showMap.count(Name)) + if (!showMap.count(pName)) { - showMap[Name] = false; + showMap[pName] = false; } - ImGui::TextUnformatted(Name); + ImGui::TextUnformatted(pName); ImGui::SameLine(); ImGui::NextColumn(); - ImGui::PushID(Name); + ImGui::PushID(pName); if (ImGui::Button("...")) { - showMap[Name] = true; + showMap[pName] = true; } ImGui::PopID(); ImGui::PushItemWidth(-1); ImGui::SameLine(); ImGui::NextColumn(); - ImGui::DragFloat3("", veccolor.begin(), 0, 0.0f, 1.0f); + if constexpr (std::is_same()) + { + ImGui::DragFloat3("", color.begin(), 0, 0.0f, 1.0f); + } + else if constexpr (std::is_same()) + { + ImGui::DragFloat4("", color.begin(), 0, 0.0f, 1.0f); + } + else + { + static_assert("Unsupported color data type for ImGuiColorPickerProperty."); + } + ImGui::PopItemWidth(); - if (showMap[Name]) + if (showMap[pName]) { ImGuiIO& io = ImGui::GetIO(); ImVec2 mainWindowSize = io.DisplaySize; @@ -306,8 +319,15 @@ static void ColorPickerProperty(const char* Name, cd::Vec3f& veccolor) ImVec2 windowPos(mainWindowSize.x - offsetX, mainWindowSize.y - offsetY); ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always); - ImGui::Begin(Name, &showMap[Name], ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); - ImGui::ColorPicker3("Color Picker", veccolor.begin()); + ImGui::Begin(pName, &showMap[pName], ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); + if constexpr (std::is_same()) + { + ImGui::ColorPicker3("Color Picker", color.begin()); + } + else if constexpr (std::is_same()) + { + ImGui::ColorPicker4("Color Picker", color.begin()); + } ImGui::End(); } ImGui::Separator(); diff --git a/Engine/Source/Runtime/Rendering/Light.h b/Engine/Source/Runtime/Rendering/Light.h index a920580e..aaac0923 100644 --- a/Engine/Source/Runtime/Rendering/Light.h +++ b/Engine/Source/Runtime/Rendering/Light.h @@ -6,6 +6,7 @@ namespace engine { using vec3 = cd::Vec3f; +using vec4 = cd::Vec4f; #include "U_Light.sh" } diff --git a/Engine/Source/Runtime/Rendering/LightUniforms.h b/Engine/Source/Runtime/Rendering/LightUniforms.h index f3f98ee8..a3151553 100644 --- a/Engine/Source/Runtime/Rendering/LightUniforms.h +++ b/Engine/Source/Runtime/Rendering/LightUniforms.h @@ -13,7 +13,7 @@ class RenderContext; namespace { -constexpr uint16_t MAX_LIGHT_COUNT = 64; +constexpr uint16_t MAX_LIGHT_COUNT = 3; constexpr uint16_t ConstexprCeil(float x) { @@ -51,6 +51,8 @@ class LightUniform final { float range; vec3 direction; float radius; vec3 up; float width, height, lightAngleScale, lightAngleOffeset; + int shadowType, lightViewProjOffset, cascadeNum; float shadowBias; + vec4 frustumClips; }; LightParameters m_lightParameters[MAX_LIGHT_COUNT]; diff --git a/Engine/Source/Runtime/Rendering/ShadowMapRenderer.cpp b/Engine/Source/Runtime/Rendering/ShadowMapRenderer.cpp new file mode 100644 index 00000000..f6abd976 --- /dev/null +++ b/Engine/Source/Runtime/Rendering/ShadowMapRenderer.cpp @@ -0,0 +1,399 @@ +#include "ShadowMapRenderer.h" + +#include "ECWorld/CameraComponent.h" +#include "ECWorld/SceneWorld.h" +#include "ECWorld/StaticMeshComponent.h" +#include "ECWorld/TransformComponent.h" +#include "ECWorld/LightComponent.h" +#include "LightUniforms.h" +#include "Material/ShaderSchema.h" +#include "Math/Transform.hpp" +#include "RenderContext.h" + +#include + +namespace engine +{ + +namespace +{ +// uniform name +constexpr const char* cameraPos = "u_cameraPos"; +constexpr const char* lightCountAndStride = "u_lightCountAndStride"; +constexpr const char* lightParams = "u_lightParams"; +constexpr const char* lightPosAndFarPlane = "u_lightWorldPos_farPlane"; +constexpr const char* lightDir = "u_LightDir"; +constexpr const char* heightOffsetAndshadowLength = "u_HeightOffsetAndshadowLength"; + +// +constexpr uint64_t samplerFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP; +constexpr uint64_t defaultRenderingState = BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z +| BGFX_STATE_WRITE_MASK | BGFX_STATE_MSAA | BGFX_STATE_DEPTH_TEST_LESS; +constexpr uint64_t depthBufferFlags = BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL; +constexpr uint64_t linearDepthBufferFlags = BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + +} + +void ShadowMapRenderer::Init() +{ + constexpr StringCrc shadowMapProgramCrc = StringCrc("ShadowMapProgram"); + GetRenderContext()->RegisterShaderProgram(shadowMapProgramCrc, { "vs_shadowMap", "fs_shadowMap" }); + constexpr StringCrc linearshadowMapProgramCrc = StringCrc("LinearShadowMapProgram"); + GetRenderContext()->RegisterShaderProgram(linearshadowMapProgramCrc, { "vs_shadowMap", "fs_shadowMap_linear" }); + + for (int lightIndex = 0; lightIndex < shadowLightMaxNum; lightIndex++) + { + for (int mapId = 0; mapId < shadowTexturePassMaxNum; mapId++) + { + m_renderPassID[lightIndex * shadowTexturePassMaxNum + mapId] = GetRenderContext()->CreateView(); + bgfx::setViewName(m_renderPassID[lightIndex * shadowTexturePassMaxNum + mapId], "ShadowMapRenderer"); + } + } +} + +void ShadowMapRenderer::Warmup() +{ + GetRenderContext()->UploadShaderProgram("ShadowMapProgram"); + GetRenderContext()->UploadShaderProgram("LinearShadowMapProgram"); + GetRenderContext()->CreateUniform(lightPosAndFarPlane, bgfx::UniformType::Vec4, 1); +} + +void ShadowMapRenderer::UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) +{ + //UpdateViewRenderTarget(); //in Render() +} + +void ShadowMapRenderer::Render(float deltaTime) +{ + // TODO : Remove it. If every renderer need to submit camera related uniform, it should be done not inside Renderer class. + const cd::Transform& cameraTransform = m_pCurrentSceneWorld->GetTransformComponent(m_pCurrentSceneWorld->GetMainCameraEntity())->GetTransform(); + + // Submit uniform values : light settings + auto lightEntities = m_pCurrentSceneWorld->GetLightEntities(); + + if (!lightEntities.empty()) + { + // camera + CameraComponent* pMainCameraComponent = m_pCurrentSceneWorld->GetCameraComponent(m_pCurrentSceneWorld->GetMainCameraEntity()); + const cd::Matrix4x4 camView = pMainCameraComponent->GetViewMatrix(); + const cd::Matrix4x4 camProj = pMainCameraComponent->GetProjectionMatrix(); + const cd::Matrix4x4 invCamViewProj = (camProj * camView).Inverse(); + bool ndcDepthMinusOneToOne = cd::NDCDepth::MinusOneToOne == pMainCameraComponent->GetNDCDepth(); + + // lambda : unproject ndc sapce coordinates into world space + auto UnProject = [&invCamViewProj](const cd::Vec4f ndcCorner)->cd::Point + { + cd::Vec4f worldPos = invCamViewProj * ndcCorner; + return worldPos.xyz() / worldPos.w(); + }; + + uint16_t shadowNum = 0U; + for (auto lightEntity : lightEntities) + { + LightComponent* lightComponent = m_pCurrentSceneWorld->GetLightComponent(lightEntity); + + // Non-shadow-casting lights(include area lights) are excluded + if (!lightComponent->IsCastShadow()) + { + continue; + } + + // Render shadow map + switch (lightComponent->GetType()) + { + case cd::LightType::Directional: + { + uint16_t cascadeNum = lightComponent->GetCascadeNum(); + // Initialize(if not initialized) frame buffer + if (!lightComponent->IsShadowMapFBsValid()) + { + lightComponent->ClearShadowMapFBs(); + for (int i = 0; i < cascadeNum; ++i) + { + uint16_t shadowMapFB = bgfx::createFrameBuffer(lightComponent->GetShadowMapSize(), + lightComponent->GetShadowMapSize(), bgfx::TextureFormat::D32F).idx; + lightComponent->AddShadowMapFB(shadowMapFB); + } + } + + // Compute the split distances based on the partitioning mode + float CascadeSplits[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float MinDistance = 0.0f; // if this changed, UnProjection need changed + float MaxDistance = 1.0f; // if this changed, UnProjection need changed + + CascadePartitionMode cascadePatitionMode = lightComponent->GetCascadePartitionMode(); + if (CascadePartitionMode::Manual == cascadePatitionMode) + { + + } + else if (CascadePartitionMode::Logarithmic == cascadePatitionMode + || CascadePartitionMode::PSSM == cascadePatitionMode) + { + float lambda = 1.0f; + if (CascadePartitionMode::PSSM == cascadePatitionMode){} + lambda = 0.5;// TODO : user edit + + float nearClip = pMainCameraComponent->GetNearPlane(); + float farClip = pMainCameraComponent->GetFarPlane(); + float clipRange = farClip - nearClip; + + float minZ = nearClip + MinDistance * clipRange; + float maxZ = nearClip + MaxDistance * clipRange; + + float range = maxZ - minZ; + float ratio = maxZ / minZ; + + for (uint16_t i = 0; i < cascadeNum; ++i) + { + float p = (i + 1) / static_cast(cascadeNum); + float log = minZ * std::pow(ratio, p); + float uniform = minZ + range * p; + float d = lambda * (log - uniform) + uniform; + CascadeSplits[i] = (d - nearClip) / clipRange; + } + } + + // Compute the frustum according to ndc depth of different graphic backends + cd::Direction lightDirection = lightComponent->GetDirection(); + std::vector frustumCorners = ndcDepthMinusOneToOne ? + std::vector{ // Depth [-1, 1] + UnProject(cd::Vec4f(-1, -1, -1, 1)), UnProject(cd::Vec4f(-1, -1, 1, 1)), // lower-left near and far + UnProject(cd::Vec4f(-1, 1, -1, 1)), UnProject(cd::Vec4f(-1, 1, 1, 1)), // upper-left near and far + UnProject(cd::Vec4f( 1, -1, -1, 1)), UnProject(cd::Vec4f( 1, -1, 1, 1)), // lower-right near and far + UnProject(cd::Vec4f( 1, 1, -1, 1)), UnProject(cd::Vec4f( 1, 1, 1, 1)) // upper-right near and far + } : + std::vector{ // Depth [0, 1] + UnProject(cd::Vec4f(-1, -1, 0, 1)), UnProject(cd::Vec4f(-1, -1, 1, 1)), // lower-left near and far + UnProject(cd::Vec4f(-1, 1, 0, 1)), UnProject(cd::Vec4f(-1, 1, 1, 1)), // upper-left near and far + UnProject(cd::Vec4f( 1, -1, 0, 1)), UnProject(cd::Vec4f( 1, -1, 1, 1)), // lower-right near and far + UnProject(cd::Vec4f( 1, 1, 0, 1)), UnProject(cd::Vec4f( 1, 1, 1, 1)) // upper-right near and far + }; + + // Set cascade split dividing values for choosing cascade level in world renderer + lightComponent->SetComputedCascadeSplit(&CascadeSplits[0]); + lightComponent->ClearLightViewProjMatrix(); + for (uint16_t cascadeIndex = 0; cascadeIndex < cascadeNum; ++cascadeIndex) + { + // Compute every light view and every orthographic projection matrices for each cascade + cd::Point cascadeFrustum[8]; + float nearSplit = cascadeIndex == 0 ? MinDistance : CascadeSplits[cascadeIndex - 1]; + float farSplit = CascadeSplits[cascadeIndex]; + for (uint16_t cornerPairIdx = 0; cornerPairIdx < 4; ++cornerPairIdx) + { + cascadeFrustum[2*cornerPairIdx] = frustumCorners[2*cornerPairIdx] * (1 - nearSplit) + + frustumCorners[2*cornerPairIdx+1] * nearSplit; + cascadeFrustum[2*cornerPairIdx+1] = frustumCorners[2*cornerPairIdx] * (1 - farSplit) + + frustumCorners[2*cornerPairIdx+1] * farSplit; + } + + cd::Point cascadeFrustumCenter = cd::Point(0, 0, 0); + for (const auto& corner : cascadeFrustum) + { + cascadeFrustumCenter += corner; + } + cascadeFrustumCenter /= 8.0; + + cd::Matrix4x4 lightView = cd::Matrix4x4::LookAt(cascadeFrustumCenter, + cascadeFrustumCenter + lightDirection, cd::Vec3f(0.0f, 1.0f, 0.0f)); + + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::lowest(); + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::lowest(); + float minZ = std::numeric_limits::max(); + float maxZ = std::numeric_limits::lowest(); + for (const auto& corner : cascadeFrustum) + { + const auto lightSpaceCorner = lightView * cd::Vec4f(corner.x(), corner.y(), corner.z(), 1.0); + minX = std::min(minX, lightSpaceCorner.x()); + maxX = std::max(maxX, lightSpaceCorner.x()); + minY = std::min(minY, lightSpaceCorner.y()); + maxY = std::max(maxY, lightSpaceCorner.y()); + minZ = std::min(minZ, lightSpaceCorner.z()); + maxZ = std::max(maxZ, lightSpaceCorner.z()); + } + cd::Matrix4x4 lightProjection = cd::Matrix4x4::Orthographic(minX, maxX, maxY, minY, minZ, maxZ, + 0, ndcDepthMinusOneToOne); + + // Settings + bgfx::setState(defaultRenderingState); + + uint16_t viewId = m_renderPassID[shadowNum * shadowTexturePassMaxNum + cascadeIndex]; + bgfx::setViewRect(viewId, 0, 0, lightComponent->GetShadowMapSize(), lightComponent->GetShadowMapSize()); + bgfx::setViewFrameBuffer(viewId, static_cast(lightComponent->GetShadowMapFBs().at(cascadeIndex))); + bgfx::setViewClear(viewId, BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(viewId, lightView.begin(), lightProjection.begin()); + + // Set transform for projecting coordinates to light space in wolrd renderer + cd::Matrix4x4 lightCSMViewProj = lightProjection * lightView; + lightComponent->AddLightViewProjMatrix(lightCSMViewProj); + + // Submit draw call (TODO : one pass MRT + for (Entity entity : m_pCurrentSceneWorld->GetMaterialEntities()) + { + // No mesh attached? + StaticMeshComponent* pMeshComponent = m_pCurrentSceneWorld->GetStaticMeshComponent(entity); + if (!pMeshComponent) + { + continue; + } + BlendShapeComponent* pBlendShapeComponent = m_pCurrentSceneWorld->GetBlendShapeComponent(entity); + if (pBlendShapeComponent) + { + continue; + } + // Transform + if (TransformComponent* pTransformComponent = m_pCurrentSceneWorld->GetTransformComponent(entity)) + { + bgfx::setTransform(pTransformComponent->GetWorldMatrix().begin()); + } + + // Mesh + UpdateStaticMeshComponent(pMeshComponent); + GetRenderContext()->Submit(viewId, "ShadowMapProgram"); + } + } + } + break; + case cd::LightType::Point: + { + // Initialize(if not initialized) frame buffer + if (!lightComponent->IsShadowMapFBsValid()) + { + for (uint16_t i = 0U; i < 6U; ++i) + { + /*---------bgfx cube map---------- + 0:+X 1:-X 2:+Y 3:-Y 4:+Z 5:-Z + +Y + -X +Z +X -Z + -Y + ------------------------------------*/ + uint16_t shadowMapFB = bgfx::createFrameBuffer(lightComponent->GetShadowMapSize(), + lightComponent->GetShadowMapSize(), bgfx::TextureFormat::R32F).idx; + lightComponent->AddShadowMapFB(shadowMapFB); + } + } + + // Compute 6 light view and 1 perspective projection matrices according to ndc depth of different graphic backends + const cd::Point lightPosition = lightComponent->GetPosition(); + float range = lightComponent->GetRange(); + + cd::Matrix4x4 lightView[6] = + { + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(1.0f, 0.0f, 0.0f), cd::Direction(0.0f, 1.0f, 0.0f)), //Right +X + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(-1.0f, 0.0f, 0.0f), cd::Direction(0.0f, 1.0f, 0.0f)), //Left -X + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(0.0f, 1.0f, 0.0f), cd::Direction(0.0f, 0.0f, -1.0f)), //Top +Y + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(0.0f, -1.0f, 0.0f), cd::Direction(0.0f, 0.0f, 1.0f)), //Bottom -Y + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(0.0f, 0.0f, 1.0f), cd::Direction(0.0f, 1.0f, 0.0f)), //Front +Z + cd::Matrix4x4::LookAt(lightPosition, lightPosition + cd::Direction(0.0f, 0.0f, -1.0f), cd::Direction(0.0f, 1.0f, 0.0f)), //Back -Z + }; + cd::Matrix4x4 lightProjection = cd::Matrix4x4::Perspective(90.0f, 1.0f, 0.01f, range, ndcDepthMinusOneToOne); + + // Settings + bgfx::setState(defaultRenderingState); + // 6 faces + for (uint16_t i = 0U; i < 6U; ++i) + { + // Settings + uint16_t viewId = m_renderPassID[shadowNum * shadowTexturePassMaxNum + i]; + bgfx::setViewRect(viewId, 0, 0, lightComponent->GetShadowMapSize(), lightComponent->GetShadowMapSize()); + bgfx::setViewFrameBuffer(viewId, static_cast(lightComponent->GetShadowMapFBs().at(i))); + bgfx::setViewClear(viewId, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(viewId, lightView[i].begin(), lightProjection.begin()); + + constexpr StringCrc lightPosAndFarPlaneCrc(lightPosAndFarPlane); + cd::Vec4f lightPosAndFarPlaneData = cd::Vec4f(lightComponent->GetPosition().x(), lightComponent->GetPosition().y(), + lightComponent->GetPosition().z(), lightComponent->GetRange()); + GetRenderContext()->FillUniform(lightPosAndFarPlaneCrc, &lightPosAndFarPlaneData, 1); + + // Submit draw call + for (Entity entity : m_pCurrentSceneWorld->GetMaterialEntities()) + { + // No mesh attached? + StaticMeshComponent* pMeshComponent = m_pCurrentSceneWorld->GetStaticMeshComponent(entity); + if (!pMeshComponent) + { + continue; + } + + // Transform + if (TransformComponent* pTransformComponent = m_pCurrentSceneWorld->GetTransformComponent(entity)) + { + bgfx::setTransform(pTransformComponent->GetWorldMatrix().begin()); + } + UpdateStaticMeshComponent(pMeshComponent); + GetRenderContext()->Submit(viewId, "LinearShadowMapProgram"); + } + } + } + break; + case cd::LightType::Spot: + { + // Initialize(if not initialized) frame buffer + if (!lightComponent->IsShadowMapFBsValid()) + { + uint16_t shadowMapFB = bgfx::createFrameBuffer(lightComponent->GetShadowMapSize(), + lightComponent->GetShadowMapSize(), bgfx::TextureFormat::D32F).idx; + lightComponent->AddShadowMapFB(shadowMapFB); + } + + // Compute 1 light view and 1 perspective projection matrices according to ndc depth of different graphic backends + const cd::Point lightPosition = lightComponent->GetPosition(); + const cd::Point lightDirection = lightComponent->GetDirection(); + float range = lightComponent->GetRange(); + + cd::Direction upOrRight = cd::Direction(0.0f, 1.0f, 0.0f) == lightDirection ? cd::Direction(0.0f, 1.0f, 0.0f) : cd::Direction(1.0f, 0.0f, 0.0f); + cd::Matrix4x4 lightView = cd::Matrix4x4::LookAt(lightPosition, lightPosition + lightDirection, upOrRight); + cd::Matrix4x4 lightProjection = cd::Matrix4x4::Perspective(2.0f*lightComponent->GetInnerAndOuter().y(), 1.0f, 0.1f, range, ndcDepthMinusOneToOne); + + // Settings + uint64_t state = defaultRenderingState; + bgfx::setState(state); + uint16_t viewId = m_renderPassID[shadowNum * shadowTexturePassMaxNum + 0]; + bgfx::setViewRect(viewId, 0, 0, lightComponent->GetShadowMapSize(), lightComponent->GetShadowMapSize()); + bgfx::setViewFrameBuffer(viewId, static_cast(lightComponent->GetShadowMapFBs().at(0))); + bgfx::setViewClear(viewId, BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0); + bgfx::setViewTransform(viewId, lightView.begin(), lightProjection.begin()); + + // Set transform for projecting coordinates to light space in wolrd renderer + lightComponent->ClearLightViewProjMatrix(); + cd::Matrix4x4 lightCSMViewProj = lightProjection * lightView; + lightComponent->AddLightViewProjMatrix(lightCSMViewProj); + + // Submit draw call + for (Entity entity : m_pCurrentSceneWorld->GetMaterialEntities()) + { + // No mesh attached? + StaticMeshComponent* pMeshComponent = m_pCurrentSceneWorld->GetStaticMeshComponent(entity); + if (!pMeshComponent) + { + continue; + } + BlendShapeComponent* pBlendShapeComponent = m_pCurrentSceneWorld->GetBlendShapeComponent(entity); + if (pBlendShapeComponent) + { + continue; + } + // Transform + if (TransformComponent* pTransformComponent = m_pCurrentSceneWorld->GetTransformComponent(entity)) + { + bgfx::setTransform(pTransformComponent->GetWorldMatrix().begin()); + } + // Mesh + UpdateStaticMeshComponent(pMeshComponent); + GetRenderContext()->Submit(viewId, "ShadowMapProgram"); + } + } + break; + } + + ++shadowNum; + if (3 == shadowNum) + { + break; + } + } + } +} + +} diff --git a/Engine/Source/Runtime/Rendering/ShadowMapRenderer.h b/Engine/Source/Runtime/Rendering/ShadowMapRenderer.h new file mode 100644 index 00000000..567feba3 --- /dev/null +++ b/Engine/Source/Runtime/Rendering/ShadowMapRenderer.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Renderer.h" + +namespace engine +{ +namespace +{ +constexpr uint16_t shadowLightMaxNum = 3U; +constexpr uint16_t shadowTexturePassMaxNum = 6U; +} + +class SceneWorld; + +class ShadowMapRenderer final : public Renderer +{ +public: + using Renderer::Renderer; + + virtual void Init() override; + virtual void Warmup() override; + virtual void UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) override; + virtual void Render(float deltaTime) override; + + void SetSceneWorld(SceneWorld* pSceneWorld) { m_pCurrentSceneWorld = pSceneWorld; } + +private: + SceneWorld* m_pCurrentSceneWorld = nullptr; + uint16_t m_renderPassID[18]; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/WorldRenderer.cpp b/Engine/Source/Runtime/Rendering/WorldRenderer.cpp index bfa8939e..3c30ffa8 100644 --- a/Engine/Source/Runtime/Rendering/WorldRenderer.cpp +++ b/Engine/Source/Runtime/Rendering/WorldRenderer.cpp @@ -13,6 +13,7 @@ #include "Scene/Texture.h" #include "U_IBL.sh" #include "U_AtmophericScattering.sh" +#include "U_Shadow.sh" namespace engine { @@ -40,8 +41,20 @@ constexpr const char* lightParams = "u_lightParams"; constexpr const char* LightDir = "u_LightDir"; constexpr const char* HeightOffsetAndshadowLength = "u_HeightOffsetAndshadowLength"; +constexpr const char* lightViewProjs= "u_lightViewProjs"; +constexpr const char* cubeShadowMapSamplers[3] = { "s_texCubeShadowMap_1", "s_texCubeShadowMap_2" , "s_texCubeShadowMap_3" }; + +constexpr const char* cameraNearFarPlane = "u_cameraNearFarPlane"; +constexpr const char* cameraLookAt = "u_cameraLookAt"; +constexpr const char* clipFrustumDepth = "u_clipFrustumDepth"; + +constexpr const char* directionShadowMapTexture = "DirectionShadowMapTexture"; +constexpr const char* pointShadowMapTexture = "PointShadowMapTexture"; +constexpr const char* spotShadowMapTexture = "SpotShadowMapTexture"; + constexpr uint64_t samplerFlags = BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | BGFX_SAMPLER_W_CLAMP; constexpr uint64_t defaultRenderingState = BGFX_STATE_WRITE_MASK | BGFX_STATE_MSAA | BGFX_STATE_DEPTH_TEST_LESS; +constexpr uint64_t blitDstTextureFlags = BGFX_TEXTURE_BLIT_DST | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; } @@ -74,6 +87,14 @@ void WorldRenderer::Warmup() GetRenderContext()->CreateUniform(LightDir, bgfx::UniformType::Vec4, 1); GetRenderContext()->CreateUniform(HeightOffsetAndshadowLength, bgfx::UniformType::Vec4, 1); + + GetRenderContext()->CreateUniform(lightViewProjs, bgfx::UniformType::Mat4, 12); // + GetRenderContext()->CreateUniform(cubeShadowMapSamplers[0], bgfx::UniformType::Sampler); + GetRenderContext()->CreateUniform(cubeShadowMapSamplers[1], bgfx::UniformType::Sampler); + GetRenderContext()->CreateUniform(cubeShadowMapSamplers[2], bgfx::UniformType::Sampler); + + GetRenderContext()->CreateUniform(cameraNearFarPlane, bgfx::UniformType::Vec4, 1); + GetRenderContext()->CreateUniform(clipFrustumDepth, bgfx::UniformType::Vec4, 1); } void WorldRenderer::UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) @@ -88,6 +109,68 @@ void WorldRenderer::Render(float deltaTime) const cd::Transform& cameraTransform = m_pCurrentSceneWorld->GetTransformComponent(m_pCurrentSceneWorld->GetMainCameraEntity())->GetTransform(); SkyComponent* pSkyComponent = m_pCurrentSceneWorld->GetSkyComponent(m_pCurrentSceneWorld->GetSkyEntity()); + auto lightEntities = m_pCurrentSceneWorld->GetLightEntities(); + size_t lightEntityCount = lightEntities.size(); + + // Blit RTV to SRV to update light shadow map + for (int i = 0; i < lightEntityCount; i++) + { + auto lightComponent = m_pCurrentSceneWorld->GetLightComponent(lightEntities[i]); + cd::LightType lightType = lightComponent->GetType(); + if (cd::LightType::Directional == lightType) + { + uint16_t cascadeNum = lightComponent->GetCascadeNum(); + // Create textureCube if not valid + if (!lightComponent->IsShadowMapTextureValid()) + { + bgfx::TextureHandle blitDstShadowMapTexture = bgfx::createTextureCube(lightComponent->GetShadowMapSize(), + false, 1, bgfx::TextureFormat::D32F, blitDstTextureFlags); + GetRenderContext()->SetTexture(engine::StringCrc(directionShadowMapTexture), blitDstShadowMapTexture); + lightComponent->SetShadowMapTexture(blitDstShadowMapTexture.idx); + } + // Blit RTV(FrameBuffer Texture) to SRV(Texture) + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + for (uint16_t cascadeIdx = 0; cascadeIdx < cascadeNum; ++cascadeIdx) + { + bgfx::TextureHandle blitSrcShadowMapTexture = bgfx::getTexture(static_cast(lightComponent->GetShadowMapFBs().at(cascadeIdx))); + bgfx::blit(GetViewID(), blitDstShadowMapTexture, 0, 0, 0, cascadeIdx, blitSrcShadowMapTexture, 0, 0, 0, 0); + } + } + else if (cd::LightType::Point == lightType) + { + // Create textureCube if not valid + if (!lightComponent->IsShadowMapTextureValid()) + { + StringCrc blitDstShadowMapTextureName = StringCrc(pointShadowMapTexture); + bgfx::TextureHandle blitDstShadowMapTexture = bgfx::createTextureCube(lightComponent->GetShadowMapSize(), + false, 1, bgfx::TextureFormat::R32F, blitDstTextureFlags); + GetRenderContext()->SetTexture(blitDstShadowMapTextureName, blitDstShadowMapTexture); + lightComponent->SetShadowMapTexture(blitDstShadowMapTexture.idx); + } + // Blit RTV(FrameBuffer Texture) to SRV(Texture) + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + for (uint16_t i = 0; i < 6; ++i) + { + bgfx::TextureHandle blitSrcShadowMapTexture = bgfx::getTexture(static_cast(lightComponent->GetShadowMapFBs().at(i))); + bgfx::blit(GetViewID(), blitDstShadowMapTexture, 0, 0, 0, i, blitSrcShadowMapTexture, 0, 0, 0, 0); + } + } + else if (cd::LightType::Spot == lightType) + { + // Create texture2D if not valid + if (!lightComponent->IsShadowMapTextureValid()) + { + bgfx::TextureHandle blitDstShadowMapTexture = bgfx::createTextureCube(lightComponent->GetShadowMapSize(), + false, 1, bgfx::TextureFormat::D32F, blitDstTextureFlags); + lightComponent->SetShadowMapTexture(blitDstShadowMapTexture.idx); + } + // Blit RTV(FrameBuffer Texture) to SRV(Texture) + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + bgfx::TextureHandle blitSrcShadowMapTexture = bgfx::getTexture(static_cast(lightComponent->GetShadowMapFBs().at(0))); + bgfx::blit(GetViewID(), blitDstShadowMapTexture, 0, 0, 0, 0, blitSrcShadowMapTexture, 0, 0, 0, 0); + } + } + for (Entity entity : m_pCurrentSceneWorld->GetMaterialEntities()) { MaterialComponent* pMaterialComponent = m_pCurrentSceneWorld->GetMaterialComponent(entity); @@ -204,19 +287,76 @@ void WorldRenderer::Render(float deltaTime) constexpr StringCrc emissiveColorCrc(emissiveColorAndFactor); GetRenderContext()->FillUniform(emissiveColorCrc, pMaterialComponent->GetFactor(cd::MaterialPropertyGroup::Emissive), 1); - // Submit uniform values : light settings - auto lightEntities = m_pCurrentSceneWorld->GetLightEntities(); - size_t lightEntityCount = lightEntities.size(); + // Submit light data constexpr engine::StringCrc lightCountAndStrideCrc(lightCountAndStride); static cd::Vec4f lightInfoData(0, LightUniform::LIGHT_STRIDE, 0.0f, 0.0f); lightInfoData.x() = static_cast(lightEntityCount); GetRenderContext()->FillUniform(lightCountAndStrideCrc, lightInfoData.begin(), 1); - if (lightEntityCount > 0) + int totalLightViewProjOffset = 0; + float lightData[4 *7 * 3] = { 0 }; + for (uint16_t i = 0U; i < lightEntityCount; ++i) + { + LightComponent* lightComponent = m_pCurrentSceneWorld->GetLightComponent(lightEntities[i]); + if (cd::LightType::Directional == lightComponent->GetType()) + { + lightComponent->SetLightViewProjOffset(totalLightViewProjOffset); + totalLightViewProjOffset += 4; + } + else if (cd::LightType::Spot == lightComponent->GetType()) + { + lightComponent->SetLightViewProjOffset(totalLightViewProjOffset); + totalLightViewProjOffset++; + } + memcpy(&lightData[4 * 7 * i], lightComponent->GetLightUniformData(), sizeof(U_Light)); + } + constexpr engine::StringCrc lightParamsCrc(lightParams); + GetRenderContext()->FillUniform(lightParamsCrc, lightData, static_cast(lightEntityCount * LightUniform::LIGHT_STRIDE)); + + // Submit light view&projection transform + std::vector lightViewProjsData; + for (uint16_t i = 0U; i < lightEntityCount; ++i) { - // Light component storage has continus memory address and layout. - float* pLightDataBegin = reinterpret_cast(m_pCurrentSceneWorld->GetLightComponent(lightEntities[0])); - constexpr engine::StringCrc lightParamsCrc(lightParams); - GetRenderContext()->FillUniform(lightParamsCrc, pLightDataBegin, static_cast(lightEntityCount * LightUniform::LIGHT_STRIDE)); + LightComponent* lightComponent = m_pCurrentSceneWorld->GetLightComponent(lightEntities[i]); + const std::vector& lightViewProjs = lightComponent->GetLightViewProjMatrix(); + for (auto lightViewProj : lightViewProjs) + { + lightViewProjsData.push_back(lightViewProj); + } + } + constexpr engine::StringCrc lightViewProjsCrc(lightViewProjs); + GetRenderContext()->FillUniform(lightViewProjsCrc, lightViewProjsData.data(), totalLightViewProjOffset); + + // Submit shared uniform (related to camera) + constexpr StringCrc cameraNearFarPlaneCrc(cameraNearFarPlane); + CameraComponent* pMainCameraComponent = m_pCurrentSceneWorld->GetCameraComponent(m_pCurrentSceneWorld->GetMainCameraEntity()); + float cameraNearFarPlanedata[2] = { pMainCameraComponent->GetNearPlane(), pMainCameraComponent->GetFarPlane() }; + GetRenderContext()->FillUniform(cameraNearFarPlaneCrc, &cameraNearFarPlanedata[0], 1); + + // Submit shadow map and settings of each light + constexpr StringCrc shadowMapSamplerCrcs[3] = { StringCrc(cubeShadowMapSamplers[0]), StringCrc(cubeShadowMapSamplers[1]), StringCrc(cubeShadowMapSamplers[2]) }; + for (int lightIndex = 0; lightIndex < lightEntityCount; lightIndex++) + { + auto lightComponent = m_pCurrentSceneWorld->GetLightComponent(lightEntities[lightIndex]); + cd::LightType lightType = lightComponent->GetType(); + if (cd::LightType::Directional == lightType) + { + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + bgfx::setTexture(SHADOW_MAP_CUBE_FIRST_SLOT+lightIndex, GetRenderContext()->GetUniform(shadowMapSamplerCrcs[lightIndex]), blitDstShadowMapTexture); + // TODO : manual + constexpr StringCrc clipFrustumDepthCrc(clipFrustumDepth); + GetRenderContext()->FillUniform(clipFrustumDepthCrc, lightComponent->GetComputedCascadeSplit(), 1); + } + else if (cd::LightType::Point == lightType) + { + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + bgfx::setTexture(SHADOW_MAP_CUBE_FIRST_SLOT+lightIndex, GetRenderContext()->GetUniform(shadowMapSamplerCrcs[lightIndex]), blitDstShadowMapTexture); + } + else if (cd::LightType::Spot == lightType) + { + // Blit RTV(FrameBuffer Texture) to SRV(Texture) + bgfx::TextureHandle blitDstShadowMapTexture = static_cast(lightComponent->GetShadowMapTexture()); + bgfx::setTexture(SHADOW_MAP_CUBE_FIRST_SLOT+lightIndex, GetRenderContext()->GetUniform(shadowMapSamplerCrcs[lightIndex]), blitDstShadowMapTexture); + } } uint64_t state = defaultRenderingState;