Files
UnrealEngine/Engine/Shaders/Private/RectLightIntegrate.ush
2025-05-18 13:04:45 +08:00

397 lines
11 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#define CHEAP_RECT 0
#include "DeferredShadingCommon.ush"
#include "MonteCarlo.ush"
#include "AreaLightCommon.ush"
#include "ShadingModels.ush"
#include "RectLight.ush"
float3 ClampToRect( float3 L, FRect Rect )
{
// Bias toward plane
//L -= Rect.Axis[2] * saturate( 0.001 + dot( Rect.Axis[2], L ) );
//L = normalize( L );
// Intersect ray with plane
float3 PointOnPlane = L * ( dot( Rect.Axis[2], Rect.Origin ) / dot( Rect.Axis[2], L ) );
//float3 PointOnPlane = L * ( dot( Rect.Axis[2], Rect.Origin ) / -saturate( 0.001 - dot( Rect.Axis[2], L ) ) );
float2 PointInRect;
PointInRect.x = dot( Rect.Axis[0], PointOnPlane - Rect.Origin );
PointInRect.y = dot( Rect.Axis[1], PointOnPlane - Rect.Origin );
// Clamp point to rect
PointInRect = clamp( PointInRect, -Rect.Extent, Rect.Extent );
float3 ToRect = Rect.Origin;
ToRect += PointInRect.x * Rect.Axis[0];
ToRect += PointInRect.y * Rect.Axis[1];
return normalize( ToRect );
}
bool RayHitRect( float3 L, FRect Rect )
{
// Intersect ray with plane
float t = dot( Rect.Axis[2], Rect.Origin ) / dot( Rect.Axis[2], L );
float3 PointOnPlane = L * t;
bool InExtentX = abs( dot( Rect.Axis[0], PointOnPlane - Rect.Origin ) ) <= Rect.Extent.x;
bool InExtentY = abs( dot( Rect.Axis[1], PointOnPlane - Rect.Origin ) ) <= Rect.Extent.y;
return t >= 0 && InExtentX && InExtentY;
}
float IntegrateLight( FRect Rect )
{
// No visibile rect light due to barn door occlusion
if (Rect.Extent.x == 0 || Rect.Extent.y == 0) return 0;
float NoL;
float Falloff;
#if !CHEAP_RECT // Optimized Lambert
float3 L = RectIrradianceLambert( 0, Rect, Falloff, NoL );
#elif 1 // Karis
float3 L = RectIrradianceApproxKaris( 0, Rect, Falloff, NoL );
#elif 1 // Lagarde
float3 L = RectIrradianceApproxLagarde( 0, Rect, Falloff, NoL );
#else // Drobot
float3 L = RectIrradianceApproxDrobot( 0, Rect, Falloff, NoL );
#endif
return Falloff;
}
FAreaLightIntegrateContext CreateRectIntegrateContext( float Roughness, half3 N, half3 V, FRect Rect, FRectTexture SourceTexture )
{
float NoL = 0;
float Falloff = 0;
FAreaLightIntegrateContext Out = InitAreaLightIntegrateContext();
#if !CHEAP_RECT // Optimized Lambert
float3 L = RectIrradianceLambert( N, Rect, Falloff, NoL );
#elif 1 // Karis
float3 L = RectIrradianceApproxKaris( N, Rect, Falloff, NoL );
#elif 1 // Lagarde
float3 L = RectIrradianceApproxLagarde( N, Rect, Falloff, NoL );
#else // Drobot
float3 L = RectIrradianceApproxDrobot( N, Rect, Falloff, NoL );
#endif
#if CHEAP_RECT
float3 R = reflect( -V, N );
#if 0
float NoV = saturate( dot( N, V ) );
L = lerp( L, R, saturate( -2*dot( Rect.Axis[2], R ) ) );
L = normalize( L );
UNROLL
for( int k = 0; k < 2; k++ )
{
#if 1
float NoL = dot( N, L );
float NoV = dot( N, V );
float VoL = dot( V, L );
float NoHInvLenH = ( NoL + NoV ) / ( 2 + 2*VoL );
float3 Gradient = ( ( N - L*NoL ) - ( V - L*VoL ) * NoHInvLenH ) * (2*NoHInvLenH);
#else
float RoL = dot( R, L );
float3 Gradient = 2 * RoL * ( R - L * RoL );
#endif
Gradient -= Rect.Axis[2] * dot( Rect.Axis[2], Gradient );
Gradient = lerp( Gradient, 0, (1 - NoV) * saturate( 2*dot( Rect.Axis[2], L ) ) );
L = ClampToRect( L + Gradient * ( 2.0 / ( 2.0 + k ) ), Rect );
}
#elif 1
float3 Ls = L;
float3 v[4];
v[0] = Rect.Origin - Rect.Axis[0] * Rect.Extent.x - Rect.Axis[1] * Rect.Extent.y;
v[1] = Rect.Origin + Rect.Axis[0] * Rect.Extent.x - Rect.Axis[1] * Rect.Extent.y;
v[2] = Rect.Origin + Rect.Axis[0] * Rect.Extent.x + Rect.Axis[1] * Rect.Extent.y;
v[3] = Rect.Origin - Rect.Axis[0] * Rect.Extent.x + Rect.Axis[1] * Rect.Extent.y;
float3 e0 = v[0];
float3 e1 = v[1];
float3 MinEdgeN = 0;
float MinEdgeCos = 1;
UNROLL
for( uint i = 0; i < 4; i++ )
{
float3 v0 = v[i];
float3 v1 = v[ (i+1) % 4 ];
float3 EdgeN = normalize( cross( v0, v1 ) );
float EdgeCos = dot( R, EdgeN );
if( EdgeCos < MinEdgeCos )
{
MinEdgeN = EdgeN;
MinEdgeCos = EdgeCos;
e0 = v0;
e1 = v1;
}
}
if( MinEdgeCos > 0 )
{
Ls = R;
}
else
{
#if 0
Ls = SmallestAnglePointOnLineToRay( e0, e1, length( e0 - e1 ), R );
#else
float3 Rp = R - MinEdgeCos * MinEdgeN;
if( dot( cross( Rp, e0 ), R ) < 0 ) Ls = e0;
else if(dot( cross( e1, Rp ), R ) < 0 ) Ls = e1;
else Ls = Rp;
#endif
Ls = normalize( Ls );
}
float a = Pow2( GBuffer.Roughness );
//L = lerp( Ls, L, a );
L = normalize( Ls );
#else
L = R;
if( !RayHitRect( R, Rect ) )
{
float3 MaxL = R;
float MaxNoH = -1;
uint NumSteps = 128;
for( uint i = 0; i < NumSteps; i++ )
{
float Theta = (2*PI) * i / (float)NumSteps;
float2 p;
p.x = cos( Theta );
p.y = sin( Theta );
p.xy /= dot( 1, abs(p) );
float2 PointInRect = float2( p.x + p.y, p.x - p.y ) * Rect.Extent;
//0.5 * sqrt( 2 + (2*sqrt(2.0)) *
float3 ToRect = Rect.Origin;
ToRect += PointInRect.x * Rect.Axis[0];
ToRect += PointInRect.y * Rect.Axis[1];
L = normalize( ToRect );
float RoL = dot( R, L );
BxDFContext Context;
Context.Init( N, V, L );
if( Context.NoH > MaxNoH )
{
MaxNoH = Context.NoH;
MaxL = L;
}
}
L = MaxL;
}
for( int k = 0; k < 0; k++ )
{
float NoL = dot( N, L );
float NoV = dot( N, V );
float VoL = dot( V, L );
float NoHInvLenH = ( NoL + NoV ) / ( 2 + 2*VoL );
float3 Gradient = ( ( N - L*NoL ) - ( V - L*VoL ) * NoHInvLenH ) * (2*NoHInvLenH);
L = ClampToRect( L + Gradient * ( 2.0 / ( 2.0 + k ) ), Rect );
}
#endif
Out.AreaLight.SphereSinAlpha = sqrt( Falloff * (1.0 / PI) );
Out.AreaLight.SphereSinAlphaSoft = 0;
Out.AreaLight.LineCosSubtended = 1;
Out.AreaLight.FalloffColor = 1;
Out.AreaLight.Rect = Rect;
Out.AreaLight.Texture = SourceTexture;
Out.AreaLight.IsRectAndDiffuseMicroReflWeight = 0;
SetIsRectLight(Out.AreaLight, false);
SetAreaLightDiffuseMicroReflWeight(Out.AreaLight, 0.0);
Out.L = L;
Out.NoL = NoL;
Out.Falloff = Falloff;
#else
float3 FalloffColor = SampleSourceTexture( L, Rect, SourceTexture );
Out.AreaLight.SphereSinAlpha = 0;
Out.AreaLight.SphereSinAlphaSoft = 0;
Out.AreaLight.LineCosSubtended = 1;
Out.AreaLight.FalloffColor = FalloffColor;
Out.AreaLight.Rect = Rect;
Out.AreaLight.Texture = SourceTexture;
Out.AreaLight.IsRectAndDiffuseMicroReflWeight = 0;
SetIsRectLight(Out.AreaLight, true);
SetAreaLightDiffuseMicroReflWeight(Out.AreaLight, 0.0);
Out.L = L;
Out.NoL = NoL;
Out.Falloff = Falloff;
#endif
return Out;
}
FDirectLighting IntegrateBxDF(FGBufferData GBuffer, half3 N, half3 V, FRect Rect, FShadowTerms Shadow, FRectTexture SourceTexture)
{
// Compute distance to light plane and cull if not larger than threshold to
// avoid numerical issue in (nearly) coplanar configuration
const float Distance = dot(Rect.Axis[2], Rect.Origin);
// No-visible rect light due to barn door occlusion
FDirectLighting Out = (FDirectLighting)0;
if (IsRectVisible(Rect) && Distance > 0.001f)
{
FAreaLightIntegrateContext Context = CreateRectIntegrateContext(GBuffer.Roughness, N, V, Rect, SourceTexture);
GBuffer.Roughness = max(GBuffer.Roughness, 0.02);
Out = IntegrateBxDF(GBuffer, N, V, Context.L, Context.Falloff, Context.NoL, Context.AreaLight, Shadow);
}
return Out;
}
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FRect Rect, FShadowTerms Shadow, FRectTexture SourceTexture, uint2 SVPos )
{
FDirectLighting Lighting = (FDirectLighting)0;
const float SurfaceArea = 4 * Rect.Extent.x * Rect.Extent.y;
const float SurfaceColor = 2.0 / SurfaceArea;
// Rect normal points away from point
if( dot( Rect.Axis[2], Rect.Origin ) < 0 )
return Lighting;
// No-visible rect light due to barn door occlusion
if (!IsRectVisible(Rect))
return Lighting;
FSphericalRect SphericalRect = BuildSphericalRect( Rect );
const uint NumSets = 4;
const uint NumSamples[ NumSets ] =
{
0, // Cosine hemisphere
16, // GGX
0, // Light area
16, // Spherical rect
};
uint2 SobolBase = SobolPixel( SVPos );
uint2 SobolFrame = SobolIndex( SobolBase, View.StateFrameIndexMod8, 3 );
UNROLL
for( uint Set = 0; Set < NumSets; Set++ )
{
LOOP
for( uint i = 0; i < NumSamples[ Set ]; i++ )
{
uint2 Random = Rand3DPCG16( uint3( SVPos.xy, View.Random ^ Set ) ).xy;
float2 E = float2( SobolIndex( SobolFrame, i << 3 ) ) / 0x10000;
//float2 E = Hammersley( i, NumSamples[ Set ], Random );
//float2 E = CorrelatedMultiJitter2D( i, NumSamples[ Set ], Random.x );
float3 L, H;
switch( Set )
{
case 0:
{
L = TangentToWorld( CosineSampleHemisphere( E ).xyz, N );
H = normalize( V + L );
break;
}
case 1:
{
H = TangentToWorld( ImportanceSampleGGX( E, Pow4(GBuffer.Roughness) ).xyz, N );
L = 2 * dot( V, H ) * H - V;
break;
}
case 2:
{
float3 ToArea = Rect.Origin;
ToArea += (E.x * 2 - 1) * Rect.Axis[0] * Rect.Extent.x;
ToArea += (E.y * 2 - 1) * Rect.Axis[1] * Rect.Extent.y;
L = normalize( ToArea );
H = normalize( V + L );
break;
}
case 3:
{
L = UniformSampleSphericalRect( E, SphericalRect ).Direction;
H = normalize( V + L );
break;
}
}
float NoL = saturate( dot(N, L) );
float NoH = saturate( dot(N, H) );
float VoH = saturate( dot(V, H) );
if( NoL > 0 && VoH > 0 )
{
// Intersect ray with plane
float t = dot( Rect.Axis[2], Rect.Origin ) / dot( Rect.Axis[2], L );
float3 PointOnPlane = L * t;
float2 PointInRect;
PointInRect.x = dot( Rect.Axis[0], PointOnPlane - Rect.Origin );
PointInRect.y = dot( Rect.Axis[1], PointOnPlane - Rect.Origin );
float2 RectUV = PointInRect / Rect.Extent * float2( 0.5, -0.5 ) + 0.5;
float3 LightColor = SampleRectTexture(SourceTexture, RectUV, 0, true);
if( Set == 0 || Set == 1 )
{
bool InExtentX = abs( PointInRect.x ) <= Rect.Extent.x;
bool InExtentY = abs( PointInRect.y ) <= Rect.Extent.y;
BRANCH
if( t < 0 || !InExtentX || !InExtentY )
{
// Missed rect
continue;
}
}
float PDF[] =
{
NoL * (1 / PI),
D_GGX( Pow4(GBuffer.Roughness), NoH ) * NoH / (4 * VoH),
dot( PointOnPlane, PointOnPlane ) / ( SurfaceArea * abs( dot( L, Rect.Axis[2] ) ) ),
1.0 / SphericalRect.SolidAngle,
};
// MIS power heuristic
float InvWeight = 0;
UNROLL for( uint j = 0; j < NumSets; j++ )
{
InvWeight += Square( PDF[j] * NumSamples[j] );
}
float Weight = rcp( InvWeight ) * PDF[Set] * NumSamples[Set];
FDirectLighting LightingSample = EvaluateBxDF( GBuffer, N, V, L, NoL, Shadow );
Lighting.Diffuse += ( LightColor * Weight ) * LightingSample.Diffuse;
Lighting.Specular += ( LightColor * Weight ) * LightingSample.Specular;
Lighting.Transmission += ( LightColor * Weight ) * LightingSample.Transmission;
}
}
}
return Lighting;
}