Files
UnrealEngineUWP/Engine/Shaders/Private/ReflectionEnvironmentShared.ush
ben ingram 0bb0a923a0 Merging Dev-LWCRendering into Main, this includes initial work to support rendering with LWC-scale position
Basic approach is to add HLSL types FLWCScalar, FLWCMatrix, FLWCVector, etc.  Inside shaders, absolute world space position values should be represented as FLWCVector3.  Matrices that transform *into* absolute world space become FLWCMatrix.  Matrices that transform *from* world space become FLWCInverseMatrix.  Generally LWC values work by extending the regular 'float' value with an additional tile coordinate.  Final tile size will be a trade-off between scale/accuracy; I'm using 256k for now, but may need to be adjusted.  Value represented by a FLWCVector thus becomes V.Tile * TileSize + V.Offset.  Most operations can be performed directly on LWC values.  There are HLSL functions like LWCAdd, LWCSub, LWCMultiply, LWCDivide (operator overloading would be really nice here).  The goal is to stay with LWC values for as long as needed, then convert to regular float values when possible.  One thing that comes up a lot is working in translated (rather than absolute) world space.  WorldSpace + View.PrevPreViewTranslation = TranslatedWorldspace.  Except 'View.PrevPreViewTranslation' is now a FLWCVector3, and WorldSpace quantities should be as well.  So that becomes LWCAdd(WorldSpace, View.PrevPreViewTranslation) = TranslatedWorldspace.  Assuming that we're talking about a position that's "reasonably close" to the camera, it should be safe to convert the translated WS value to float.  The 'tile' coordinate of the 2 LWC values should cancel out when added together in this case.  I've done some work throughout the shader code to do this.  Materials are fully supporting LWC-values as well.  Projective texturing and vertex animation materials that I've tested work correctly even when positioned "far away" from the origin.

Lots of work remains to fully convert all of our shader code.  There's a function LWCHackToFloat(), which is a simple wrapper for LWCToFloat().  The idea of HackToFloat is to mark places that need further attention, where I'm simply converting absolute WS positions to float, to get shaders to compile.  Shaders converted in this way should continue to work for all existing content (without LWC-scale values), but they will break if positions get too large.

General overview of changed files:
LargeWorldCoordinates.ush - This defines the FLWC types and operations
GPUScene.cpp, SceneData.ush - Primitives add an extra 'float3' tile coordinate.  Instance data is unchanged, so instances need to stay within single-precision range of the primitive origin.  Could potentially split instances behind the scenes (I think) if we don't want this limitation
HLSLMaterialDerivativeAutogen.cpp, HLSLMaterialTranslator.cpp, Preshader.cpp - Translated materials to use LWC values
SceneView.cpp, SceneRelativeViewMatrices.cpp, ShaderCompiler.cpp, InstancedStereo.ush - View uniform buffer includes LWC values where appropriate
#jira UE-117101
#rb arne.schober, Michael.Galetzka

#ROBOMERGE-AUTHOR: ben.ingram
#ROBOMERGE-SOURCE: CL 17787435 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v881-17767770)

[CL 17787478 by ben ingram in ue5-release-engine-test branch]
2021-10-12 13:31:00 -04:00

213 lines
9.7 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
ReflectionEnvironmentShared
=============================================================================*/
#pragma once
#if (FEATURE_LEVEL <= FEATURE_LEVEL_ES3_1)
#define SkyIrradianceEnvironmentMap View.MobileSkyIrradianceEnvironmentMap
#else
#define SkyIrradianceEnvironmentMap View.SkyIrradianceEnvironmentMap
#endif
#define REFLECTION_CAPTURE_ROUGHEST_MIP 1
#define REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE 1.2
/**
* 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, ReflectionStruct.SkyLightCubemapSampler, ReflectionVector, AbsoluteSpecularMip).rgb;
OutSkyAverageBrightness = ReflectionStruct.SkyLightCubemapBrightness * 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, ReflectionStruct.SkyLightBlendDestinationCubemapSampler, 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
float3 GetOffSpecularPeakReflectionDir(float3 Normal, float3 ReflectionVector, float Roughness)
{
float a = Square(Roughness);
return lerp( Normal, ReflectionVector, (1 - a) * ( sqrt(1 - a) + a ) );
}
float GetSpecularOcclusion(float NoV, float RoughnessSq, float 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;
}
float ComputeMixingWeight(float IndirectIrradiance, float AverageBrightness, float Roughness)
{
// Mirror surfaces should have no mixing, so they match reflections from other sources (SSR, planar reflections)
float 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.
float MixingWeight = IndirectIrradiance / max(AverageBrightness, .0001f);
MixingWeight = min(MixingWeight, View.ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight.z);
return lerp(1.0f, MixingWeight, MixingAlpha);
}