222 lines
7.7 KiB
HLSL
222 lines
7.7 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================================
|
|
PathTracingCapsuleLight.ush: Light sampling functions for capsule case of point lights
|
|
===============================================================================================*/
|
|
|
|
#pragma once
|
|
|
|
#include "PathTracingLightCommon.ush"
|
|
#include "../../CapsuleLightSampling.ush"
|
|
|
|
#define CAPSULE_SOLIDANGLE_SAMPLING 1
|
|
|
|
float3 CapsuleNormal(float3 Pos, float3 Center, float3 Axis)
|
|
{
|
|
float3 pa = Pos - Center;
|
|
float h = clamp(dot(pa, Axis) / dot(Axis, Axis), -0.5, 0.5);
|
|
return normalize(pa - h * Axis);
|
|
}
|
|
|
|
float GetCapsuleRadius(int LightId)
|
|
{
|
|
// don't allow 0 area capsules since we need to divide the light power by area to get radiance
|
|
return max(GetRadius(LightId), 0.01);
|
|
}
|
|
|
|
FLightHit CapsuleLight_TraceLight(FRayDesc Ray, int LightId)
|
|
{
|
|
float3 TranslatedLightPosition = GetTranslatedPosition(LightId);
|
|
float LightRadius = GetCapsuleRadius(LightId);
|
|
float LightRadius2 = Pow2(LightRadius);
|
|
float SourceLength = GetSourceLength(LightId);
|
|
|
|
#if 0 // DEBUG code: visualize the bounding rectangle against the capsule intersection test
|
|
FCapsuleSphericalBounds CapsuleBounds = CapsuleGetSphericalBounds(TranslatedLightPosition - Ray.Origin, GetdPdv(LightId), LightRadius, SourceLength);
|
|
|
|
float ConeSolidAngle = CapsuleBounds.ConeSolidAngle;
|
|
float RectSolidAngle = CapsuleBounds.SphericalRect.SolidAngle;
|
|
bool HitBounds = false;
|
|
float t = -1;
|
|
if (ConeSolidAngle < RectSolidAngle)
|
|
{
|
|
// Is ray pointing inside the cone of directions?
|
|
float CosTheta = dot(normalize(Ray.Direction), CapsuleBounds.ConeAxis);
|
|
t = CosTheta >= SqrtOneMinusX(CapsuleBounds.ConeSinThetaMax2) ? length(TranslatedLightPosition) : -1.0;
|
|
}
|
|
else
|
|
{
|
|
float DoN = dot(Ray.Direction, CapsuleBounds.SphericalRect.Axis[2]);
|
|
t = CapsuleBounds.SphericalRect.z0 / DoN;
|
|
// ray points toward the plane and intersect it?
|
|
if (t > Ray.TMin && t < Ray.TMax)
|
|
{
|
|
float2 UV = t * float2(
|
|
dot(Ray.Direction, CapsuleBounds.SphericalRect.Axis[0]),
|
|
dot(Ray.Direction, CapsuleBounds.SphericalRect.Axis[1])) - float2(
|
|
0.5 * (CapsuleBounds.SphericalRect.x0 + CapsuleBounds.SphericalRect.x1),
|
|
0.5 * (CapsuleBounds.SphericalRect.y0 + CapsuleBounds.SphericalRect.y1)
|
|
);
|
|
float2 Extent = 0.5 * float2(
|
|
CapsuleBounds.SphericalRect.x1 - CapsuleBounds.SphericalRect.x0,
|
|
CapsuleBounds.SphericalRect.y1 - CapsuleBounds.SphericalRect.y0);
|
|
// reject hit if outside of quad extents
|
|
t = all(abs(UV) < Extent) ? t : -1.0;
|
|
}
|
|
}
|
|
|
|
// ray points toward the plane and intersect it?
|
|
if (t > Ray.TMin && t < Ray.TMax)
|
|
{
|
|
float CapsArea = LightRadius2;
|
|
float BodyArea = 0.5 * LightRadius * SourceLength;
|
|
float3 LightPower = GetColor(LightId);
|
|
float3 LightRadiance = LightPower / (PI * (CapsArea + BodyArea));
|
|
float t = CapsuleIntersect(Ray.Direction, TranslatedLightPosition - Ray.Origin, GetdPdv(LightId), LightRadius2, SourceLength);
|
|
if (t > Ray.TMin && t < Ray.TMax)
|
|
{
|
|
LightRadiance *= float3(0, 1, 0);
|
|
}
|
|
return CreateLightHit(LightRadiance, 0.0, t);
|
|
}
|
|
|
|
// missed the bounding rect, test capsule anyway to see if any part of the capsule was missing
|
|
{
|
|
float CapsArea = LightRadius2;
|
|
float BodyArea = 0.5 * LightRadius * SourceLength;
|
|
float3 LightPower = GetColor(LightId);
|
|
float3 LightRadiance = LightPower / (PI * (CapsArea + BodyArea));
|
|
{
|
|
// exact
|
|
float t = CapsuleIntersect(Ray.Direction, TranslatedLightPosition - Ray.Origin, GetdPdv(LightId), LightRadius2, SourceLength);
|
|
if (t > Ray.TMin && t < Ray.TMax)
|
|
{
|
|
LightRadiance *= float3(1, 0, 0);
|
|
return CreateLightHit(LightRadiance, 0.0, t);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
float3 Axis = GetdPdv(LightId);
|
|
float t = CapsuleIntersect(Ray.Direction, TranslatedLightPosition - Ray.Origin, Axis, LightRadius2, SourceLength);
|
|
if (t > Ray.TMin && t < Ray.TMax)
|
|
{
|
|
float CapsArea = LightRadius2;
|
|
float BodyArea = 0.5 * LightRadius * SourceLength;
|
|
float3 LightPower = GetColor(LightId);
|
|
float3 LightRadiance = LightPower / (PI * (CapsArea + BodyArea));
|
|
float3 LightDirection = TranslatedLightPosition - Ray.Origin;
|
|
float LightDistanceSquared = dot(LightDirection, LightDirection);
|
|
#if CAPSULE_SOLIDANGLE_SAMPLING
|
|
FCapsuleSphericalBounds Bounds = CapsuleGetSphericalBounds(LightDirection, Axis, LightRadius, SourceLength);
|
|
float Pdf = 1.0 / GetCapsuleBoundsInversePdf(Ray.Direction, Bounds);
|
|
#else
|
|
float3 Normal = CapsuleNormal(Ray.Origin + t * Ray.Direction, TranslatedLightPosition, Axis * SourceLength);
|
|
|
|
float CosTheta = saturate(-dot(Normal, Ray.Direction));
|
|
|
|
float Pdf = CosTheta > 0 ? t * t / (4 * PI * CosTheta * (CapsArea + BodyArea)) : 0.0;
|
|
#endif
|
|
return CreateLightHit(LightRadiance, Pdf, t);
|
|
}
|
|
#endif
|
|
return NullLightHit();
|
|
}
|
|
|
|
|
|
FLightSample CapsuleLight_SampleLight(
|
|
int LightId,
|
|
float2 RandSample,
|
|
float3 TranslatedWorldPos,
|
|
float3 WorldNormal
|
|
)
|
|
{
|
|
// Capsule case
|
|
// #dxr_todo: only sample the visible portion of the capsule and account for the 1/d^2 falloff down the axis
|
|
|
|
float Radius = GetCapsuleRadius(LightId);
|
|
float Radius2 = Radius * Radius;
|
|
float SourceLength = GetSourceLength(LightId);
|
|
|
|
// the caps are two halves of a full sphere
|
|
// the body is a cylinder
|
|
// the common factor of 4*PI is accounted for at the end
|
|
float CapsArea = Radius2;
|
|
float BodyArea = 0.5 * Radius * SourceLength;
|
|
|
|
float3 Axis = GetdPdv(LightId);
|
|
|
|
#if CAPSULE_SOLIDANGLE_SAMPLING
|
|
float3 LightPower = GetColor(LightId);
|
|
float3 LightRadiance = LightPower / (PI * (CapsArea + BodyArea));
|
|
|
|
float3 LightDirection = GetTranslatedPosition(LightId) - TranslatedWorldPos;
|
|
FCapsuleSphericalBounds Bounds = CapsuleGetSphericalBounds(LightDirection, Axis, Radius, SourceLength);
|
|
|
|
float4 Result = SampleCapsuleBounds(Bounds, RandSample);
|
|
|
|
float3 L = Result.xyz;
|
|
float InvPdf = Result.w;
|
|
|
|
// check direction to account for rays that hit the bounding shape but not the capsule
|
|
#if 1
|
|
// optimized check (also more robust for tiny radii)
|
|
float Distance = CapsuleTest(L, LightDirection, Axis, Radius2, SourceLength);
|
|
#else
|
|
// exact check (produces artifacts for tiny radii)
|
|
float Distance = CapsuleIntersect(L, LightDirection, Axis, Radius2, SourceLength);
|
|
#endif
|
|
if (Distance > 0)
|
|
{
|
|
return CreateLightSample(LightRadiance * InvPdf, rcp(InvPdf), L, Distance);
|
|
}
|
|
// didn't pass the acceptance test -- reject this sample
|
|
return NullLightSample();
|
|
#else
|
|
// plain area sampling
|
|
float Prob = CapsArea / (CapsArea + BodyArea);
|
|
|
|
float3 LightPoint;
|
|
float3 LightNormal;
|
|
if (RandSample.x < Prob)
|
|
{
|
|
RandSample.x /= Prob;
|
|
// sample the caps
|
|
float4 Result = UniformSampleSphere(float2(RandSample));
|
|
|
|
LightPoint = Result.xyz * Radius;
|
|
LightPoint.z += 0.5 * SourceLength * sign(Result.z);
|
|
|
|
LightNormal = Result.xyz;
|
|
}
|
|
else
|
|
{
|
|
RandSample.x -= Prob;
|
|
RandSample.x /= 1 - Prob;
|
|
// sample the cylinder body
|
|
|
|
float Phi = 2 * PI * RandSample.x;
|
|
|
|
LightNormal = float3(cos(Phi), sin(Phi), 0.0);
|
|
LightPoint = float3(Radius * LightNormal.xy, (RandSample.y - 0.5) * SourceLength);
|
|
}
|
|
float3x3 CylinderBasis = GetTangentBasis(Axis);
|
|
|
|
float3 LightDirection = GetTranslatedPosition(LightId) - TranslatedWorldPos;
|
|
float3 LocalLightDirection = mul(CylinderBasis, LightDirection); // World To Local
|
|
|
|
float CosTheta = saturate(dot(LightNormal, -normalize(LocalLightDirection + LightPoint)));
|
|
|
|
LightPoint = mul(LightPoint, CylinderBasis) + LightDirection; // Local to World
|
|
|
|
float DistanceSquared = dot(LightPoint, LightPoint);
|
|
float Distance = sqrt(DistanceSquared);
|
|
float3 Direction = LightPoint * rcp(Distance);
|
|
|
|
float3 LightPower = GetColor(LightId);
|
|
float3 RadianceOverPdf = LightPower * CosTheta * 4 / DistanceSquared;
|
|
float Pdf = CosTheta > 0 ? DistanceSquared / (4 * PI * CosTheta * (CapsArea + BodyArea)) : 0.0;
|
|
return CreateLightSample(RadianceOverPdf, Pdf, Direction, Distance);
|
|
#endif
|
|
}
|