262 lines
12 KiB
HLSL
262 lines
12 KiB
HLSL
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
bool SphereIntersectCone(float4 SphereCenterAndRadius, float3 ConeVertex, float3 ConeAxis, float ConeAngleCos, float ConeAngleSin)
|
|
{
|
|
float3 U = ConeVertex - (SphereCenterAndRadius.w / ConeAngleSin) * ConeAxis;
|
|
float3 D = SphereCenterAndRadius.xyz - U;
|
|
float DSizeSq = dot(D, D);
|
|
float E = dot(ConeAxis, D);
|
|
|
|
if (E > 0 && E * E >= DSizeSq * ConeAngleCos * ConeAngleCos)
|
|
{
|
|
D = SphereCenterAndRadius.xyz - ConeVertex;
|
|
DSizeSq = dot(D, D);
|
|
E = -dot(ConeAxis, D);
|
|
|
|
if (E > 0 && E * E >= DSizeSq * ConeAngleSin * ConeAngleSin)
|
|
{
|
|
return DSizeSq <= SphereCenterAndRadius.w * SphereCenterAndRadius.w;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SphereIntersectConeWithMaxDistance(float4 SphereCenterAndRadius, float3 ConeVertex, float3 ConeAxis, float ConeAngleCos, float ConeAngleSin, float MaxDistanceAlongAxis)
|
|
{
|
|
if (SphereIntersectCone(SphereCenterAndRadius, ConeVertex, ConeAxis, ConeAngleCos, ConeAngleSin))
|
|
{
|
|
float ConeAxisDistance = dot(SphereCenterAndRadius.xyz - ConeVertex, ConeAxis);
|
|
float ConeAxisDistanceMax = ConeAxisDistance - SphereCenterAndRadius.w;
|
|
|
|
return ConeAxisDistanceMax < MaxDistanceAlongAxis;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SphereIntersectConeWithDepthRanges(float4 SphereCenterAndRadius, float3 ConeVertex, float3 ConeAxis, float ConeAngleCos, float ConeAngleSin, float4 ConeAxisDepthRanges)
|
|
{
|
|
if (SphereIntersectCone(SphereCenterAndRadius, ConeVertex, ConeAxis, ConeAngleCos, ConeAngleSin))
|
|
{
|
|
float ConeAxisDistance = dot(SphereCenterAndRadius.xyz - ConeVertex, ConeAxis);
|
|
float2 ConeAxisDistanceMinMax = float2(ConeAxisDistance + SphereCenterAndRadius.w, ConeAxisDistance - SphereCenterAndRadius.w);
|
|
|
|
if ((ConeAxisDistanceMinMax.x > ConeAxisDepthRanges.x && ConeAxisDistanceMinMax.y < ConeAxisDepthRanges.y)
|
|
|| (ConeAxisDistanceMinMax.x > ConeAxisDepthRanges.z && ConeAxisDistanceMinMax.y < ConeAxisDepthRanges.w))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SphereIntersectSphere(float4 SphereCenterAndRadius, float4 OtherSphereCenterAndRadius)
|
|
{
|
|
float CombinedRadii = SphereCenterAndRadius.w + OtherSphereCenterAndRadius.w;
|
|
float3 VectorBetweenCenters = SphereCenterAndRadius.xyz - OtherSphereCenterAndRadius.xyz;
|
|
return dot(VectorBetweenCenters, VectorBetweenCenters) < CombinedRadii * CombinedRadii;
|
|
}
|
|
|
|
// Given two ranges of distances along a ray - check if they overlap
|
|
// NOTE: This check ignores empty intervals and intervals which contain only a single point
|
|
bool CheckOverlap(float2 Ta, float2 Tb) {
|
|
return max(Ta.x, Tb.x) < min(Ta.y, Tb.y);
|
|
}
|
|
|
|
bool RayOverlapsAABB(float3 O, float3 D, float TMax, float3 Lo, float3 Hi)
|
|
{
|
|
float3 InvRayDir = rcp(D);
|
|
float3 T0 = (Lo - O) * InvRayDir;
|
|
float3 T1 = (Hi - O) * InvRayDir;
|
|
float3 TNear = min(T0, T1), TFar = max(T0, T1);
|
|
return max(max(TNear.x, TNear.y), max(TNear.z, 0.0)) <= min(min(TFar.x, TFar.y), min(TFar.z, TMax));
|
|
}
|
|
|
|
// Compute the overlap between an infinite ray and an infinite cone volume
|
|
// The Apex is the origin of the cone, the normal is the unit vector , CosTheta is the half-angle describing the cone's opening
|
|
float2 RayInfiniteConeVolumeOverlap(
|
|
float3 RayOrigin, float3 RayDirection,
|
|
float3 Apex, float3 Axis,
|
|
float CosTheta)
|
|
{
|
|
// Adapted from: https://www.iquilezles.org/www/articles/intersectors/intersectors.htm
|
|
const float3 ba = Axis * CosTheta;
|
|
const float3 oa = RayOrigin - Apex;
|
|
const float3 rd = RayDirection;
|
|
|
|
const float m0 = dot(ba, ba);
|
|
const float m1 = dot(oa, ba);
|
|
const float m3 = dot(rd, ba);
|
|
|
|
const float m4 = dot(rd, oa);
|
|
const float m5 = dot(oa, oa);
|
|
|
|
const float k2 = m0 * m0 - m3 * m3;
|
|
const float k1 = m0 * m0 * m4 - m1 * m3;
|
|
const float k0 = m0 * m0 * m5 - m1 * m1;
|
|
|
|
const float h = k1 * k1 - k2 * k0;
|
|
if (h > 0)
|
|
{
|
|
const float s = sign(k2);
|
|
const float2 t = (-k1 + float2(-s, s) * sqrt(h)) / k2;
|
|
const float2 y = m1 + t * m3;
|
|
|
|
// check which hits are valid:
|
|
const int c = ((y.x > 0) ? 1 : 0) + (y.y > 0 ? 2 : 0);
|
|
if (c == 1)
|
|
{
|
|
// only smaller root is valid, far root is invalid
|
|
return float2(0.0, t.x);
|
|
}
|
|
if (c == 2)
|
|
{
|
|
// only larger root is valid, must have started outside the cone
|
|
return float2(t.y, POSITIVE_INFINITY);
|
|
}
|
|
if (c == 3)
|
|
{
|
|
// both roots are valid
|
|
return t;
|
|
}
|
|
}
|
|
return -1.0;
|
|
}
|
|
|
|
float2 RaySphereOverlap(float3 Ro, float3 Rd, float3 Center, float Radius, float TMin, float TMax)
|
|
{
|
|
float3 oc = Ro - Center;
|
|
float b = dot(oc, Rd);
|
|
float h = Radius * Radius - length2(oc - b * Rd);
|
|
if (h > 0)
|
|
{
|
|
float2 t = -b + float2(-1, +1) * sqrt(h);
|
|
return float2(max(t.x, TMin), min(t.y, TMax));
|
|
}
|
|
return -1.0;
|
|
}
|
|
|
|
// Check for overlap between an AABB and a Cone (the portion of the cone inside a sphere of the given Radius)
|
|
// NOTE: This test can be expensive, so one should check for AABB vs sphere overlap first
|
|
// NOTE: The last argument can be used to do a cheaper test using the AABB of the rounded cone
|
|
bool AABBOverlapsCurvedCone(float3 Lo, float3 Hi, float3 Apex, float3 Axis, float CosTheta, float Radius, bool bUseApproxTest)
|
|
{
|
|
if (bUseApproxTest)
|
|
{
|
|
// Compute AABB of the cone, and check for overlap of the two AABBs
|
|
// box around ray from light center to tip of the cone
|
|
float3 Tip = Apex + Axis * Radius;
|
|
float3 LightBoundLo = min(Apex, Tip);
|
|
float3 LightBoundHi = max(Apex, Tip);
|
|
|
|
// expand by disc around the farthest part of the cone
|
|
float SinTheta2 = 1 - CosTheta * CosTheta;
|
|
float3 Disc = sqrt(saturate(SinTheta2 * (1.0 - Axis * Axis)));
|
|
LightBoundLo = min(LightBoundLo, Apex + Radius * (Axis * CosTheta - Disc));
|
|
LightBoundHi = max(LightBoundHi, Apex + Radius * (Axis * CosTheta + Disc));
|
|
|
|
float3 F = select(abs(Axis) > CosTheta, Apex + Radius * sign(Axis), Apex); // include far point along axis if it lies inside the cone
|
|
LightBoundLo = min(LightBoundLo, F);
|
|
LightBoundHi = max(LightBoundHi, F);
|
|
|
|
// intersect bounds and see if we have anything left
|
|
return all(max(Lo, LightBoundLo) < min(Hi, LightBoundHi));
|
|
}
|
|
else
|
|
{
|
|
return
|
|
// Does the Apex line inside the AABB?
|
|
all((Lo <= Apex) & (Apex <= Hi)) ||
|
|
// Does the central axis of the cone pass overlap the AABB?
|
|
RayOverlapsAABB(Apex, Axis, Radius, Lo, Hi) ||
|
|
// Test each of the 12 AABB edges against cone -- if any of them overlap, we can stop
|
|
// NOTE: this looks expensive, but there is a huge amount of simplification and common terms between each of these calls
|
|
// NOTE: This is a volumetric test, so edges entirely _inside_ the cone, will pass the test
|
|
// Check X edges
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Lo.y, Lo.z), float3(1, 0, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Lo.y, Lo.z), float3(1, 0, 0), Apex, Radius, 0, Hi.x - Lo.x)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Lo.y, Hi.z), float3(1, 0, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Lo.y, Hi.z), float3(1, 0, 0), Apex, Radius, 0, Hi.x - Lo.x)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Hi.y, Lo.z), float3(1, 0, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Hi.y, Lo.z), float3(1, 0, 0), Apex, Radius, 0, Hi.x - Lo.x)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Hi.y, Hi.z), float3(1, 0, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Hi.y, Hi.z), float3(1, 0, 0), Apex, Radius, 0, Hi.x - Lo.x)) ||
|
|
// Check Y edges
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Lo.y, Lo.z), float3(0, 1, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Lo.y, Lo.z), float3(0, 1, 0), Apex, Radius, 0, Hi.y - Lo.y)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Lo.y, Hi.z), float3(0, 1, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Lo.y, Hi.z), float3(0, 1, 0), Apex, Radius, 0, Hi.y - Lo.y)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Hi.x, Lo.y, Lo.z), float3(0, 1, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Hi.x, Lo.y, Lo.z), float3(0, 1, 0), Apex, Radius, 0, Hi.y - Lo.y)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Hi.x, Lo.y, Hi.z), float3(0, 1, 0), Apex, Axis, CosTheta), RaySphereOverlap(float3(Hi.x, Lo.y, Hi.z), float3(0, 1, 0), Apex, Radius, 0, Hi.y - Lo.y)) ||
|
|
// Check Z edges
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Lo.y, Lo.z), float3(0, 0, 1), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Lo.y, Lo.z), float3(0, 0, 1), Apex, Radius, 0, Hi.z - Lo.z)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Lo.x, Hi.y, Lo.z), float3(0, 0, 1), Apex, Axis, CosTheta), RaySphereOverlap(float3(Lo.x, Hi.y, Lo.z), float3(0, 0, 1), Apex, Radius, 0, Hi.z - Lo.z)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Hi.x, Lo.y, Lo.z), float3(0, 0, 1), Apex, Axis, CosTheta), RaySphereOverlap(float3(Hi.x, Lo.y, Lo.z), float3(0, 0, 1), Apex, Radius, 0, Hi.z - Lo.z)) ||
|
|
CheckOverlap(RayInfiniteConeVolumeOverlap(float3(Hi.x, Hi.y, Lo.z), float3(0, 0, 1), Apex, Axis, CosTheta), RaySphereOverlap(float3(Hi.x, Hi.y, Lo.z), float3(0, 0, 1), Apex, Radius, 0, Hi.z - Lo.z));
|
|
}
|
|
}
|
|
|
|
// Test AABB against solid sphere
|
|
bool AABBOverlapsSphere(float3 Lo, float3 Hi, float3 Center, float Radius)
|
|
{
|
|
// James Arvo - Graphics Gems (p. 335)
|
|
return length2(max(Lo - Center, 0) + max(Center - Hi, 0)) < Radius * Radius;
|
|
}
|
|
|
|
// Does the AABB lie fully "behind" the plane defined by Center and Normal
|
|
// NOTE: Normal vector does not need to be normalized
|
|
bool IsBoxBehindPlane(float3 Lo, float3 Hi, float3 Normal, float3 Center)
|
|
{
|
|
return dot(select(Normal > 0, Hi, Lo) - Center, Normal) < 0;
|
|
}
|
|
|
|
// Test AABB against the penumbra frustum defined by a rectlight's barndoors
|
|
// This test is expensive, so should only be used if the barndoors are active
|
|
// It is assumed the AABB already overlaps the sphere of influence of the rect light and is at least partially "in front" of the light
|
|
// The last portion of the test is a bit more expensive, so skip it optionally (at the risk of more false-positives)
|
|
bool AABBOverlapsRectLightFrustum(float3 Lo, float3 Hi, float3 Center, float3 Normal, float3 Tangent, float HalfWidth, float HalfHeight, float Radius, float BarnCos, float BarnLen, bool bImprovedFrustumTest)
|
|
{
|
|
const float3 dPdv = Tangent;
|
|
const float3 dPdu = cross(Normal, dPdv);
|
|
const float BarnSin = sqrt(1 - BarnCos * BarnCos);
|
|
|
|
float3 BoundingPlane = float3(
|
|
2 * HalfWidth + BarnLen * BarnSin,
|
|
2 * HalfHeight + BarnLen * BarnSin,
|
|
BarnLen * BarnCos
|
|
);
|
|
// Test if box is outside any of the 4 barndoor penumbra planes:
|
|
if (IsBoxBehindPlane(Lo, Hi, BoundingPlane.x * Normal + BoundingPlane.z * dPdu, Center - dPdu * HalfWidth) ||
|
|
IsBoxBehindPlane(Lo, Hi, BoundingPlane.x * Normal - BoundingPlane.z * dPdu, Center + dPdu * HalfWidth) ||
|
|
IsBoxBehindPlane(Lo, Hi, BoundingPlane.y * Normal + BoundingPlane.z * dPdv, Center - dPdv * HalfHeight) ||
|
|
IsBoxBehindPlane(Lo, Hi, BoundingPlane.y * Normal - BoundingPlane.z * dPdv, Center + dPdv * HalfHeight))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!bImprovedFrustumTest)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Checking the AABB against the 4 planes is not sufficient, and some cases which still lie outside could still pass through
|
|
// See description here: https://iquilezles.org/articles/frustumcorrect/
|
|
|
|
// Normalize the penumbra vector and scale it to the back of the sphere of influence
|
|
BoundingPlane = normalize(BoundingPlane);
|
|
BoundingPlane.xy *= Radius * rcp(BoundingPlane.z);
|
|
// Get 8 corners of the penumbra frustum in world space
|
|
const float3 C00 = Center - HalfWidth * dPdu - HalfHeight * dPdv, F00 = C00 - BoundingPlane.x * dPdu - BoundingPlane.y * dPdv + Radius * Normal;
|
|
const float3 C01 = Center + HalfWidth * dPdu + HalfHeight * dPdv, F01 = C01 - BoundingPlane.x * dPdu + BoundingPlane.y * dPdv + Radius * Normal;
|
|
const float3 C10 = Center - HalfWidth * dPdu - HalfHeight * dPdv, F10 = C10 + BoundingPlane.x * dPdu - BoundingPlane.y * dPdv + Radius * Normal;
|
|
const float3 C11 = Center + HalfWidth * dPdu + HalfHeight * dPdv, F11 = C11 + BoundingPlane.x * dPdu + BoundingPlane.y * dPdv + Radius * Normal;
|
|
// Get an AABB around the 8 frustum points and check if it overlaps the query AABB
|
|
if (any(max(max(max(C00, F00), max(C01, F01)), max(max(C10, F10), max(C11, F11))) < Lo) ||
|
|
any(min(min(min(C00, F00), min(C01, F01)), min(min(C10, F10), min(C11, F11))) > Hi))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
} |