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

286 lines
8.5 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "DeferredShadingCommon.ush"
#include "MonteCarlo.ush"
#include "AreaLightCommon.ush"
#include "ShadingModels.ush"
#include "CapsuleLight.ush"
#include "CapsuleLightSampling.ush"
#include "SobolRandom.ush"
// Should this be SH instead?
float IntegrateLight( FCapsuleLight Capsule, bool bInverseSquared )
{
float Falloff;
BRANCH
if( Capsule.Length > 0 )
{
float NoL;
float LineCosSubtended = 1;
LineIrradiance( 0, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
}
else
{
float3 ToLight = Capsule.LightPos[0];
float DistSqr = dot( ToLight, ToLight );
Falloff = rcp( DistSqr + Capsule.DistBiasSqr );
}
Falloff = bInverseSquared ? Falloff : 1;
return Falloff;
}
FAreaLightIntegrateContext CreateCapsuleIntegrateContext(float Roughness, half3 N, half3 V, FCapsuleLight Capsule, bool bInverseSquared )
{
FAreaLightIntegrateContext Out = InitAreaLightIntegrateContext();
float NoL;
float Falloff;
float LineCosSubtended = 1;
// Clip to horizon
//float NoP0 = dot( N, Capsule.LightPos[0] );
//float NoP1 = dot( N, Capsule.LightPos[1] );
//if( NoP0 < 0 ) Capsule.LightPos[0] = ( Capsule.LightPos[0] * NoP1 - Capsule.LightPos[1] * NoP0 ) / ( NoP1 - NoP0);
//if( NoP1 < 0 ) Capsule.LightPos[1] = ( -Capsule.LightPos[0] * NoP1 + Capsule.LightPos[1] * NoP0 ) / (-NoP1 + NoP0);
BRANCH
if( Capsule.Length > 0 )
{
LineIrradiance( N, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
}
else
{
float DistSqr = dot( Capsule.LightPos[0], Capsule.LightPos[0] );
Falloff = rcp( DistSqr + Capsule.DistBiasSqr );
float3 L = Capsule.LightPos[0] * rsqrt( DistSqr );
NoL = dot( N, L );
}
if( Capsule.Radius > 0 )
{
// TODO Use capsule area?
float SinAlphaSqr = saturate( Pow2( Capsule.Radius ) * Falloff );
NoL = SphereHorizonCosWrap( NoL, SinAlphaSqr );
}
NoL = saturate( NoL );
Falloff = bInverseSquared ? Falloff : 1;
float3 ToLight = Capsule.LightPos[0];
if( Capsule.Length > 0 )
{
float3 R = reflect( -V, N );
#if 0
// Fix hard edge when ray is nearly parallel to line
float3 PointOnLine = ClosestPointLineToPoint( Capsule.LightPos[0], Capsule.LightPos[1], Capsule.Length );
float3 DirToLine = normalize( PointOnLine );
R = lerp( DirToLine, R, saturate( dot( DirToLine, R ) ) );
R = normalize( R );
#endif
ToLight = ClosestPointLineToRay( Capsule.LightPos[0], Capsule.LightPos[1], Capsule.Length, R );
}
float DistSqr = dot( ToLight, ToLight );
float InvDist = rsqrt( DistSqr );
float3 L = ToLight * InvDist;
Roughness = max( Roughness, View.MinRoughness );
float a = Pow2( Roughness );
// Diffuse micro refelction contribution will softly fade out when th light become an area light.
// We only based this assumption based on the light size.
const float SizeFadesOutDiffuseMicroRefl = 20.0;
Out.AreaLight.SphereSinAlpha = saturate( Capsule.Radius * InvDist * (1 - a) );
Out.AreaLight.SphereSinAlphaSoft = saturate( Capsule.SoftRadius * InvDist );
Out.AreaLight.LineCosSubtended = LineCosSubtended;
Out.AreaLight.FalloffColor = 1;
Out.AreaLight.Rect = (FRect)0;
Out.AreaLight.Texture = InitRectTexture();
Out.AreaLight.IsRectAndDiffuseMicroReflWeight = 0;
SetIsRectLight(Out.AreaLight, false);
SetAreaLightDiffuseMicroReflWeight(Out.AreaLight, saturate(1.0f - max(Capsule.Length, Capsule.Radius) / SizeFadesOutDiffuseMicroRefl));
Out.NoL = NoL;
Out.Falloff = Falloff;
Out.L = L;
return Out;
}
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FCapsuleLight Capsule, FShadowTerms Shadow, bool bInverseSquared )
{
GBuffer.Roughness = max( GBuffer.Roughness, View.MinRoughness );
FAreaLightIntegrateContext Context = CreateCapsuleIntegrateContext(GBuffer.Roughness, N, V, Capsule, bInverseSquared);
return IntegrateBxDF( GBuffer, N, V, Context.L, Context.Falloff, Context.NoL, Context.AreaLight, Shadow );
}
FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, FCapsuleLight Capsule, FShadowTerms Shadow, uint2 SVPos )
{
FDirectLighting Lighting = (FDirectLighting)0;
Capsule.Radius = max( 1, Capsule.Radius );
const float SphereArea = (4*PI) * Pow2( Capsule.Radius );
const float CylinderArea = (2*PI) * Capsule.Radius * Capsule.Length;
const float SurfaceArea = SphereArea + CylinderArea;
const float SurfaceColor = 4.0 / SurfaceArea;
float3 ToLight = 0.5 * ( Capsule.LightPos[0] + Capsule.LightPos[1] );
float3 CapsuleAxis = normalize( Capsule.LightPos[1] - Capsule.LightPos[0] );
float DistanceSqr = dot( ToLight, ToLight );
float3 ConeAxis = ToLight * rsqrt( DistanceSqr );
float SineConeSqr = saturate(Pow2(Capsule.Radius) / DistanceSqr);
FCapsuleSphericalBounds CapsuleBounds = CapsuleGetSphericalBounds(ToLight, CapsuleAxis, Capsule.Radius, Capsule.Length);
const uint NumSets = 3;
const uint NumSamples[ NumSets ] =
{
0, // Cosine hemisphere
16, // GGX
16, // Light area
};
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;
if( Set == 0 )
{
L = TangentToWorld( CosineSampleHemisphere( E ).xyz, N );
H = normalize(V + L);
}
else if( Set == 1 )
{
H = TangentToWorld( ImportanceSampleGGX( E, Pow4(GBuffer.Roughness) ).xyz, N );
L = 2 * dot( V, H ) * H - V;
}
else
{
/*if( SourceLength > 0 )
{
uint2 Random = Rand3DPCG16( uint3( SVPos.xy, View.Random + i ) ).xy;
float3 ToArea = ToLight;
float3x3 Basis = GetTangentBasis( LightData.Tangent );
if( ( (float)Random.x / 0xffff ) * SurfaceArea < SphereArea )
{
// Sphere caps
float3 SpherePos = SourceRadius * UniformSampleSphere( E ).xyz;
ToArea += mul( SpherePos, Basis );
ToArea += LightData.Tangent * ( SpherePos.z > 0 ? 0.5 * SourceLength : -0.5 * SourceLength;
}
else
{
// Cylinder
float Phi = (2*PI) * E.x;
float3 CylinderPos;
CylinderPos.x = SourceRadius * cos( Phi );
CylinderPos.y = SourceRadius * sin( Phi );
CylinderPos.z = SourceLength * E.z;
ToArea += mul( CylinderPos, Basis );
}
L = normalize( ToArea );
H = normalize( V + L );
}
else
{
uint2 Random = Rand3DPCG16( uint3( SVPos.xy, View.Random + i ) ).xy;
float3 L01 = LightData.Tangent * SourceLength;
float3 L0 = ToLight - 0.5 * L01;
float3 L1 = ToLight + 0.5 * L01;
L = lerp( L0, L1, (float)Random.x / 0xffff );
float DistanceSqr = dot( L, L );
float3 ConeAxis = L * rsqrt( DistanceSqr );
float ConeCos = sqrt( 1 - Square( SourceRadius ) / DistanceSqr );
L = TangentToWorld( UniformSampleCone( E, ConeCos ).xyz, ConeAxis );
H = normalize(V + L);
}*/
if( Capsule.Length > 0 )
{
float3 ToArea = SampleCapsuleBounds(CapsuleBounds, E).xyz;
L = normalize( ToArea );
H = normalize( V + L );
}
else
{
L = TangentToWorld( UniformSampleConeRobust( E, SineConeSqr).xyz, ConeAxis );
H = normalize(V + L);
}
}
float NoL = saturate( dot(N, L) );
float NoH = saturate( dot(N, H) );
float VoH = saturate( dot(V, H) );
if( NoL > 0 && VoH > 0 )
{
{
if (CapsuleTest(L, ToLight, CapsuleAxis, Pow2(Capsule.Radius), Capsule.Length) > 0)
{
// Ray misses sphere
continue;
}
}
float PDF[] =
{
NoL / PI,
D_GGX(Pow4(GBuffer.Roughness), NoH) * NoH / (4 * VoH),
rcp(GetCapsuleBoundsInversePdf(L, CapsuleBounds))
};
if( Capsule.Length == 0 )
{
PDF[2] = 1.0 / UniformConeSolidAngle(SineConeSqr);
}
// 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 += SurfaceColor * Weight * LightingSample.Diffuse;
Lighting.Specular += SurfaceColor * Weight * LightingSample.Specular;
Lighting.Transmission += SurfaceColor * Weight * LightingSample.Transmission;
}
}
}
return Lighting;
}