diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl index 523353f8fa..091e0a5645 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.azsl @@ -92,6 +92,7 @@ struct VSOutput float3 m_bitangent : BITANGENT; float3 m_worldPosition : UV0; float3 m_shadowCoords[ViewSrg::MaxCascadeCount] : UV4; + float3 m_shrinkedShadowCoords[ViewSrg::MaxCascadeCount] : UV9; // Extended fields (only referenced in this azsl file)... float2 m_uv[UvSetCount] : UV1; @@ -137,6 +138,14 @@ VSOutput SkinVS(VSInput IN) VertexHelper(IN, OUT, worldPosition, false); + // Fetch shadow coords for shrinked world position (used in thin transmission materials) + const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; + DirectionalLightShadow::GetShadowCoords( + shadowIndex, + worldPosition - 0.005 * OUT.m_normal, + OUT.m_normal, + OUT.m_shrinkedShadowCoords); + return OUT; } @@ -336,6 +345,7 @@ PbrLightingOutput SkinPS_Common(VSOutput IN) // Directional light shadow coordinates lightingData.shadowCoords = IN.m_shadowCoords; + lightingData.shrinkedShadowCoords = IN.m_shrinkedShadowCoords; // Diffuse and Specular response (used in IBL calculations) lightingData.specularResponse = FresnelSchlickWithRoughness(lightingData.NdotV, surface.specularF0, surface.roughnessLinear); diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli index dfd5522f5c..70f49f0a66 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/BackLighting.azsli @@ -16,24 +16,26 @@ #include // Analytical integation (approximation) of diffusion profile over radius, could be replaced by other pre integrated kernels -// such as sum of Gaussian +// such as sum of Gaussian (see T(s)) float3 TransmissionKernel(float t, float3 s) { float3 exponent = s * t; return 0.25 * (1.0 / exp(exponent) + 3.0 / exp(exponent / 3.0)); } -float ThinObjectFalloff(const float3 surfaceNormal, const float3 dirToLight) +// Analytical integation (approximation) of diffusion profile over radius, could be precomputed in a LUT +float3 T(float s) { - const float ndl = saturate(dot(-surfaceNormal, dirToLight)); - - // ndl works decently well but it can produce a harsh discontinuity in the area just before - // the shadow starts appearing on objects like cylinder and tubes. - // Smoothing out ndl does a decent enough job of removing this artifact. - return smoothstep(0, 1, ndl * ndl); + // dipoles and multipoles are approximated with sums of a small number of Gaussians with variable weights and variances + return float3(0.233, 0.455, 0.649) * exp(-s*s/0.0064) + + float3(0.1, 0.336, 0.344) * exp(-s*s/0.0484) + + float3(0.118, 0.198, 0.0) * exp(-s*s/0.187) + + float3(0.113, 0.007, 0.007) * exp(-s*s/0.567) + + float3(0.358, 0.004, 0.0) * exp(-s*s/1.99) + + float3(0.078, 0.0, 0.0) * exp(-s*s/7.41); } -float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightIntensity, float3 dirToLight, float shadowRatio) +float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightIntensity, float3 dirToLight, float transmissionDistance) { float3 result = float3(0.0, 0.0, 0.0); float thickness = 0.0; @@ -49,7 +51,7 @@ float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightI // https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/ { - thickness = max(shadowRatio, surface.transmission.thickness); + thickness = max(transmissionDistance, surface.transmission.thickness); float transmittance = pow( saturate( dot( lightingData.dirToCamera, -normalize( dirToLight + surface.normal * transmissionParams.z ) ) ), transmissionParams.y ) * transmissionParams.w; float lamberAttenuation = exp(-thickness * transmissionParams.x) * saturate(1.0 - thickness); result = transmittance * lamberAttenuation * lightIntensity; @@ -57,18 +59,21 @@ float3 GetBackLighting(Surface surface, LightingData lightingData, float3 lightI break; case TransmissionMode::ThinObject: - // Thin object mode, using thin-film assumption proposed by Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency" + // Thin object mode, based on Jimenez J. et al, 2010, "Real-Time Realistic Skin Translucency" // http://www.iryoku.com/translucency/downloads/Real-Time-Realistic-Skin-Translucency.pdf - - float litRatio = 1.0 - shadowRatio; - if (litRatio) + { - const float thickness = surface.transmission.thickness * transmissionParams.w; - const float3 invScattering = rcp(transmissionParams.xyz); - const float falloff = ThinObjectFalloff(surface.normal, dirToLight); - result = TransmissionKernel(thickness, invScattering) * falloff * lightIntensity * litRatio; - } + // Irradiance arround surface point. + // Begin the transmittance dot product slightly before it would with the regular dot(N,L) + float E = max(0.30 + dot(-surface.normal, dirToLight), 0.0); + // Transmission distance computed from shadowmaps modulated by editor-exposed parameters + float s = transmissionDistance * surface.transmission.thickness * (20 - transmissionParams.w) * 10; + + // Albedo at front (surface point) is used to approximate irradiance at the back of the object + // See observation 4 in [Jimenez J. et al, 2010] + result = T(s) * lightIntensity * surface.albedo * E; + } break; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli index 878bed02d4..51bd421418 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli @@ -28,6 +28,9 @@ class LightingData // Direction light shadow coordinates float3 shadowCoords[ViewSrg::MaxCascadeCount]; + // Direction light shadow coordinates for shrinked positions + float3 shrinkedShadowCoords[ViewSrg::MaxCascadeCount]; + // Normalized direction from surface to camera float3 dirToCamera; diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli index 1eee53a24f..32654e0427 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/Lights/DirectionalLight.azsli @@ -18,7 +18,9 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData) // Shadowed check const uint shadowIndex = ViewSrg::m_shadowIndexDirectionalLight; float litRatio = 1.0f; + float transmissionDistance = 0.0f; float backShadowRatio = 0.0f; + if (o_enableShadows && shadowIndex < SceneSrg::m_directionalLightCount) { litRatio = DirectionalLightShadow::GetVisibility( @@ -30,6 +32,12 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData) if (o_transmission_mode == TransmissionMode::ThickObject) { backShadowRatio = DirectionalLightShadow::GetThickness(shadowIndex, lightingData.shadowCoords); + } + else if (o_transmission_mode == TransmissionMode::ThinObject) + { + // Use shrinked positions for thin object transmission to ensure they fall onto the object when querying + // the depth from the shadow map + transmissionDistance = DirectionalLightShadow::GetThickness(shadowIndex, lightingData.shrinkedShadowCoords); } } @@ -50,21 +58,26 @@ void ApplyDirectionalLights(Surface surface, inout LightingData lightingData) // [GFX TODO][ATOM-2012] care of multiple directional light // Currently shadow check is done only for index == shadowIndex. float currentLitRatio = 1.0f; - float currentBackShadowRatio = 1.0f; + float currentTransmissionParameter = 1.0f; if (o_enableShadows) { - currentLitRatio = (index == shadowIndex) ? litRatio : 1.; - - currentBackShadowRatio = 1.0 - currentLitRatio; + bool activeLight = index == shadowIndex; + currentLitRatio = activeLight ? litRatio : 1.; if (o_transmission_mode == TransmissionMode::ThickObject) { - currentBackShadowRatio = (index == shadowIndex) ? backShadowRatio : 0.; + // Back shadow ratio (add contribution only if current directional light is the active one for shadows) + currentTransmissionParameter = activeLight ? backShadowRatio : 0.; + } + else if (o_transmission_mode == TransmissionMode::ThinObject) + { + // Transmission distance (add contribution only if current directional light is the active one for shadows) + currentTransmissionParameter = activeLight ? transmissionDistance : 9999.f; } } lightingData.diffuseLighting += GetDiffuseLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio; lightingData.specularLighting += GetSpecularLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight) * currentLitRatio; - lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, currentBackShadowRatio); + lightingData.translucentBackLighting += GetBackLighting(surface, lightingData, light.m_rgbIntensityLux, dirToLight, currentTransmissionParameter); } // Add debug coloring for directional light shadow