Files
UnrealEngineUWP/Engine/Shaders/PostProcessAmbient.usf
Martin Mittring b58ae4285d * added new shading model Subsurface_Profile, can specify SubsurfaceProfile asset on the material, create it in content browser
no backscattering yet, might replace Preintegrated and Subsurface shading models, can be optimized, postprocess pass only runs if an object on the screen is using it, uses SeparableSSS by Jorge Jimenez and Diego Gutierrez

[CL 2236313 by Martin Mittring in Main branch]
2014-07-29 17:33:28 -04:00

508 lines
15 KiB
Plaintext

// Copyright 1998-2014 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, float3 N )
{
float3 DiffuseLighting = 0;
const uint NumSamples = 32;
for( uint i = 0; i < NumSamples; i++ )
{
float2 E = Hammersley( i, NumSamples, Random );
float3 L = TangentToWorld( CosineSampleHemisphere( E ).xyz, N );
float NoL = saturate( dot( N, L ) );
if( NoL > 0 )
{
float3 SampleColor = AmbientCubemap.SampleLevel( AmbientCubemapSampler, L, 0 ).rgb;
// lambert = DiffuseColor * NoL / PI
// pdf = NoL / PI
DiffuseLighting += SampleColor * DiffuseColor;
}
}
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_Smith( 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_Schlick( Roughness, NoV, NoL );
float3 F = F_Schlick( GBuffer.SpecularColor, VoH );
//float k = Square( Roughness ) * 0.25;
//float Vis = 0.25 / Square( VoH * (1 - k) + k );
//float Vis = 0.25 / ( VoH * VoH * (1 - k*k) + k*k );
//float Vis = Vis_Smith( Roughness, NoV, NoL );
return Diffuse_Lambert( GBuffer.DiffuseColor ) + (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,
16,
};
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 );
}
float2 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;
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_Schlick( Roughness, NoV, NoL );
// 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;
}
}
return float2( A, B ) / 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);
float NoV = saturate( dot( GBuffer.WorldNormal, -ScreenVector ) );
float3 R = reflect( ScreenVector, GBuffer.WorldNormal );
uint2 Random = ScrambleTEA( PixelPos );
Random.x ^= View.Random;
Random.y ^= View.Random;
float3 NonSpecularContribution = 0;
float3 SpecularContribution = 0;
float3 DiffuseLookup = TextureCubeSampleLevel(AmbientCubemap, AmbientCubemapSampler, GBuffer.WorldNormal, AbsoluteDiffuseMip).rgb;
// 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 );
}
}
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;
}