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

170 lines
5.4 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "RectLight.ush"
float SqrtOneMinusX(float x)
{
return x < 0.01 ? 1 - x * (0.5 + x * 0.125) : sqrt(1 - x);
}
// The footprint of a capsule can be bounded by either a rectangle or a cone
// This class holds both and will do sampling from whichever has the smallest solidangle
struct FCapsuleSphericalBounds
{
FSphericalRect SphericalRect;
float3 ConeAxis;
float ConeSinThetaMax2;
float ConeSolidAngle;
};
float GetCapsuleBoundsInversePdf(float3 Direction, FCapsuleSphericalBounds Bounds)
{
if (Bounds.ConeSolidAngle < Bounds.SphericalRect.SolidAngle)
{
return Bounds.ConeSolidAngle;
}
float LocalDirZ = dot(Direction, Bounds.SphericalRect.Axis[2]);
float DistanceSquared = Square(Bounds.SphericalRect.z0 / LocalDirZ);
return GetSphericalRectInversePdf(Direction, DistanceSquared, Bounds.SphericalRect);
}
// return world space direction within the cone and solidangle
float4 SampleCapsuleBounds(FCapsuleSphericalBounds Bounds, float2 E)
{
if (Bounds.ConeSolidAngle < Bounds.SphericalRect.SolidAngle)
{
return float4(TangentToWorld(UniformSampleConeRobust(E, Bounds.ConeSinThetaMax2).xyz, Bounds.ConeAxis), Bounds.ConeSolidAngle);
}
else
{
FSphericalRectSample Result = UniformSampleSphericalRect(E, Bounds.SphericalRect);
return float4(Result.Direction, Result.InvPdf);
}
}
// Given a bounding rectangle of a capsule, compute a bounding cone
FCapsuleSphericalBounds CapsuleGetSphericalBounds(float3 Origin, float3 Axis, float Radius, float Length)
{
float h = dot(Axis, Origin);
float3 ClosestPointOnAxis = Origin - Axis * h;
float DistanceToAxisSqr = dot(ClosestPointOnAxis, ClosestPointOnAxis);
float RadiusSqr = Pow2(Radius);
if (DistanceToAxisSqr <= RadiusSqr)
{
// we are inside the infinite cylinder - only one of the caps can be visible
float3 CapCenter = Origin - Axis * Length * 0.5 * sign(h);
float LightDistanceSquared = dot(CapCenter, CapCenter);
float SinThetaMax2 = saturate(RadiusSqr / LightDistanceSquared);
FCapsuleSphericalBounds Result;
Result.SphericalRect = (FSphericalRect)0;
Result.SphericalRect.SolidAngle = POSITIVE_INFINITY; // make sure we don't pick this
Result.ConeAxis = normalize(CapCenter);
Result.ConeSinThetaMax2 = SinThetaMax2;
Result.ConeSolidAngle = UniformConeSolidAngle(SinThetaMax2);
return Result;
}
// we are outside the infinite cylinder, build a bounding rectangle
FRect Rect;
Rect.Origin = Origin;
Rect.Axis[1] = Axis;
Rect.Axis[2] = normalize(-ClosestPointOnAxis);
Rect.Axis[0] = cross(Rect.Axis[1], Rect.Axis[2]);
float SinCylinderAngle = Radius * rsqrt(DistanceToAxisSqr);
// width * cos(angle) = 2r
float RectRadius = Radius * rsqrt(1 - Pow2(SinCylinderAngle));
float Extension[2];
for (int i = 0; i < 2; i++)
{
float hi = Length * (i > 0 ? 0.5 : -0.5);
float3 PointPos = Origin + Axis * hi;
float InverseDist = rsqrt(dot(PointPos, PointPos));
float SinSphereAngle = saturate(Radius * InverseDist);
float CosSphereAngle = SqrtOneMinusX(Pow2(SinSphereAngle));
float CosAxisAngle = -dot(Axis, PointPos) * InverseDist;
CosAxisAngle = sign(CosAxisAngle * hi) * saturate(abs(CosAxisAngle));
float SinAxisAngle = SqrtOneMinusX(Pow2(CosAxisAngle));
float CosExtension = SinAxisAngle * CosSphereAngle + CosAxisAngle * SinSphereAngle;
Extension[i] = Radius / CosExtension;
}
float Translate = 0.5 * (Extension[1] - Extension[0]);
float Extend = 0.5 * (Extension[0] + Extension[1]);
Rect.Origin += Translate * Rect.Axis[1];
Rect.Extent = float2(RectRadius, 0.5 * Length + Extend);
// get a bounding cone from the bounding rectangle
float3 R0 = Rect.Origin - Rect.Axis[1] * Rect.Extent.y;
float3 R1 = Rect.Origin + Rect.Axis[1] * Rect.Extent.y;
float InvDistR0 = rsqrt(dot(R0, R0));
float InvDistR1 = rsqrt(dot(R1, R1));
FCapsuleSphericalBounds Result;
Result.SphericalRect = BuildSphericalRect(Rect);
Result.ConeAxis = normalize(lerp(R0, R1, saturate(InvDistR1 / (InvDistR0 + InvDistR1))));
Result.ConeSinThetaMax2 = saturate(0.5 - 0.5 * dot(R0, R1) * InvDistR0 * InvDistR1); // sin(x/2)^2 = (1-cos(x))/2
Result.ConeSolidAngle = UniformConeSolidAngle(Result.ConeSinThetaMax2);
return Result;
}
// Adapted from:
// https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
// This code assume the ray direction is normalized and only returns the front hit
float CapsuleIntersect(float3 Rd, float3 Center, float3 Axis, float Radius2, float Length)
{
float3 ba = Axis;
float3 oa = -Center;
float bard = dot(ba, Rd);
float baoa = dot(ba, oa);
float rdoa = dot(Rd, oa);
float oaoa = dot(oa, oa);
float a = 1 - bard * bard;
float b = rdoa - baoa * bard;
float c = oaoa - baoa * baoa - Radius2;
float h = b * b - a * c;
if (h >= 0.0)
{
float t = (-b - sqrt(h)) / a;
float y = baoa + t * bard;
// body
if (abs(y) < 0.5 * Length)
{
return t;
}
// caps
float3 oc = oa - (sign(y) * 0.5 * Length) * ba;
b = dot(Rd, oc);
h = Radius2 - length2(oc - b * Rd);
if (h > 0.0)
{
return -b - sqrt(h);
}
}
return -1.0;
}
// simpler intersection test for rays that are likely to intersect
// returned intersection distance is approximate
float CapsuleTest(float3 Rd, float3 Center, float3 Axis, float Radius2, float Length)
{
// Shortest distance
float B = dot(Rd, Axis);
float t = clamp(dot(Center, B * Rd - Axis) / (1 - B * B), -0.5 * Length, 0.5 * Length);
float3 ToSphere = Center + t * Axis;
float3 C = cross(Rd, ToSphere);
return dot(C, C) <= Radius2 ? length(ToSphere) : -1.0;
}