286 lines
8.5 KiB
HLSL
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;
|
|
} |