// Copyright Epic Games, Inc. All Rights Reserved. #pragma once /*============================================================================= TransmissionThickness.ush: Calc Transmission Thickness shaders. =============================================================================*/ #include "TransmissionCommon.ush" /*------------------------------------------------------------------------------- Functions for Transmission Thickness -------------------------------------------------------------------------------*/ float4x4 ScreenToShadowMatrix; // x is start, z is end,From 1 to 32 sample // .x:DepthBias, .y:SlopeDepthBias, .z:MaxSlopeDepthBias, .w: MaxSubjectZ - MinSubjectZ float4 ProjectionDepthBiasParameters; // Poisson disks generated with http://www.coderhaus.com/?p=11 // Combine all Poisson disk togeather, borrow idea from Jorge's multiple sample single scattering // http://www.iryoku.com/translucency/ int PoissonMethod; static int2 PoissonStartAndEnd[] = { int2(0, 1), int2(1, 3), int2(3, 7), int2(7, 15), int2(15, 31), int2(31, 63), }; static const float2 PoissonDisk[] = { float2(0.402211, 0.126575), float2(0.297056, 0.616830), float2(0.298156, -0.001704), float2(0.019369, 0.395482), float2(-0.066918, -0.367739), float2(-0.955010, 0.372377), float2(0.800057, 0.120602), float2(-0.7494944, 0.1827986), float2(-0.8572887, -0.4169083), float2(-0.1087135, -0.05238153), float2(0.1045462, 0.9657645), float2(-0.0135659, -0.698451), float2(-0.4942278, 0.7898396), float2(0.7970678, -0.4682421), float2(0.8084122, 0.533884), float2(0.4036454, -0.793536), float2(0.3826454, -0.2730118), float2(-0.04124885, -0.5971786), float2(-0.3709261, -0.9179904), float2(-0.3795351, -0.3353493), float2(-0.3154466, 0.1069074), float2(-0.7671808, -0.6143452), float2(-0.4865215, 0.6395131), float2(0.2359872, 0.1510548), float2(0.03092861, 0.7309022), float2(-0.82846, -0.1055831), float2(-0.8732378, 0.3034171), float2(0.9268684, -0.116035), float2(0.6980102, 0.3764873), float2(0.8239923, -0.515003), float2(0.5084407, 0.7533528), float2(-0.975402, -0.0711386), float2(-0.920347, -0.41142), float2(-0.883908, 0.217872), float2(-0.884518, 0.568041), float2(-0.811945, 0.90521), float2(-0.792474, -0.779962), float2(-0.614856, 0.386578), float2(-0.580859, -0.208777), float2(-0.53795, 0.716666), float2(-0.515427, 0.0899991), float2(-0.454634, -0.707938), float2(-0.420942, 0.991272), float2(-0.261147, 0.588488), float2(-0.211219, 0.114841), float2(-0.146336, -0.259194), float2(-0.139439, -0.888668), float2(0.0116886, 0.326395), float2(0.0380566, 0.625477), float2(0.0625935, -0.50853), float2(0.125584, 0.0469069), float2(0.169469, -0.997253), float2(0.320597, 0.291055), float2(0.359172, -0.633717), float2(0.435713, -0.250832), float2(0.507797, -0.916562), float2(0.545763, 0.730216), float2(0.56859, 0.11655), float2(0.743156, -0.505173), float2(0.736442, -0.189734), float2(0.843562, 0.357036), float2(0.865413, 0.763726), float2(0.872005, -0.927) }; float CalcShadowLinearDepth(float shadowDepth, float2 param) { return 1.0f / (shadowDepth * param.x - param.y); } float CalculateThickness(float ShadowmapDepth, float SceneDepth, float DensityMulConstant, float NormalScale, float NoL) { // Determine the distance that the light traveled through the subsurface object // This assumes that anything between this subsurface pixel and the light was also a subsurface material, // As a result, subsurface materials receive leaked light based on how transparent they are float Thickness = (SceneDepth - ShadowmapDepth) * DensityMulConstant; Thickness = clamp(abs(Thickness > 0 ? Thickness + NormalScale : max(0.0, Thickness * NoL + NormalScale)), 0.15, 5) + 0.25; // Never shadow from depths that were never written to (max depth value) return Thickness; } float CalcTransmissionThickness(float3 ViewPos, float3 LightPos, float3 WorldNormal, FTransmissionProfileParams TransmissionParams, FPCFSamplerSettings Settings) { ///Get -NoL, maybe unnecessary. float4 HomogeneousWorldPosition = mul(float4(ViewPos.xyz, 1), DFHackToFloat(PrimaryView.ScreenToWorld)); float3 WorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w; float3 ToLight = LightPos.xyz - WorldPosition; float DistanceSqr = dot(ToLight, ToLight); float3 L = ToLight * rsqrt(DistanceSqr); float NoL = 1;// dot(-L, WorldNormal); //Shrink Pos in View Space. float3 ViewNormal = mul(WorldNormal, (float3x3)View.TranslatedWorldToView); //Shrink world position with neg worldNormal to get thickness //These scalar const is based on adjust expericence. float DepthScale = TransmissionParams.ExtinctionScale * 3.1f; float NormalScale = TransmissionParams.NormalScale * 0.5; float ShadowFilterRadius = ShadowBufferSize.w; float4 ShrinkedPos = float4(ViewPos.xyz - (NormalScale)* ViewNormal.xyz, 1.0); float4 ShrinkedScreenPos = mul(ShrinkedPos, ScreenToShadowMatrix); ShrinkedScreenPos.xy = ShrinkedScreenPos.xy / ShrinkedScreenPos.w; float DensityMulConstant = DepthScale * ProjectionDepthBiasParameters.w; Settings.SceneDepth = (1.0f - ShrinkedScreenPos.z) + ProjectionDepthBiasParameters.x; //Alpha is the thickness. float ThicknessSum = 0.0; uint PoissonMethod = 4; float SampleNum = PoissonStartAndEnd[PoissonMethod].y - PoissonStartAndEnd[PoissonMethod].x; for (int i = PoissonStartAndEnd[PoissonMethod].x; i < PoissonStartAndEnd[PoissonMethod].y; i++) { float2 DeltUv = PoissonDisk[i] * ShadowFilterRadius; float2 ShadowUv = ShrinkedScreenPos.xy + DeltUv; float ShadowmapDepth = Texture2DSampleLevel(Settings.ShadowDepthTexture, Settings.ShadowDepthTextureSampler, ShadowUv.xy, 0).r; float Thickness = CalculateThickness(ShadowmapDepth, Settings.SceneDepth, DensityMulConstant, NormalScale, NoL); ThicknessSum += (Thickness); } float Thickness = ThicknessSum / SampleNum; return EncodeThickness(Thickness / SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE); } /** Computes thickness for a given world position from a cubemap shadowmap used on a point light. */ float CalcTransmissionThickness(float3 WorldNormal, FTransmissionProfileParams TransmissionParams, float3 WorldSampleToLightVec, float2 ProjParam, float LightInvRadius, float DepthBias) { float Thickness = 1; float DepthScale = TransmissionParams.ExtinctionScale * 3.1f; float NormalScale = TransmissionParams.NormalScale * 0.5; //Shrink pos aligh -WorldNormal float3 Normal = WorldNormal; float3 LightRelativeBiasedPosition = -WorldSampleToLightVec - Normal * NormalScale; float DistanceSqr = dot(WorldSampleToLightVec, WorldSampleToLightVec); float3 L = WorldSampleToLightVec * rsqrt(DistanceSqr); float NoL = pow(saturate(1 * dot(-L, Normal)), 1); float Distance = length(WorldSampleToLightVec); float ShadowFilterRadius = 1.0f; BRANCH // Skip pixels outside of the light's influence if (Distance * LightInvRadius < 1.0f) { float3 NormalizedLightVector = WorldSampleToLightVec / Distance, SideVector = 0, UpVector = 0; int CubeFaceIndex = 0; CalcCubemapVectorAndFaceIndex(NormalizedLightVector, ShadowFilterRadius, SideVector, UpVector, CubeFaceIndex); // Transform the position into shadow space float4 ShadowPosition = mul(float4(LightRelativeBiasedPosition, 1), ShadowViewProjectionMatrices[CubeFaceIndex]); // Calculate the Z buffer value that would have been stored for this position in the shadow map float CompareDistance = ShadowPosition.z / ShadowPosition.w; float ShadowDepthBias = -DepthBias / ShadowPosition.w; //Shadow Depth range Near to far, so no need mul far here. float DensityMulConstant = TransmissionParams.ExtinctionScale * (10.0f / LightInvRadius); float SceneDepth = CalcShadowLinearDepth(CompareDistance + ShadowDepthBias, ProjParam) * LightInvRadius; Thickness = 0; UNROLL for (int i = 0; i < 5; ++i) { float2 DeltUv = DiscSamples5[i] * ShadowFilterRadius; float3 SamplePos = NormalizedLightVector + SideVector * DeltUv.x + UpVector * DeltUv.y; #if USE_SEPARATE_SHADOW_DEPTH_CUBE_TEXTURE float ShadowSample = TextureCubeSampleLevel(ShadowDepthCubeTexture2, ShadowDepthTextureSampler, SamplePos, 0.0).r; #else float ShadowSample = TextureCubeSampleDepthLevel(ShadowDepthCubeTexture, ShadowDepthTextureSampler, SamplePos, 0.0); #endif float ShadowDepth = CalcShadowLinearDepth(ShadowSample, ProjParam) * LightInvRadius; Thickness += CalculateThickness(ShadowDepth, SceneDepth, DensityMulConstant, NormalScale, NoL); } Thickness /= 5; } return EncodeThickness(Thickness / SSSS_MAX_TRANSMISSION_PROFILE_DISTANCE); }