Files
UnrealEngineUWP/Engine/Shaders/PostProcessAmbient.usf
Brian Karis bf6fa8d6db Switched diffuse shading model from Lambert to energy conserving Burley.
Lighting done in the bass pass does not properly use the new shading model yet. All dynamic lighting does: deferred direct lights, ambient cubemap, and dynamic skylight.

[CL 2411324 by Brian Karis in Main branch]
2015-01-19 15:48:04 -05:00

568 lines
17 KiB
Plaintext

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostprocessAmbient.usf: To apply a ambient cubemap as a postprocess
=============================================================================*/
#include "Common.usf"
#include "PostProcessCommon.usf"
#include "DeferredShadingCommon.usf"
#include "CubemapCommon.usf"
#include "Random.usf"
#include "BRDF.usf"
#include "MonteCarlo.usf"
#define IMPORTANCE_SAMPLE 0
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, 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( 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 StandardShading( FGBufferData GBuffer, float Roughness, float3 L, float3 V, half3 N )
{
float3 H = normalize(V + L);
float NoL = saturate( dot(N, L) );
float NoV = saturate( dot(N, V) );
float NoH = saturate( dot(N, H) );
float VoH = saturate( dot(V, H) );
// Generalized microfacet specular
float D = D_GGX( Roughness, NoH );
float Vis = Vis_SmithJointApprox( Roughness, NoV, NoL );
float3 F = F_Schlick( GBuffer.SpecularColor, VoH );
float3 Diffuse = Diffuse_Burley( GBuffer.DiffuseColor, Roughness, NoV, NoL, VoH );
return Diffuse + (D * Vis) * F;
}
float3 ClearCoatShading( FGBufferData GBuffer, float Roughness, float3 L, float3 V, half3 N )
{
const float ClearCoat = GBuffer.CustomData.x;
const float ClearCoatRoughness = GBuffer.CustomData.y;
const float Film = 1 * ClearCoat;
const float MetalSpec = 0.9;
#if 0
float3 H = normalize(V + L);
float NoL = saturate( dot(N, L) );
float NoV = saturate( dot(N, V) );
float NoH = saturate( dot(N, H) );
float VoH = saturate( dot(V, H) );
// Generalized microfacet specular
float D = D_GGX( ClearCoatRoughness, NoH );
float Vis = Vis_Kelemen( VoH );
// F_Schlick
float F0 = 0.04;
float Fc = pow( 1 - VoH, 5 );
float F = Fc + (1 - Fc) * F0;
F *= ClearCoat;
float Fr1 = D * Vis * F;
float LayerAttenuation = (1 - F);
// Generalized microfacet specular
float D2 = D_GGX( Roughness, NoH );
float Vis2 = Vis_Schlick( Roughness, NoV, NoL );
float3 F2 = saturate( 50.0 * GBuffer.SpecularColor.g ) * Fc + (1 - Fc) * GBuffer.SpecularColor;
float3 Fr2 = Diffuse_Lambert( GBuffer.DiffuseColor ) + (D2 * Vis2) * F2;
return Fr1 + Fr2 * LayerAttenuation;
#else
float3 H = normalize(V + L);
float NoL = saturate( dot(N, L) );
float NoV = saturate( dot(N, V) );
float NoH = saturate( dot(N, H) );
float VoH = saturate( dot(V, H) );
// Hard coded IOR of 1.5
// Generalized microfacet specular
float D = D_GGX( ClearCoatRoughness, NoH );
float Vis = Vis_Kelemen( VoH );
// F_Schlick
float F0 = 0.04;
float Fc = pow( 1 - VoH, 5 );
float F = Fc + (1 - Fc) * F0;
float Fr1 = D * Vis * F;
#if 1
// Refract rays
float3 L2 = refract( -L, -H, 1 / 1.5 );
float3 V2 = refract( -V, -H, 1 / 1.5 );
// LoH == VoH
float RefractBlend = sqrt( 4 * VoH*VoH + 5 ) / 3 + 2.0 / 3 * VoH;
//float3 L2 = RefractBlend * H - L / 1.5;
//float3 V2 = RefractBlend * H - V / 1.5;
float3 H2 = normalize( V2 + L2 );
float NoL2 = saturate( dot(N, L2) );
float NoV2 = saturate( dot(N, V2) );
float NoH2 = saturate( dot(N, H2) );
float VoH2 = saturate( dot(V2, H2) );
#else
// Approximation
float RefractBlend = (0.22 * VoH + 0.7) * VoH + 0.745; // 2 mad
// Dot products distribute. No need for L2 and V2.
float RefractNoH = RefractBlend * NoH; // 1 mul
float NoL2 = saturate( RefractNoH - (1 / 1.5) * NoL ); // 1 mad
float NoV2 = saturate( RefractNoH - (1 / 1.5) * NoV ); // 1 mad
// Should refract H too but unimportant
#endif
NoL2 = max( 0.001, NoL2 );
NoV2 = max( 0.001, NoV2 );
float3 AbsorptionColor = (1 - Film) + GBuffer.BaseColor * ( Film * (1 / MetalSpec) );
float AbsorptionDist = rcp(NoV2) + rcp(NoL2);
float3 Absorption = pow( AbsorptionColor, 0.5 * AbsorptionDist );
// Approximation
//float AbsorptionDist = ( NoV2 + NoL2 ) / ( NoV2 * NoL2 );
//float3 Absorption = AbsorptionColor * ( AbsorptionColor * (AbsorptionDist * 0.5 - 1) + (2 - 0.5 * AbsorptionDist) );
//float3 Absorption = AbsorptionColor + AbsorptionColor * (AbsorptionColor - 1) * (AbsorptionDist * 0.5 - 1); // use for shared version
#if 1
float F21 = F_Schlick( 0.04, saturate( dot(V2, H) ) );
float k = Square( Roughness ) * 0.5;
float G_SchlickV2 = NoV2 / ( NoV2 * (1 - k) + k );
float G_SchlickL2 = NoL2 / ( NoL2 * (1 - k) + k );
float TotalInternalReflection = 1 - F21 * G_SchlickV2 * G_SchlickL2;
float3 LayerAttenuation = ( (1 - F) * TotalInternalReflection ) * Absorption;
#else
// Approximation
float3 LayerAttenuation = (1 - F) * Absorption;
#endif
// Approximation for IOR == 1.5
//SpecularColor = ChangeBaseMedium( SpecularColor, 1.5 );
//SpecularColor = saturate( ( 0.55 * SpecularColor + (0.45 * 1.08) ) * SpecularColor - (0.45 * 0.08) );
// Treat SpecularColor as relative to IOR. Artist compensates.
// Generalized microfacet specular
#if 1
float D2 = D_GGX( Roughness, NoH2 );
float Vis2 = Vis_Schlick( Roughness, NoV2, NoL2 );
float3 F2 = F_Schlick( MetalSpec, VoH2 );
#else
// Approximation
float D2 = D_GGX( Roughness, NoH );
float Vis2 = Vis_Schlick( Roughness, NoV2, NoL2 );
float3 F2 = F_Schlick( GBuffer.SpecularColor, VoH );
#endif
float3 Fr2 = Diffuse_Lambert( GBuffer.DiffuseColor ) + (D2 * Vis2) * F2;
return Fr1 + Fr2 * LayerAttenuation;
#endif
}
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, Roughness1 ).xyz, N );
L = 2 * dot( V, H ) * H - V;
}
else
{
H = TangentToWorld( ImportanceSampleGGX( E, 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( Roughness1, NoH ) * NoH / (4 * VoH),
D_GGX( 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 );
float3 Shading = 0;
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT )
{
Shading = ClearCoatShading( GBuffer, GBuffer.Roughness, L, V, N );
}
else
{
Shading = StandardShading( GBuffer, GBuffer.Roughness, L, V, N );
}
Lighting += SampleColor * Shading * ( NoL * Weight );
}
}
}
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, 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, 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, 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 Vis = Vis_SmithJointApprox( Roughness, NoV, NoL );
float a = Square( Roughness );
float a2 = a*a;
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 );
return PrefilteredColor * ( SpecularColor * AB.x + AB.y );
}
void MainPS(in float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV);
FGBufferData GBuffer = ScreenSpaceData.GBuffer;
float AbsoluteDiffuseMip = AmbientCubemapMipAdjust.z;
// screen position in [-1, 1] screen space
float2 ScreenSpacePos = UVAndScreenPos.zw;
int2 PixelPos = int2(UVAndScreenPos.zw * ScreenPosToPixel.xy + ScreenPosToPixel.zw + 0.5f);
float3 ScreenVector = normalize(mul(float4(ScreenSpacePos, 1, 0), View.ScreenToWorld).xyz);
float3 N = GBuffer.WorldNormal;
float3 V = -ScreenVector;
float3 R = 2 * dot( V, N ) * N - V;
float NoV = abs( dot(N, V) ) + 1e-5;
// Point lobe in off-specular peak direction
float a = Square( GBuffer.Roughness );
R = lerp( N, R, (1 - a) * ( sqrt(1 - a) + a ) );
uint2 Random = ScrambleTEA( PixelPos );
Random.x ^= View.Random;
Random.y ^= View.Random;
float3 NonSpecularContribution = 0;
float3 SpecularContribution = 0;
#if 1
float3 RetroReflectionDir = lerp( N, V, saturate( ( NoV * (1.02341 * a - 1.51174) + -0.511705 * a + 0.755868 ) * a ) );
float3 DiffuseLookup = TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, RetroReflectionDir, AbsoluteDiffuseMip).rgb;
float3 ABC = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rgb;
//float3 ABC = IntegrateBRDF( Random, GBuffer.Roughness, NoV );
NonSpecularContribution += GBuffer.DiffuseColor * DiffuseLookup * ABC.z;
//NonSpecularContribution += DiffuseIBL( Random, GBuffer.DiffuseColor, GBuffer.Roughness, N, V );// * ABC.z;
#else
float3 DiffuseLookup = TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, N, AbsoluteDiffuseMip).rgb;
NonSpecularContribution += GBuffer.DiffuseColor * DiffuseLookup;
#endif
// Diffuse
{
// we want to access the mip with the preconvolved diffuse lighting (coneangle=90 degree)
//NonSpecularContribution += GBuffer.DiffuseColor * DiffuseLookup;
}
// Specular
{
float Mip = ComputeCubemapMipFromRoughness( GBuffer.Roughness, AmbientCubemapMipAdjust.w );
float3 SampleColor = TextureCubeSampleLevel( AmbientCubemap, AmbientCubemapSampler, R, Mip ).rgb;
BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT )
{
float ClearCoat = GBuffer.CustomData.x;
float2 AB = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rg;
SpecularContribution += SampleColor * ( GBuffer.SpecularColor * AB.x + AB.y * (1 - ClearCoat) );
}
else
{
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, R, 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;
}
#if IMPORTANCE_SAMPLE
if( GBuffer.ShadingModelID > 0 )
{
NonSpecularContribution = 0;
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 * ScreenSpaceData.AmbientOcclusion;
// 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;
LightAccumulator_Add(LightAccumulator, NonSpecularContribution, SpecularContribution, AmbientCubemapColor.rgb);
OutColor = LightAccumulator_GetResult(LightAccumulator);
OutColor *= AmbientOcclusion;
}