// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= PostprocessAmbient.usf: To apply a ambient cubemap as a postprocess =============================================================================*/ #include "Common.ush" #include "PostProcessCommon.ush" #include "DeferredShadingCommon.ush" #include "CubemapCommon.ush" #include "Random.ush" #include "BRDF.ush" #include "MonteCarlo.ush" #include "ShadingModels.ush" #include "SceneTextureParameters.ush" #define IMPORTANCE_SAMPLE 0 Texture2D AmbientOcclusionTexture; SamplerState AmbientOcclusionSampler; #if SUBTRATE_GBUFFER_FORMAT==1 // Override the default lighting input used for the env. lighting to sample the 'custom composite env. lighting' float3 GetEnvDiffuseLighting(float3 InBentNormal) { const float AbsoluteDiffuseMip = AmbientCubemapMipAdjust.z; return TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, InBentNormal, AbsoluteDiffuseMip).rgb * AmbientCubemapColor.rgb; } float3 GetEnvSpecularLighting( float CompositeAlpha, float3 TranslatedWorldPosition, float3 SpecularDirection, float SpecularSafeRoughness, float IndirectIrradiance, float IndirectSpecularOcclusion, float3 ExtraIndirectSpecular, uint NumCulledReflectionCaptures, uint CaptureDataStartIndex) { const float SpecularMip = ComputeCubemapMipFromRoughness(SpecularSafeRoughness, AmbientCubemapMipAdjust.w); return TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, SpecularDirection, SpecularMip).rgb * AmbientCubemapColor.rgb; } #define APPLY_SKY_SHADOWING 0 #define USE_DEFAULT_ENV_LIGHTING_INPUT 0 #define USE_SUBSTRATE_ENV_LIGHTING_COMMON 1 #include "ReflectionEnvironmentShared.ush" #include "Substrate/Substrate.ush" #include "Substrate/SubstrateEvaluation.ush" #include "Substrate/SubstrateLightingCommon.ush" void MainPS( float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 #if SUBSTRATE_OPAQUE_ROUGH_REFRACTION_ENABLED , out float3 OutOpaqueRoughRefractionSceneColor : SV_Target1 , out float3 OutSubSurfaceSceneColor : SV_Target2 #endif ) { const int2 PixelPos = int2(SvPosition.xy); const float2 BufferUV = SvPositionToBufferUV(SvPosition); const float2 ScreenPosition = SvPositionToScreenPosition(SvPosition).xy; OutColor = 0; #if SUBSTRATE_OPAQUE_ROUGH_REFRACTION_ENABLED OutOpaqueRoughRefractionSceneColor = 0.0f; OutSubSurfaceSceneColor = 0.0f; #endif FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel); FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture); BRANCH if (SubstratePixelHeader.ClosureCount > 0) // This test is also enough to exclude sky pixels { float DeviceZ = SampleDeviceZFromSceneTextures(BufferUV); float SceneDepth = ConvertFromDeviceZ(DeviceZ); float3 TranslatedWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToTranslatedWorld).xyz; float3 CameraToPixel = normalize(TranslatedWorldPosition - PrimaryView.TranslatedWorldCameraOrigin); float3 V = -CameraToPixel; // Sample the ambient occlusion that is dynamically generated every frame. const float AmbientOcclusion = AmbientOcclusionTexture.SampleLevel(AmbientOcclusionSampler, BufferUV, 0).r; const FSubstrateIntegrationSettings Settings = InitSubstrateIntegrationSettings(false /*bForceFullyRough*/, Substrate.bRoughDiffuse, Substrate.PeelLayersAboveDepth, Substrate.bRoughnessTracking); FSubstrateDeferredLighting SubstrateLighting = GetInitialisedSubstrateDeferredLighting(); const float CombinedScreenAndMaterialAO = SubstrateGetAO(SubstratePixelHeader) * AmbientOcclusion; Substrate_for (uint ClosureIndex = 0, ClosureIndex < SubstratePixelHeader.ClosureCount, ++ClosureIndex) { FSubstrateBSDF BSDF = UnpackSubstrateBSDF(Substrate.MaterialTextureArray, SubstrateAddressing, SubstratePixelHeader); FSubstrateBSDFContext SubstrateBSDFContext = SubstrateCreateBSDFContext(SubstratePixelHeader, BSDF, SubstrateAddressing, V); const float3 BSDFThroughput = LuminanceWeight(SubstrateBSDFContext, BSDF); // Use the reflected direction // Evaluate environment lighting FSubstrateEnvLightResult SubstrateEnvLight = SubstrateEvaluateForEnvLight(SubstrateBSDFContext, true /*bEnableSpecular*/, Settings); float3 DiffuseLighting = 0; float3 SpecularLighting = 0; SubstrateEnvLightingCommon( SubstrateEnvLight, SubstratePixelHeader, SubstrateBSDFContext, BSDF, SubstrateEnvLight.DiffuseNormal, BSDFThroughput, 0 /*CaptureDataStartIndex*/, 0 /*NumCulledReflectionCaptures*/, AmbientOcclusion, 1.0f /*CloudVolumetricAOShadow*/, 1.0f /*TopLayerSpecularContributionFactor*/, TranslatedWorldPosition, CombinedScreenAndMaterialAO, DiffuseLighting, SpecularLighting); FLightAccumulator Out = (FLightAccumulator)0; LightAccumulator_AddSplit(Out, DiffuseLighting, SpecularLighting, DiffuseLighting, View.PreExposure, SubstrateEnvLight.bPostProcessSubsurface); AccumulateSubstrateDeferredLighting(SubstrateLighting, Out, SubstrateEnvLight.bPostProcessSubsurface, BSDF_GETISTOPLAYER(BSDF)); } OutColor += SubstrateLighting.SceneColor; #if SUBSTRATE_OPAQUE_ROUGH_REFRACTION_ENABLED OutOpaqueRoughRefractionSceneColor += SubstrateLighting.OpaqueRoughRefractionSceneColor; OutSubSurfaceSceneColor += SubstrateLighting.SubSurfaceSceneColor; #endif } } #else // SUBTRATE_GBUFFER_FORMAT==1 float3 DiffuseIBL( uint2 Random, float3 DiffuseColor, float Roughness, float3 N, float3 V ) { N = normalize( N ); V = normalize( V ); float3 DiffuseLighting = 0; float NoV = saturate( dot( N, V ) ); const uint NumSamples = 32; for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); float3 L = TangentToWorld( CosineSampleHemisphere( E ).xyz, N ); float3 H = normalize(V + L); float NoL = saturate( dot( N, L ) ); float NoH = saturate( dot( N, H ) ); float VoH = saturate( dot( V, H ) ); if( NoL > 0 ) { float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb; float FD90 = ( 0.5 + 2 * VoH * VoH ) * Roughness; //float FD90 = 0.5 + 2 * VoH * VoH * Roughness; float FdV = 1 + (FD90 - 1) * pow( 1 - NoV, 5 ); float FdL = 1 + (FD90 - 1) * pow( 1 - NoL, 5 ); #if 1 // lambert = DiffuseColor * NoL / PI // pdf = NoL / PI DiffuseLighting += SampleColor * DiffuseColor * FdV * FdL * ( 1 - 0.3333 * Roughness ); #else DiffuseLighting += SampleColor * DiffuseColor; #endif } } return DiffuseLighting / NumSamples; } float3 SpecularIBL( uint2 Random, float3 SpecularColor, float Roughness, float3 N, float3 V ) { float3 SpecularLighting = 0; const uint NumSamples = 32; for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); float3 H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness) ).xyz, N ); float3 L = 2 * dot( V, H ) * H - V; float NoV = saturate( dot( N, V ) ); float NoL = saturate( dot( N, L ) ); float NoH = saturate( dot( N, H ) ); float VoH = saturate( dot( V, H ) ); if( NoL > 0 ) { float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb; float Vis = Vis_SmithJointApprox( Pow4(Roughness), NoV, NoL ); float Fc = pow( 1 - VoH, 5 ); float3 F = (1 - Fc) * SpecularColor + Fc; // Incident light = SampleColor * NoL // Microfacet specular = D*G*F / (4*NoL*NoV) = D*Vis*F // pdf = D * NoH / (4 * VoH) SpecularLighting += SampleColor * F * ( NoL * Vis * (4 * VoH / NoH) ); } } return SpecularLighting / NumSamples; } float3 ImageBasedLightingMIS( FGBufferData GBuffer, float3 V, float3 N, uint2 Random ) { float3 Lighting = 0; float Roughness1 = GBuffer.Roughness; float Roughness2 = 0.1; uint NumSamples[] = { 16, 16, 0, }; UNROLL for( uint Set = 0; Set < 3; Set++ ) { LOOP for( uint i = 0; i < NumSamples[ Set ]; i++ ) { float2 E = Hammersley( i, NumSamples[ Set ], Random ); float3 L, H; if( Set == 0 ) { L = TangentToWorld( CosineSampleHemisphere( E ).xyz, N ); H = normalize(V + L); } else if( Set == 1 ) { H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness1) ).xyz, N ); L = 2 * dot( V, H ) * H - V; } else { H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness2) ).xyz, N ); L = 2 * dot( V, H ) * H - V; } float NoL = saturate( dot(N, L) ); float NoH = saturate( dot(N, H) ); float VoH = saturate( dot(V, H) ); if( NoL > 0 && VoH > 0 ) { float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb; float PDF[] = { NoL / PI, D_GGX( Pow4(Roughness1), NoH ) * NoH / (4 * VoH), D_GGX( Pow4(Roughness2), NoH ) * NoH / (4 * VoH), }; // MIS balance heuristic float InvWeight = 0; UNROLL for( uint j = 0; j < 3; j++ ) { InvWeight += PDF[j] * NumSamples[j]; } float Weight = rcp( InvWeight ); FShadowTerms Shadow = { 1, 1, 1, InitHairTransmittanceData() }; FDirectLighting LightingSample = EvaluateBxDF( GBuffer, N, V, L, NoL, Shadow ); Lighting += SampleColor * Weight * ( LightingSample.Diffuse + LightingSample.Specular + LightingSample.Transmission ); } } } return Lighting; } float3 FilterEnvMap( uint2 Random, float Roughness, float3 N, float3 V ) { float3 FilteredColor = 0; float Weight = 0; const uint NumSamples = 64; for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); float3 H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness) ).xyz, N ); float3 L = 2 * dot( V, H ) * H - V; float NoL = saturate( dot( N, L ) ); if( NoL > 0 ) { FilteredColor += AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb * NoL; Weight += NoL; } } return FilteredColor / max( Weight, 0.001 ); } float3 PrefilterEnvMap( uint2 Random, float Roughness, float3 R ) { float3 FilteredColor = 0; float Weight = 0; const uint NumSamples = 64; for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); float3 H = TangentToWorld( ImportanceSampleGGX( E, Pow4(Roughness) ).xyz, R ); float3 L = 2 * dot( R, H ) * H - R; float NoL = saturate( dot( R, L ) ); if( NoL > 0 ) { FilteredColor += AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb * NoL; Weight += NoL; } } return FilteredColor / max( Weight, 0.001 ); } float3 IntegrateBRDF( uint2 Random, float Roughness, float NoV ) { float3 V; V.x = sqrt( 1.0f - NoV * NoV ); // sin V.y = 0; V.z = NoV; // cos float A = 0; float B = 0; float C = 0; const uint NumSamples = 64; for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); { float3 H = ImportanceSampleGGX( E, Pow4(Roughness) ).xyz; float3 L = 2 * dot( V, H ) * H - V; float NoL = saturate( L.z ); float NoH = saturate( H.z ); float VoH = saturate( dot( V, H ) ); if( NoL > 0 ) { float a = Square( Roughness ); float a2 = a*a; float Vis = Vis_SmithJointApprox( a2, NoV, NoL ); float Vis_SmithV = NoL * sqrt( NoV * (NoV - NoV * a2) + a2 ); float Vis_SmithL = NoV * sqrt( NoL * (NoL - NoL * a2) + a2 ); //float Vis = 0.5 * rcp( Vis_SmithV + Vis_SmithL ); // Incident light = NoL // pdf = D * NoH / (4 * VoH) // NoL * Vis / pdf float NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH); float Fc = pow( 1 - VoH, 5 ); A += (1 - Fc) * NoL_Vis_PDF; B += Fc * NoL_Vis_PDF; } } { float3 L = CosineSampleHemisphere( E ).xyz; float3 H = normalize(V + L); float NoL = saturate( L.z ); float NoH = saturate( H.z ); float VoH = saturate( dot( V, H ) ); float FD90 = ( 0.5 + 2 * VoH * VoH ) * Roughness; float FdV = 1 + (FD90 - 1) * pow( 1 - NoV, 5 ); float FdL = 1 + (FD90 - 1) * pow( 1 - NoL, 5 ); C += FdV * FdL * ( 1 - 0.3333 * Roughness ); } } return float3( A, B, C ) / NumSamples; } float3 ApproximateSpecularIBL( uint2 Random, float3 SpecularColor, float Roughness, float3 N, float3 V ) { // Function replaced with prefiltered environment map sample float3 R = 2 * dot( V, N ) * N - V; float3 PrefilteredColor = PrefilterEnvMap( Random, Roughness, R ); //float3 PrefilteredColor = FilterEnvMap( Random, Roughness, N, V ); // Function replaced with 2D texture sample float NoV = saturate( dot( N, V ) ); float2 AB = IntegrateBRDF( Random, Roughness, NoV ).xy; return PrefilteredColor * ( SpecularColor * AB.x + AB.y ); } float3 ImageBasedLightingHair( FGBufferData GBuffer, float3 V, float3 N, uint2 Random ) { float3 Lighting = 0; uint NumSamples = 32; FHairTransmittanceData HairTransmittance = InitHairTransmittanceData(true); LOOP for( uint i = 0; i < NumSamples; i++ ) { float2 E = Hammersley( i, NumSamples, Random ); float3 L = UniformSampleSphere( E ).xyz; { float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb; float PDF = 1.0 / (4 * PI); float InvWeight = PDF * NumSamples; float Weight = rcp( InvWeight ); float3 Shading = 0; Shading = HairShading( GBuffer, L, V, N, 1, HairTransmittance, 0, 0, Random ); Lighting += SampleColor * Shading * Weight; } } return Lighting; } void MainPS(in noperspective float4 UVAndScreenPos : TEXCOORD0, float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0) { float2 BufferUV = UVAndScreenPos.xy; float2 ScreenSpacePos = UVAndScreenPos.zw; int2 PixelPos = int2(SvPosition.xy); FGBufferData GBuffer = GetGBufferDataFromSceneTextures(BufferUV); float AbsoluteDiffuseMip = AmbientCubemapMipAdjust.z; float3 ScreenVector = normalize(mul(float3(ScreenSpacePos, 1), DFToFloat3x3(PrimaryView.ScreenToWorld)).xyz); float3 N = GBuffer.WorldNormal; float3 V = -ScreenVector; float3 R0 = 2 * dot( V, N ) * N - V; //float NoV = max( dot(N, V), 1e-5 ); float NoV = saturate( dot(N, V) ); // Point lobe in off-specular peak direction float a = Square( GBuffer.Roughness ); float3 R = lerp( N, R0, (1 - a) * ( sqrt(1 - a) + a ) ); uint2 Random = Rand3DPCG16( uint3( PixelPos, View.StateFrameIndexMod8) ).xy; float3 NonSpecularContribution = 0; float3 SpecularContribution = 0; BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT && CLEAR_COAT_BOTTOM_NORMAL) { const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal); N = OctahedronToUnitVector(oct1); } float3 DiffuseLookup = TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, N, AbsoluteDiffuseMip).rgb; NonSpecularContribution += GBuffer.DiffuseColor * DiffuseLookup; // Diffuse { // we want to access the mip with the preconvolved diffuse lighting (coneangle=90 degree) //NonSpecularContribution += GBuffer.DiffuseColor * DiffuseLookup; } // Specular { BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT ) { float ClearCoat = GBuffer.CustomData.x; #if CLEAR_COAT_BOTTOM_NORMAL //const float3 ClearCoatUnderNormal = OctahedronToUnitVector((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 2) - (256.0/255.0)); const float2 oct1 = ((float2(GBuffer.CustomData.a, GBuffer.CustomData.z) * 4) - (512.0/255.0)) + UnitVectorToOctahedron(GBuffer.WorldNormal); const float3 ClearCoatUnderNormal = OctahedronToUnitVector(oct1); float3 R2 = 2 * dot( V, ClearCoatUnderNormal ) * ClearCoatUnderNormal - V; float Mip = ComputeCubemapMipFromRoughness( GBuffer.Roughness, AmbientCubemapMipAdjust.w ); float3 SampleColor = TextureCubeSampleLevel( AmbientCubemap, AmbientCubemapSampler, R2, Mip ).rgb; float2 AB = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rg; SpecularContribution += SampleColor * ( GBuffer.SpecularColor * AB.x + AB.y * (1 - ClearCoat) ); #else float Mip = ComputeCubemapMipFromRoughness( GBuffer.Roughness, AmbientCubemapMipAdjust.w ); float3 SampleColor = TextureCubeSampleLevel( AmbientCubemap, AmbientCubemapSampler, R, Mip ).rgb; float2 AB = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rg; SpecularContribution += SampleColor * ( GBuffer.SpecularColor * AB.x + AB.y * (1 - ClearCoat) ); #endif } else { float Mip = ComputeCubemapMipFromRoughness( GBuffer.Roughness, AmbientCubemapMipAdjust.w ); float3 SampleColor = TextureCubeSampleLevel( AmbientCubemap, AmbientCubemapSampler, R, Mip ).rgb; SpecularContribution += SampleColor * EnvBRDF( GBuffer.SpecularColor, GBuffer.Roughness, NoV ); //SpecularContribution += ApproximateSpecularIBL( Random, GBuffer.SpecularColor, GBuffer.Roughness, GBuffer.WorldNormal, -ScreenVector ); } } BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT ) { const float ClearCoat = GBuffer.CustomData.x; const float ClearCoatRoughness = GBuffer.CustomData.y; float Mip = ComputeCubemapMipFromRoughness( ClearCoatRoughness, AmbientCubemapMipAdjust.w ); float3 SampleColor = TextureCubeSampleLevel( AmbientCubemap, AmbientCubemapSampler, R0, Mip ).rgb; // F_Schlick float F0 = 0.04; float Fc = pow( 1 - NoV, 5 ); float F = Fc + (1 - Fc) * F0; F *= ClearCoat; float LayerAttenuation = (1 - F); NonSpecularContribution *= LayerAttenuation; SpecularContribution *= LayerAttenuation; SpecularContribution += SampleColor * F; } BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR ) { FHairTransmittanceData HairTransmittance = InitHairTransmittanceData(true); float3 FakeNormal = normalize( V - N * dot(V,N) ); FakeNormal = normalize( FakeNormal + 0 * float3(0,0,1) ); SpecularContribution = TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, FakeNormal, AbsoluteDiffuseMip).rgb; SpecularContribution *= PI * HairShading( GBuffer, FakeNormal, V, N, 1, HairTransmittance, 0, 0.2, Random); NonSpecularContribution = 0; } #if IMPORTANCE_SAMPLE if( GBuffer.ShadingModelID > 0 && BufferUV.x > 0.5 ) { NonSpecularContribution = 0; BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_HAIR ) { SpecularContribution = ImageBasedLightingHair( GBuffer, -ScreenVector, GBuffer.WorldNormal, Random ); } else { SpecularContribution = ImageBasedLightingMIS( GBuffer, -ScreenVector, GBuffer.WorldNormal, Random ); } } #endif // apply darkening from ambient occlusion (does not use PostprocessInput1 to set white texture if SSAO is off) float AmbientOcclusion = GBuffer.GBufferAO * AmbientOcclusionTexture.SampleLevel(AmbientOcclusionSampler, BufferUV, 0).r; // Subsurface BRANCH if(GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE || GBuffer.ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) { // some view dependent and some non view dependent (hard coded) float DependentSplit = 0.5f; float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer); // view independent (shared lookup for diffuse for better performance NonSpecularContribution += DiffuseLookup * SubsurfaceColor * (DependentSplit); // view dependent (blurriness is hard coded) SpecularContribution += TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, ScreenVector, AbsoluteDiffuseMip - 2.5f).rgb * SubsurfaceColor * (AmbientOcclusion * (1.0f - DependentSplit)); } FLightAccumulator LightAccumulator = (FLightAccumulator)0; const bool bNeedsSeparateSubsurfaceLightAccumulation = UseSubsurfaceProfile(GBuffer.ShadingModelID); LightAccumulator_Add(LightAccumulator, NonSpecularContribution + SpecularContribution, NonSpecularContribution, AmbientCubemapColor.rgb, bNeedsSeparateSubsurfaceLightAccumulation); OutColor = LightAccumulator_GetResult(LightAccumulator); OutColor *= AmbientOcclusion; } #endif // SUBTRATE_GBUFFER_FORMAT==1