397 lines
11 KiB
HLSL
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;
|
|
} |