Files
UnrealEngineUWP/Engine/Shaders/ReflectionEnvironmentShaders.usf
Gil Gribb 35cf42566a UE4 - merge GDC branch, code @2465640 to main
[CL 2468685 by Gil Gribb in Main branch]
2015-03-04 08:31:40 -05:00

549 lines
19 KiB
Plaintext

// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
=============================================================================*/
#include "Common.usf"
#include "DeferredShadingCommon.usf"
#include "BRDF.usf"
#include "SHCommon.usf"
#include "ReflectionEnvironmentShared.usf"
#include "MonteCarlo.usf"
#include "SkyLightingShared.usf"
struct FCopyToCubeFaceVSOutput
{
float2 UV : TEXCOORD0;
float3 ScreenVector : TEXCOORD1;
float4 Position : SV_POSITION;
};
void CopyToCubeFaceVS(
in float2 InPosition : ATTRIBUTE0,
in float2 InUV : ATTRIBUTE1,
out FCopyToCubeFaceVSOutput Out
)
{
DrawRectangle(float4(InPosition.xy, 0, 1), InUV, Out.Position, Out.UV);
Out.ScreenVector = mul(float4(Out.Position.xy, 1, 0), View.ScreenToTranslatedWorld).xyz;
}
struct FRouteToCubeFaceGSOut
{
FCopyToCubeFaceVSOutput Vertex;
uint LayerIndex : SV_RenderTargetArrayIndex;
};
int CubeFace;
[maxvertexcount(3)]
void RouteToCubeFaceGS(triangle FCopyToCubeFaceVSOutput Input[3], inout TriangleStream<FRouteToCubeFaceGSOut> OutStream)
{
FRouteToCubeFaceGSOut Vertex0;
Vertex0.Vertex = Input[0];
Vertex0.LayerIndex = CubeFace;
FRouteToCubeFaceGSOut Vertex1;
Vertex1.Vertex = Input[1];
Vertex1.LayerIndex = CubeFace;
FRouteToCubeFaceGSOut Vertex2;
Vertex2.Vertex = Input[2];
Vertex2.LayerIndex = CubeFace;
OutStream.Append(Vertex0);
OutStream.Append(Vertex1);
OutStream.Append(Vertex2);
}
Texture2D InTexture;
SamplerState InTextureSampler;
/**
* X = 0 if capturing sky light, 1 if capturing reflection capture with MaxDistance fade, 2 otherwise,
* Y = Sky distance threshold,
* Z = whether a skylight's lower hemisphere should be black.
*/
float3 SkyLightCaptureParameters;
void CopySceneColorToCubeFaceColorPS(
FCopyToCubeFaceVSOutput Input,
out float4 OutColor : SV_Target0
)
{
float SceneDepth = CalcSceneDepth(Input.UV);
float3 SceneColor = Texture2DSample(InTexture, InTextureSampler, Input.UV).rgb;
// Convert INF's to valid values
SceneColor = min(SceneColor, 65503);
float3 WorldPosition = Input.ScreenVector * SceneDepth + View.ViewOrigin.xyz;
float Alpha = 1;
if (SkyLightCaptureParameters.x == 0)
{
// Assuming we're on a planet and no sky lighting is coming from below the horizon
// This is important to avoid leaking from below since we are integrating incoming lighting and shadowing separately
float AboveHorizonMask = Input.ScreenVector.z > 0 || SkyLightCaptureParameters.z < 1;
SceneColor *= AboveHorizonMask;
}
else if (SkyLightCaptureParameters.x == 1)
{
float RadialDistance = length(WorldPosition - View.ViewOrigin.xyz);
float MaxDistance = SkyLightCaptureParameters.y;
// Setup alpha to fade out smoothly past the max distance
// This allows a local reflection capture to only provide reflections where it has valid data, falls back to sky cubemap
Alpha = 1 - smoothstep(.8f * MaxDistance, MaxDistance, RadialDistance);
}
// Pre-multiplied alpha for correct filtering
OutColor = float4(SceneColor * Alpha, Alpha);
}
float3 GetCubemapVector(float2 ScaledUVs)
{
float3 CubeCoordinates;
//@todo - this could be a 3x3 matrix multiply
if (CubeFace == 0)
{
CubeCoordinates = float3(1, -ScaledUVs.y, -ScaledUVs.x);
}
else if (CubeFace == 1)
{
CubeCoordinates = float3(-1, -ScaledUVs.y, ScaledUVs.x);
}
else if (CubeFace == 2)
{
CubeCoordinates = float3(ScaledUVs.x, 1, ScaledUVs.y);
}
else if (CubeFace == 3)
{
CubeCoordinates = float3(ScaledUVs.x, -1, -ScaledUVs.y);
}
else if (CubeFace == 4)
{
CubeCoordinates = float3(ScaledUVs.x, -ScaledUVs.y, 1);
}
else
{
CubeCoordinates = float3(-ScaledUVs.x, -ScaledUVs.y, -1);
}
return CubeCoordinates;
}
TextureCube SourceTexture;
SamplerState SourceTextureSampler;
void CopyCubemapToCubeFaceColorPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
OutColor = TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, CubeCoordinates, 0);
if (SkyLightCaptureParameters.x > 0)
{
// Assuming we're on a planet and no sky lighting is coming from below the horizon
// This is important to avoid leaking from below since we are integrating incoming lighting and shadowing separately
float AboveHorizonMask = (CubeCoordinates.z > 0) || SkyLightCaptureParameters.z < 1;
OutColor *= AboveHorizonMask;
}
}
int SourceMipIndex;
float4 SampleCubemap(float3 Coordinates)
{
return TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, Coordinates, SourceMipIndex);
}
void DownsamplePS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
OutColor = SampleCubemap(CubeCoordinates);
}
int NumCaptureArrayMips;
/** Cube map array of reflection captures. */
TextureCube ReflectionEnvironmentColorTexture;
SamplerState ReflectionEnvironmentColorSampler;
#if COMPUTEBRIGHTNESS_PIXELSHADER
void ComputeBrightnessMain(
in float4 UVAndScreenPos : TEXCOORD0,
out float4 OutColor : SV_Target0
)
{
// Sample the 6 1x1 cube faces and average
float3 AverageColor = TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(1, 0, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(-1, 0, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 1, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, -1, 0), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, 1), NumCaptureArrayMips - 1).rgb;
AverageColor += TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, float3(0, 0, -1), NumCaptureArrayMips - 1).rgb;
OutColor = dot(AverageColor / 6, .3333f);
}
#endif
#define NUM_FILTER_SAMPLES 1024
Texture2D AverageBrightnessTexture;
SamplerState AverageBrightnessSampler;
void FilterPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
#if ES2_PROFILE
//@todo - mwassmer. Write filtering that works for ES2 so specular will work.
#define FILTER_CUBEMAP 0
#else
#define FILTER_CUBEMAP 1
#endif
#if FILTER_CUBEMAP
float3 N = normalize(CubeCoordinates);
float Roughness = ComputeReflectionCaptureRoughnessFromMip( SourceMipIndex );
float4 FilteredColor = 0;
float Weight = 0;
LOOP
for( int i = 0; i < NUM_FILTER_SAMPLES; i++ )
{
float2 E = Hammersley( i, NUM_FILTER_SAMPLES, 0 );
float3 H = TangentToWorld( ImportanceSampleGGX( E, Roughness ).xyz, N );
float3 L = 2 * dot( N, H ) * H - N;
float NoL = saturate( dot( N, L ) );
if( NoL > 0 )
{
FilteredColor += SampleCubemap(L) * NoL;
Weight += NoL;
}
}
OutColor = FilteredColor / max( Weight, 0.001 );
#else
OutColor = SampleCubemap(CubeCoordinates);
#endif
#if NORMALIZE && ALLOW_STATIC_LIGHTING
// Remove low frequency
// TODO match the frequency removed to the one added. In other words divide out 2nd order SH instead of 1st.
float LowFrequencyBrightness = Texture2DSample(AverageBrightnessTexture, AverageBrightnessSampler, float2(0, 0)).r;
OutColor.rgb /= max(LowFrequencyBrightness, 0.01f);
#endif
}
float4 CoefficientMask0;
float4 CoefficientMask1;
float CoefficientMask2;
int NumSamples;
void DiffuseIrradianceCopyPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float2 ScaledUVs = Input.UV * 2 - 1;
float3 CubeCoordinates = normalize(GetCubemapVector(ScaledUVs));
float SquaredUVs = 1 + dot(ScaledUVs, ScaledUVs);
// Dividing by NumSamples here to keep the sum in the range of fp16, once we get down to the 1x1 mip
float TexelWeight = 4 / (sqrt(SquaredUVs) * SquaredUVs);
FThreeBandSHVector SHCoefficients = SHBasisFunction3(CubeCoordinates);
float CurrentSHCoefficient = dot(SHCoefficients.V0, CoefficientMask0) + dot(SHCoefficients.V1, CoefficientMask1) + SHCoefficients.V2 * CoefficientMask2;
float3 TexelLighting = SampleCubemap(CubeCoordinates).rgb;
OutColor = float4(TexelLighting * CurrentSHCoefficient * TexelWeight, TexelWeight);
}
float4 Sample01;
float4 Sample23;
void DiffuseIrradianceAccumulatePS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float4 AccumulatedValue = 0;
{
float2 ScaledUVs = saturate(Input.UV + Sample01.xy) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample01.zw) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample23.xy) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
{
float2 ScaledUVs = saturate(Input.UV + Sample23.zw) * 2 - 1;
float3 CubeCoordinates = GetCubemapVector(ScaledUVs);
AccumulatedValue += SampleCubemap(CubeCoordinates);
}
OutColor = float4(AccumulatedValue.rgb / 4.0f, AccumulatedValue.a / 4.0f);
}
void AccumulateCubeFacesPS(
FScreenVertexOutput Input,
out float4 OutColor : SV_Target0
)
{
float4 AccumulatedValue = TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(1, 0, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(-1, 0, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(0, 1, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(0, -1, 0), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(0, 0, 1), SourceMipIndex);
AccumulatedValue += TextureCubeSampleLevel(SourceTexture, SourceTextureSampler, float3(0, 0, -1), SourceMipIndex);
OutColor = float4(4 * PI * AccumulatedValue.rgb / ( max(AccumulatedValue.a, .00001f)), 0);
}
// Used during reflection captures to get bounce light from specular surfaces
void SpecularBouncePS(in float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV);
FGBufferData GBuffer = ScreenSpaceData.GBuffer;
// Factors derived from EnvBRDFApprox( SpecularColor, 1, 1 ) == SpecularColor * 0.4524 - 0.0024
OutColor = float4( 0.45 * GBuffer.SpecularColor * GBuffer.IndirectIrradiance, 0 );
}
float4 CapturePositionAndRadius;
float4 CaptureProperties;
float4x4 CaptureBoxTransform;
float4 CaptureBoxScales;
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
TextureCubeArray ReflectionEnvironmentColorTextureArray;
int CaptureArrayIndex;
#endif
/** Standard deferred shading implementation of reflection environment, used in feature level SM4 */
void StandardDeferredReflectionPS(
float4 InScreenPosition : TEXCOORD0,
out float4 OutColor : SV_Target0
)
{
OutColor = 0;
float2 ScreenUV = InScreenPosition.xy / InScreenPosition.w * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(ScreenUV);
BRANCH
// Only light pixels marked as using deferred shading
if (ScreenSpaceData.GBuffer.ShadingModelID > 0)
{
FGBufferData InGBufferData = ScreenSpaceData.GBuffer;
float SceneDepth = CalcSceneDepth(ScreenUV);
float4 HomogeneousWorldPosition = mul(float4(InScreenPosition.xy / InScreenPosition.w * SceneDepth, SceneDepth, 1), View.ScreenToWorld);
float3 WorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w;
float3 CameraToPixel = normalize(WorldPosition - View.ViewOrigin.xyz);
float3 ReflectionVector = reflect(CameraToPixel, InGBufferData.WorldNormal);
float3 ProjectedCaptureVector = ReflectionVector;
float DistanceAlpha = 0;
//@todo - find a way to share this code with the compute shader version
#if SPHERE_CAPTURE
float3 RayDirection = ReflectionVector;
float ProjectionSphereRadius = CapturePositionAndRadius.w * 1.2f;
float SphereRadiusSquared = ProjectionSphereRadius * ProjectionSphereRadius;
float3 ReceiverToSphereCenter = WorldPosition - CapturePositionAndRadius.xyz;
float ReceiverToSphereCenterSq = dot(ReceiverToSphereCenter, ReceiverToSphereCenter);
float3 CaptureVector = WorldPosition - CapturePositionAndRadius.xyz;
float CaptureVectorLength = sqrt(dot(CaptureVector, CaptureVector));
float NormalizedDistanceToCapture = saturate(CaptureVectorLength / CapturePositionAndRadius.w);
// Find the intersection between the ray along the reflection vector and the capture's sphere
float3 QuadraticCoef;
QuadraticCoef.x = 1;
QuadraticCoef.y = 2 * dot(RayDirection, ReceiverToSphereCenter);
QuadraticCoef.z = ReceiverToSphereCenterSq - SphereRadiusSquared;
float Determinant = QuadraticCoef.y * QuadraticCoef.y - 4 * QuadraticCoef.z;
BRANCH
// Only continue if the ray intersects the sphere
if (Determinant >= 0)
{
float FarIntersection = (sqrt(Determinant) - QuadraticCoef.y) * 0.5;
float3 IntersectPosition = WorldPosition + FarIntersection * RayDirection;
ProjectedCaptureVector = IntersectPosition - CapturePositionAndRadius.xyz;
// Fade out based on distance to capture
DistanceAlpha = 1.0 - smoothstep(.6, 1, NormalizedDistanceToCapture);
}
#elif BOX_CAPTURE
float3 RayDirection = ReflectionVector * CapturePositionAndRadius.w * 2;
// Transform the ray into the local space of the box, where it is an AABB with mins at -1 and maxs at 1
float3 LocalRayStart = mul(float4(WorldPosition, 1), CaptureBoxTransform).xyz;
float3 LocalRayDirection = mul(RayDirection, (float3x3)CaptureBoxTransform);
// Intersections.y is the intersection with the far side of the box
float2 Intersections = LineBoxIntersect(LocalRayStart, LocalRayStart + LocalRayDirection, -1, 1);
{
// Compute the reprojected vector
float3 IntersectPosition = WorldPosition + Intersections.y * RayDirection;
ProjectedCaptureVector = IntersectPosition - CapturePositionAndRadius.xyz;
// Compute the distance from the receiving pixel to the box for masking
// Apply local to world scale to take scale into account without transforming back to world space
// Shrink the box by the transition distance (BoxScales.w) so that the fade happens inside the box influence area
float4 BoxScales = CaptureBoxScales;
float BoxDistance = ComputeDistanceFromBoxToPoint(-(BoxScales.xyz - .5f * BoxScales.w), BoxScales.xyz - .5f * BoxScales.w, LocalRayStart * BoxScales.xyz);
// Setup a fade based on receiver distance to the box, hides the box influence shape
float BoxDistanceAlpha = 1.0 - smoothstep(0, .7f * BoxScales.w, BoxDistance);
// Setup a fade based on reflection ray intersection distance, hides the discontinuity between rays that just barely
float RayDistanceAlpha = smoothstep(0, BoxScales.w, Intersections.y * CapturePositionAndRadius.w * 2);
DistanceAlpha = BoxDistanceAlpha * RayDistanceAlpha;
}
#endif
BRANCH
if (DistanceAlpha > 0)
{
float AbsoluteSpecularMip = ComputeReflectionCaptureMipFromRoughness(InGBufferData.Roughness);
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
float4 Reflection = ReflectionEnvironmentColorTextureArray.SampleLevel(ReflectionEnvironmentColorSampler, float4(ProjectedCaptureVector, CaptureArrayIndex), AbsoluteSpecularMip);
#else
float4 Reflection = TextureCubeSampleLevel(ReflectionEnvironmentColorTexture, ReflectionEnvironmentColorSampler, ProjectedCaptureVector, AbsoluteSpecularMip);
#endif
Reflection.rgb *= CaptureProperties.r;
// Composite using the influence shape's alpha, which allows it to only override where it has valid data
// Reflection texture was premultiplied by reflection alpha
OutColor = Reflection * DistanceAlpha;
}
}
}
Texture2D ReflectionEnvTexture;
SamplerState ReflectionEnvSampler;
Texture2D ScreenSpaceReflectionsTexture;
SamplerState ScreenSpaceReflectionsSampler;
/** Used to apply reflection capture contribution along with SSR to scene color. */
void ReflectionApplyPS(
in float4 UVAndScreenPos : TEXCOORD0,
out float4 OutColor : SV_Target0)
{
float2 UV = UVAndScreenPos.xy;
FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(UV);
FGBufferData GBuffer = ScreenSpaceData.GBuffer;
OutColor = 0;
BRANCH
// Only light pixels marked as lit
if( GBuffer.ShadingModelID > 0 )
{
// TODO if SpecularColor and Roughness were in same texture would remove 2 fetches
float3 V = -normalize( mul( float4(UVAndScreenPos.zw, 1, 0), View.ScreenToWorld ).xyz );
float NoV = saturate( dot( GBuffer.WorldNormal, V ) );
float3 SpecularColor = EnvBRDF( GBuffer.SpecularColor, GBuffer.Roughness, NoV );
float AO = GBuffer.GBufferAO * ScreenSpaceData.AmbientOcclusion;
float SpecularOcclusion = saturate( Square( NoV + AO ) - 1 + AO );
SpecularColor *= SpecularOcclusion;
float4 SpecularLighting = float4(0, 0, 0, 1);
#if APPLY_REFLECTION_ENV
// Alpha of the reflection environment texture contains coverage, where 0 the sky reflection should be applied
SpecularLighting = Texture2DSample( ReflectionEnvTexture, ReflectionEnvSampler, UV );
#if ALLOW_STATIC_LIGHTING
FLATTEN
if( View.UseLightmaps > 0 )
{
// We have high frequency directional data but low frequency spatial data in the envmap.
// We have high frequency spatial data but low frequency directional data in the lightmap.
// So, we combine the two for the best of both. This is done by removing the low spatial frequencies from the envmap and replacing them with the lightmap data.
// This is only done with luma so as to not get odd color shifting.
// Note: make sure this matches the lightmap mixing done for translucency (BasePassPixelShader.usf)
SpecularLighting.rgb *= GBuffer.IndirectIrradiance;
}
#endif
#endif
#if APPLY_SKYLIGHT
BRANCH
if (SpecularLighting.a > .001f)
{
float3 ReflectionVector = reflect(-V, GBuffer.WorldNormal);
// Normalize for static skylight types which mix with lightmaps
bool bNormalize = SkyLightParameters.z < 1 && View.UseLightmaps > 0 && !ALLOW_STATIC_LIGHTING;
float3 SkyLighting = GetSkyLightReflection(ReflectionVector, GBuffer.Roughness, bNormalize);
FLATTEN
if (bNormalize)
{
SkyLighting *= GBuffer.IndirectIrradiance;
}
float Visibility = GetDistanceFieldAOSpecularOcclusion(UV, ReflectionVector, GBuffer.Roughness, GBuffer.ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE);
SpecularLighting.rgb += (Visibility * SpecularLighting.a) * SkyLighting;
}
#endif
#if APPLY_SSR
float4 SSR = Texture2DSample( ScreenSpaceReflectionsTexture, ScreenSpaceReflectionsSampler, UV );
SpecularLighting.rgb = SpecularLighting.rgb * (1 - SSR.a) + SSR.rgb;
#endif
OutColor.rgb = SpecularLighting.rgb * SpecularColor;
// Transform NaNs to black, transform negative colors to black.
OutColor.rgb = -min(-OutColor.rgb, 0.0);
}
}