// 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; } int CubeFace; 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) // NOTE: this breaks LPV specular if r.allowstaticlighting is enabled SpecularLighting.rgb *= GBuffer.IndirectIrradiance; } #endif #endif #if APPLY_SSR float4 SSR = Texture2DSample( ScreenSpaceReflectionsTexture, ScreenSpaceReflectionsSampler, UV ); #endif BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT ) { const float ClearCoat = GBuffer.CustomData.x; const float ClearCoatRoughness = GBuffer.CustomData.y; // TODO EnvBRDF should have a mask param float2 AB = PreIntegratedGF.SampleLevel( PreIntegratedGFSampler, float2( NoV, GBuffer.Roughness ), 0 ).rg; SpecularColor = GBuffer.SpecularColor * AB.x + AB.y * saturate( 50 * GBuffer.SpecularColor.g ) * (1 - ClearCoat); // F_Schlick float F0 = 0.04; float Fc = Pow5( 1 - NoV ); float F = Fc + (1 - Fc) * F0; F *= ClearCoat; float LayerAttenuation = (1 - F); SpecularColor *= LayerAttenuation; SpecularColor *= SpecularOcclusion; #if APPLY_SSR SpecularLighting.rgb *= lerp( 1 - SSR.a, 1, ClearCoat ); SpecularLighting.rgb += lerp( SSR.rgb, 0, ClearCoat ); #endif SpecularLighting.rgb *= SpecularColor; #if APPLY_SSR SpecularLighting.rgb += SSR.rgb * F; #endif } else { #if APPLY_SSR SpecularLighting.rgb = SSR.rgb + SpecularLighting.rgb * (1 - SSR.a); #endif SpecularLighting.rgb *= SpecularColor; } #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 = GetSkyLightReflectionSupportingBlend(ReflectionVector, GBuffer.Roughness, bNormalize); SkyLighting *= SpecularColor; BRANCH if( GBuffer.ShadingModelID == SHADINGMODELID_CLEAR_COAT ) { const float ClearCoat = GBuffer.CustomData.x; const float ClearCoatRoughness = GBuffer.CustomData.y; // F_Schlick float F0 = 0.04; float Fc = Pow5( 1 - NoV ); float F = Fc + (1 - Fc) * F0; F *= ClearCoat; #if APPLY_SSR F *= 1 - SSR.a; #endif float3 SampleColor = GetSkyLightReflectionSupportingBlend(ReflectionVector, ClearCoatRoughness, bNormalize); SkyLighting += SampleColor * F; } float DirectionalOcclusion = ScreenSpaceData.DirectionalOcclusion.g; SkyLighting *= DirectionalOcclusion; 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 OutColor.rgb = SpecularLighting.rgb; // Transform NaNs to black, transform negative colors to black. OutColor.rgb = -min(-OutColor.rgb, 0.0); } }