233 lines
11 KiB
HLSL
233 lines
11 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#if (FEATURE_LEVEL <= FEATURE_LEVEL_ES3_1)
|
|
#define SkyIrradianceEnvironmentMap View.MobileSkyIrradianceEnvironmentMap
|
|
#else
|
|
#define SkyIrradianceEnvironmentMap View.SkyIrradianceEnvironmentMap
|
|
#endif
|
|
|
|
float GetSkyLightCubemapBrightness()
|
|
{
|
|
return SkyIrradianceEnvironmentMap[7].x; // Refer to FSceneRenderer::UpdateSkyIrradianceGpuBuffer for more details.
|
|
}
|
|
|
|
#define REFLECTION_CAPTURE_ROUGHEST_MIP 1
|
|
#define REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE 1.2
|
|
|
|
// This sampler cannot be shared because they come from the texture assets setup by users. We could enforce those to be min/mag/mip trilinear filtering.
|
|
#define SharedSkyLightCubemapSampler ReflectionStruct.SkyLightCubemapSampler
|
|
#define SharedSkyLightBlendDestinationCubemapSampler ReflectionStruct.SkyLightBlendDestinationCubemapSampler
|
|
|
|
/**
|
|
* Compute absolute mip for a reflection capture cubemap given a roughness.
|
|
*/
|
|
half ComputeReflectionCaptureMipFromRoughness(half Roughness, half CubemapMaxMip)
|
|
{
|
|
// Heuristic that maps roughness to mip level
|
|
// This is done in a way such that a certain mip level will always have the same roughness, regardless of how many mips are in the texture
|
|
// Using more mips in the cubemap just allows sharper reflections to be supported
|
|
half LevelFrom1x1 = REFLECTION_CAPTURE_ROUGHEST_MIP - REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE * log2(max(Roughness, 0.001));
|
|
return CubemapMaxMip - 1 - LevelFrom1x1;
|
|
}
|
|
|
|
float ComputeReflectionCaptureRoughnessFromMip(float Mip, half CubemapMaxMip)
|
|
{
|
|
float LevelFrom1x1 = CubemapMaxMip - 1 - Mip;
|
|
return exp2( ( REFLECTION_CAPTURE_ROUGHEST_MIP - LevelFrom1x1 ) / REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE );
|
|
}
|
|
|
|
// ReflectionStruct.SkyLightParameters: X = max mip, Y = 1 if sky light should be rendered, 0 otherwise, Z = 1 if sky light is dynamic, 0 otherwise, W = blend fraction.
|
|
|
|
float3 GetSkyLightReflection(float3 ReflectionVector, float Roughness, out float OutSkyAverageBrightness)
|
|
{
|
|
float AbsoluteSpecularMip = ComputeReflectionCaptureMipFromRoughness(Roughness, ReflectionStruct.SkyLightParameters.x);
|
|
float3 Reflection = TextureCubeSampleLevel(ReflectionStruct.SkyLightCubemap, SharedSkyLightCubemapSampler, ReflectionVector, AbsoluteSpecularMip).rgb;
|
|
|
|
OutSkyAverageBrightness = GetSkyLightCubemapBrightness() * Luminance(View.SkyLightColor.rgb);
|
|
return Reflection * View.SkyLightColor.rgb;
|
|
}
|
|
|
|
float3 GetSkyLightReflectionSupportingBlend(float3 ReflectionVector, float Roughness, out float OutSkyAverageBrightness)
|
|
{
|
|
float3 Reflection = GetSkyLightReflection(ReflectionVector, Roughness, OutSkyAverageBrightness);
|
|
|
|
BRANCH
|
|
if (ReflectionStruct.SkyLightParameters.w > 0)
|
|
{
|
|
float AbsoluteSpecularMip = ComputeReflectionCaptureMipFromRoughness(Roughness, ReflectionStruct.SkyLightParameters.x);
|
|
float3 BlendDestinationReflection = TextureCubeSampleLevel(ReflectionStruct.SkyLightBlendDestinationCubemap, SharedSkyLightBlendDestinationCubemapSampler, ReflectionVector, AbsoluteSpecularMip).rgb;
|
|
|
|
Reflection = lerp(Reflection, BlendDestinationReflection * View.SkyLightColor.rgb, ReflectionStruct.SkyLightParameters.w);
|
|
}
|
|
|
|
return Reflection;
|
|
}
|
|
|
|
bool ShouldSkyLightApplyPrecomputedBentNormalShadowing() {
|
|
return View.SkyLightApplyPrecomputedBentNormalShadowingFlag != 0.0f;
|
|
}
|
|
|
|
bool ShouldSkyLightAffectReflection() {
|
|
return View.SkyLightAffectReflectionFlag != 0.0f;
|
|
}
|
|
|
|
bool ShouldSkyLightAffectGlobalIllumination() {
|
|
return View.SkyLightAffectGlobalIlluminationFlag != 0.0f;
|
|
}
|
|
|
|
/**
|
|
* Computes sky diffuse lighting from the SH irradiance map.
|
|
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
|
|
*/
|
|
float3 GetSkySHDiffuse(float3 Normal)
|
|
{
|
|
float4 NormalVector = float4(Normal, 1.0f);
|
|
|
|
float3 Intermediate0, Intermediate1, Intermediate2;
|
|
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
|
|
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
|
|
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
|
|
|
|
float4 vB = NormalVector.xyzz * NormalVector.yzzx;
|
|
Intermediate1.x = dot(SkyIrradianceEnvironmentMap[3], vB);
|
|
Intermediate1.y = dot(SkyIrradianceEnvironmentMap[4], vB);
|
|
Intermediate1.z = dot(SkyIrradianceEnvironmentMap[5], vB);
|
|
|
|
float vC = NormalVector.x * NormalVector.x - NormalVector.y * NormalVector.y;
|
|
Intermediate2 = SkyIrradianceEnvironmentMap[6].xyz * vC;
|
|
|
|
// max to not get negative colors
|
|
return max(0, Intermediate0 + Intermediate1 + Intermediate2);
|
|
}
|
|
|
|
/**
|
|
* Computes sky diffuse lighting from the SH irradiance map.
|
|
* This has the SH basis evaluation and diffuse convolution weights combined for minimal ALU's - see "Stupid Spherical Harmonics (SH) Tricks"
|
|
* Only does the first 3 components for speed.
|
|
*/
|
|
float3 GetSkySHDiffuseSimple(float3 Normal)
|
|
{
|
|
float4 NormalVector = float4(Normal, 1);
|
|
|
|
float3 Intermediate0;
|
|
Intermediate0.x = dot(SkyIrradianceEnvironmentMap[0], NormalVector);
|
|
Intermediate0.y = dot(SkyIrradianceEnvironmentMap[1], NormalVector);
|
|
Intermediate0.z = dot(SkyIrradianceEnvironmentMap[2], NormalVector);
|
|
|
|
// max to not get negative colors
|
|
return max(0, Intermediate0);
|
|
}
|
|
|
|
|
|
// Point lobe in off-specular peak direction
|
|
half3 GetOffSpecularPeakReflectionDir(half3 Normal, half3 ReflectionVector, half Roughness)
|
|
{
|
|
half a = Roughness * Roughness;
|
|
return lerp( Normal, ReflectionVector, (1 - a) * ( sqrt(1 - a) + a ) );
|
|
}
|
|
|
|
half GetSpecularOcclusion(half NoV, half RoughnessSq, half AO)
|
|
{
|
|
return saturate( pow( NoV + AO, RoughnessSq ) - 1 + AO );
|
|
}
|
|
|
|
float3 GetLookupVectorForBoxCapture(float3 ReflectionVector, float3 WorldPosition, float4 BoxCapturePositionAndRadius, float4x4 RelativeWorldToBox, float4 BoxScales, float3 LocalCaptureOffset, out float DistanceAlpha)
|
|
{
|
|
// 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 - BoxCapturePositionAndRadius.xyz, 1), RelativeWorldToBox).xyz;
|
|
float3 LocalRayDirection = mul(float4(ReflectionVector, 0), RelativeWorldToBox).xyz;
|
|
|
|
float3 InvRayDir = rcp(LocalRayDirection);
|
|
|
|
//find the ray intersection with each of the 3 planes defined by the minimum extrema.
|
|
float3 FirstPlaneIntersections = -InvRayDir - LocalRayStart * InvRayDir;
|
|
//find the ray intersection with each of the 3 planes defined by the maximum extrema.
|
|
float3 SecondPlaneIntersections = InvRayDir - LocalRayStart * InvRayDir;
|
|
//get the furthest of these intersections along the ray
|
|
float3 FurthestPlaneIntersections = max(FirstPlaneIntersections, SecondPlaneIntersections);
|
|
|
|
//clamp the intersections to be between RayOrigin and RayEnd on the ray
|
|
float Intersection = min(FurthestPlaneIntersections.x, min(FurthestPlaneIntersections.y, FurthestPlaneIntersections.z));
|
|
|
|
// Compute the reprojected vector
|
|
float3 IntersectPosition = WorldPosition + Intersection * ReflectionVector;
|
|
float3 ProjectedCaptureVector = IntersectPosition - (BoxCapturePositionAndRadius.xyz + LocalCaptureOffset);
|
|
|
|
// 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
|
|
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
|
|
DistanceAlpha = 1.0 - smoothstep(0, .7f * BoxScales.w, BoxDistance);
|
|
|
|
return ProjectedCaptureVector;
|
|
}
|
|
|
|
float3 GetLookupVectorForSphereCapture(float3 ReflectionVector, float3 WorldPosition, float4 SphereCapturePositionAndRadius, float NormalizedDistanceToCapture, float3 LocalCaptureOffset, inout float DistanceAlpha)
|
|
{
|
|
float3 ProjectedCaptureVector = ReflectionVector;
|
|
float ProjectionSphereRadius = SphereCapturePositionAndRadius.w;
|
|
float SphereRadiusSquared = ProjectionSphereRadius * ProjectionSphereRadius;
|
|
|
|
float3 LocalPosition = WorldPosition - SphereCapturePositionAndRadius.xyz;
|
|
float LocalPositionSqr = dot(LocalPosition, LocalPosition);
|
|
|
|
// Find the intersection between the ray along the reflection vector and the capture's sphere
|
|
float3 QuadraticCoef;
|
|
QuadraticCoef.x = 1;
|
|
QuadraticCoef.y = dot(ReflectionVector, LocalPosition);
|
|
QuadraticCoef.z = LocalPositionSqr - SphereRadiusSquared;
|
|
|
|
float Determinant = QuadraticCoef.y * QuadraticCoef.y - QuadraticCoef.z;
|
|
|
|
// Only continue if the ray intersects the sphere
|
|
FLATTEN
|
|
if (Determinant >= 0)
|
|
{
|
|
float FarIntersection = sqrt(Determinant) - QuadraticCoef.y;
|
|
|
|
float3 LocalIntersectionPosition = LocalPosition + FarIntersection * ReflectionVector;
|
|
ProjectedCaptureVector = LocalIntersectionPosition - LocalCaptureOffset;
|
|
// Note: some compilers don't handle smoothstep min > max (this was 1, .6)
|
|
//DistanceAlpha = 1.0 - smoothstep(.6, 1, NormalizedDistanceToCapture);
|
|
|
|
float x = saturate( 2.5 * NormalizedDistanceToCapture - 1.5 );
|
|
DistanceAlpha = 1 - x*x*(3 - 2*x);
|
|
}
|
|
return ProjectedCaptureVector;
|
|
}
|
|
|
|
half ComputeMixingWeight(half IndirectIrradiance, half AverageBrightness, half Roughness)
|
|
{
|
|
// Mirror surfaces should have no mixing, so they match reflections from other sources (SSR, planar reflections)
|
|
half MixingAlpha = smoothstep(0, 1, saturate(Roughness * View.ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight.x + View.ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight.y));
|
|
|
|
// 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.
|
|
half MixingWeight = IndirectIrradiance / max(AverageBrightness, .0001f);
|
|
|
|
MixingWeight = min(MixingWeight, View.ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight.z);
|
|
|
|
return lerp(1.0f, MixingWeight, MixingAlpha);
|
|
}
|
|
|
|
#if ES3_1_PROFILE
|
|
#define GetReflectionPositionAndRadius(CaptureIndex) ReflectionCaptureES31.PositionHighAndRadius[CaptureIndex]
|
|
#define GetReflectionCaptureProperties(CaptureIndex) ReflectionCaptureES31.CaptureProperties[CaptureIndex]
|
|
#define GetReflectionCaptureOffsetAndAverageBrightness(CaptureIndex) ReflectionCaptureES31.CaptureOffsetAndAverageBrightness[CaptureIndex]
|
|
#define GetReflectionBoxTransform(CaptureIndex) ReflectionCaptureES31.BoxTransform[CaptureIndex]
|
|
#define GetReflectionBoxScales(CaptureIndex) ReflectionCaptureES31.BoxScales[CaptureIndex]
|
|
#define GetReflectionPositionLow(CaptureIndex) ReflectionCaptureES31.PositionLow[CaptureIndex]
|
|
#else
|
|
#define GetReflectionPositionAndRadius(CaptureIndex) ReflectionCaptureSM5.PositionHighAndRadius[CaptureIndex]
|
|
#define GetReflectionCaptureProperties(CaptureIndex) ReflectionCaptureSM5.CaptureProperties[CaptureIndex]
|
|
#define GetReflectionCaptureOffsetAndAverageBrightness(CaptureIndex) ReflectionCaptureSM5.CaptureOffsetAndAverageBrightness[CaptureIndex]
|
|
#define GetReflectionBoxTransform(CaptureIndex) ReflectionCaptureSM5.BoxTransform[CaptureIndex]
|
|
#define GetReflectionBoxScales(CaptureIndex) ReflectionCaptureSM5.BoxScales[CaptureIndex]
|
|
#define GetReflectionPositionLow(CaptureIndex) ReflectionCaptureSM5.PositionLow[CaptureIndex]
|
|
#endif |