2025-09-11 20:42:09 +08:00
|
|
|
#ifndef CAPSULE_SHADOW_INCLUDED
|
|
|
|
|
#define CAPSULE_SHADOW_INCLUDED
|
|
|
|
|
#pragma multi_compile_fragment _ _MultiCapsule_Shadow_AO_ON
|
|
|
|
|
#pragma multi_compile_fragment _ _MultiCapsule_AO_ON
|
|
|
|
|
|
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
|
|
|
struct Capsule
|
|
|
|
|
{
|
|
|
|
|
half3 a, b;
|
|
|
|
|
half radius;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Character
|
|
|
|
|
{
|
|
|
|
|
half3 position;
|
|
|
|
|
half radius;
|
|
|
|
|
int capsuleStartID, capsuleEndID;
|
|
|
|
|
uint capsuleAOSettingMask;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct CapsuleShadowArea
|
|
|
|
|
{
|
|
|
|
|
half AmbientIntensity;
|
|
|
|
|
half ConeAngle;
|
|
|
|
|
half ShadowIntensity;
|
|
|
|
|
half ShadowSharpness;
|
|
|
|
|
int lightStartID, lightEndID;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
StructuredBuffer<Capsule> _CapsuleData;
|
|
|
|
|
uint _CapsulesCount;
|
|
|
|
|
StructuredBuffer<Character> _CharacterData;
|
|
|
|
|
uint _CharactersCount;
|
|
|
|
|
|
|
|
|
|
StructuredBuffer<CapsuleShadowArea> _CapsuleShadowData;
|
|
|
|
|
uint _CapsuleShadowDataCount;
|
|
|
|
|
|
|
|
|
|
half4 _CapsuleLightsDir[32];
|
|
|
|
|
uint _CapsuleLightsCount;
|
|
|
|
|
|
|
|
|
|
float CalcCapsuleShadowByIndex(float3 ro, float3 rd, uint s, uint e, in float k, float intensity, float intensity1)
|
|
|
|
|
{
|
|
|
|
|
float shadow = 1.0;
|
|
|
|
|
for (uint i = s; i < e; ++i)
|
|
|
|
|
{
|
|
|
|
|
Capsule c = _CapsuleData[i];
|
|
|
|
|
float3 a = c.a;
|
|
|
|
|
float3 b = c.b;
|
|
|
|
|
float r = c.radius;
|
|
|
|
|
float3 ba = b - a;
|
|
|
|
|
float3 oa = ro - a;
|
|
|
|
|
|
|
|
|
|
float oad = dot(oa, rd);
|
|
|
|
|
float dba = dot(rd, ba);
|
|
|
|
|
float baba = dot(ba, ba);
|
|
|
|
|
float oaba = dot(oa, ba);
|
|
|
|
|
float2 th = float2(-oad * baba + dba * oaba, oaba - oad * dba) / (baba - dba * dba);
|
|
|
|
|
|
|
|
|
|
th.x = max(th.x, 0.0001);
|
|
|
|
|
th.y = saturate(th.y);
|
|
|
|
|
|
|
|
|
|
//线段ab上离射线最近的点p
|
|
|
|
|
float3 p = a + ba * th.y;
|
|
|
|
|
//射线rord 找到距离线段 ab 最近的点q
|
|
|
|
|
float3 q = ro + rd * th.x;
|
|
|
|
|
//沿着光的方向发射射线, 射线离胶囊体最短的距离
|
|
|
|
|
float d = length(p - q) - r;
|
|
|
|
|
|
|
|
|
|
float s = saturate(k * d / th.x + 0.3);
|
|
|
|
|
s = s * s * (3.0 - 2.0 * s);
|
|
|
|
|
shadow *= s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shadow = saturate(lerp(1, shadow, intensity));
|
|
|
|
|
return saturate(lerp(1, shadow, intensity1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CalcCapsuleOcclusionByIndex(float3 p, float3 n, uint s, uint e, float intensity, float intensity1)
|
|
|
|
|
{
|
|
|
|
|
float ao = 1.0;
|
|
|
|
|
for (uint i = s; i < e; ++i)
|
|
|
|
|
{
|
|
|
|
|
Capsule capsule = _CapsuleData[i];
|
|
|
|
|
float3 a = capsule.a;
|
|
|
|
|
float3 b = capsule.b;
|
|
|
|
|
float r = capsule.radius;
|
|
|
|
|
float3 ba = b - a;
|
|
|
|
|
float3 pa = p - a;
|
|
|
|
|
float h = saturate(dot(pa, ba) / dot(ba, ba));
|
|
|
|
|
// 地面点 p 到 ab 最近点 x, 向量 xp = d
|
|
|
|
|
float3 d = pa - h * ba;
|
|
|
|
|
float l = length(d);
|
|
|
|
|
float nl = dot(-d, n);
|
|
|
|
|
float o = 1.0 - max(0.0, nl) * r * r / (l * l * l);
|
|
|
|
|
// multiplier o *= 1.0 + r*(l-r)/(l*l);
|
|
|
|
|
o = sqrt(o * o * o);
|
|
|
|
|
ao *= o;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ao = saturate(lerp(1, ao, intensity));
|
|
|
|
|
return clamp(lerp(1, ao, intensity1), 0.6, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define sq(x) (x * x)
|
|
|
|
|
float acosFast(float x)
|
|
|
|
|
{
|
|
|
|
|
// Lagarde 2014, "Inverse trigonometric functions GPU optimization for AMD GCN architecture"
|
|
|
|
|
// This is the approximation of degree 1, with a max absolute error of 9.0x10^-3
|
|
|
|
|
float y = abs(x);
|
|
|
|
|
float p = -0.1565827 * y + 1.570796;
|
|
|
|
|
p *= sqrt(1.0 - y);
|
|
|
|
|
return x >= 0.0 ? p : PI - p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float acosFastPositive(float x)
|
|
|
|
|
{
|
|
|
|
|
// Lagarde 2014, "Inverse trigonometric functions GPU optimization for AMD GCN architecture"
|
|
|
|
|
float p = -0.1565827 * x + 1.570796;
|
|
|
|
|
return p * sqrt(1.0 - x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
ref: https://developer.amd.com/wordpress/media/2012/10/Oat-AmbientApetureLighting.pdf
|
|
|
|
|
Approximate the area of intersection of two spherical caps.
|
|
|
|
|
|
|
|
|
|
With some modifcations proposed by the shadertoy implementation
|
|
|
|
|
*/
|
|
|
|
|
float SphericalCapsIntersectionAreaFast(float cosCap1, float cosCap2, float cap2, float cosDistance)
|
|
|
|
|
{
|
|
|
|
|
// Precompute constants
|
|
|
|
|
float radius1 = acosFastPositive(cosCap1); // First caps radius (arc length in radians)
|
|
|
|
|
float radius2 = cap2; // Second caps radius (in radians)
|
|
|
|
|
float dist = acosFast(cosDistance); // Distance between caps (radians between centers of caps)
|
|
|
|
|
|
|
|
|
|
// Conditional expressions instead of if-else
|
|
|
|
|
float check1 = min(radius1, radius2) <= max(radius1, radius2) - dist;
|
|
|
|
|
float check2 = radius1 + radius2 <= dist;
|
|
|
|
|
|
|
|
|
|
// Ternary operator to replace if-else
|
|
|
|
|
float result = check1 ? (1.0 - max(cosCap1, cosCap2)) : (check2 ? 0.0 : 1.0 - max(cosCap1, cosCap2));
|
|
|
|
|
|
|
|
|
|
float delta = abs(radius1 - radius2);
|
|
|
|
|
float x = 1.0 - saturate((dist - delta) / max(radius1 + radius2 - delta, FLT_EPS));
|
|
|
|
|
|
|
|
|
|
// simplified smoothstep()
|
|
|
|
|
float area = sq(x) * (-2.0 * x + 3.0);
|
|
|
|
|
|
|
|
|
|
// Multiply by (1.0 - max(cosCap1, cosCap2)) only once
|
|
|
|
|
return area * result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float DirectionalOcclusionSphere(float3 rayPosition, float3 spherePosition, float sphereRadius, float4 coneProperties)
|
|
|
|
|
{
|
|
|
|
|
float3 occluderPosition = spherePosition.xyz - rayPosition;
|
|
|
|
|
float occluderLength2 = dot(occluderPosition, occluderPosition);
|
|
|
|
|
float3 occluderDir = occluderPosition * rsqrt(occluderLength2);
|
|
|
|
|
|
|
|
|
|
float cosPhi = dot(occluderDir, coneProperties.xyz);
|
|
|
|
|
// sq(sphere.w) should be a uniform --> capsuleRadius^2
|
|
|
|
|
float cosTheta = sqrt(occluderLength2 / (sq(sphereRadius) + occluderLength2));
|
|
|
|
|
float cosCone = cos(coneProperties.w);
|
|
|
|
|
|
|
|
|
|
return 1.0 - SphericalCapsIntersectionAreaFast(cosTheta, cosCone, coneProperties.w, cosPhi) / (1.0 - cosCone);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float DirectionalOcclusionCapsule(float3 rayPosition, float3 capsuleA, float3 capsuleB, float capsuleRadius, float4 coneProperties)
|
|
|
|
|
{
|
|
|
|
|
float3 Ld = capsuleB - capsuleA;
|
|
|
|
|
float3 L0 = capsuleA - rayPosition;
|
|
|
|
|
float a = dot(coneProperties.xyz, Ld);
|
|
|
|
|
float t = saturate(dot(L0, a * coneProperties.xyz - Ld) / (dot(Ld, Ld) - a * a));
|
|
|
|
|
float3 positionToRay = capsuleA + t * Ld;
|
|
|
|
|
|
|
|
|
|
return DirectionalOcclusionSphere(rayPosition, positionToRay, capsuleRadius, coneProperties);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CalcCapsuleShadowByIndexV2(float3 ro, float4 cone, uint s, uint e, float intensity, float intensity1)
|
|
|
|
|
{
|
|
|
|
|
float shadow = 1.0;
|
|
|
|
|
for (uint i = s; i < e; ++i)
|
|
|
|
|
{
|
|
|
|
|
Capsule c = _CapsuleData[i];
|
|
|
|
|
shadow*= DirectionalOcclusionCapsule(ro, c.a, c.b, c.radius, cone);
|
|
|
|
|
}
|
|
|
|
|
shadow = saturate(lerp(1, shadow, intensity));
|
|
|
|
|
return saturate(lerp(1, shadow, intensity1));
|
|
|
|
|
}
|
|
|
|
|
float CalcSphereOcclusion2(in float3 pos, in float3 nor, in float4 sph)
|
|
|
|
|
{
|
|
|
|
|
float3 di = sph.xyz - pos;
|
|
|
|
|
float l = length(di);
|
|
|
|
|
float rad = sph.w;
|
|
|
|
|
float nl = max(0.0, dot(nor, di / l));
|
|
|
|
|
return (1.0 - nl * rad * rad / (l * l ));
|
|
|
|
|
|
|
|
|
|
// float3 di = (sph.xyz - pos) / sph.w;
|
|
|
|
|
// float sqL = dot(di, di);
|
|
|
|
|
// return clamp(1.0 - dot(nor, di) * rsqrt(sqL * sqL * sqL), 0.1, 1);
|
|
|
|
|
}
|
2026-01-30 17:49:21 +08:00
|
|
|
|
2026-01-30 17:57:41 +08:00
|
|
|
float CalcSphereOcclusion2_( in float3 pos, in float3 nor, in float4 sph )
|
2026-01-30 17:49:21 +08:00
|
|
|
{
|
|
|
|
|
float3 di = sph.xyz - pos;
|
|
|
|
|
float l = length(di);
|
|
|
|
|
float nl = dot(nor,di/l) * _CapsuleShadowData[0].ConeAngle;
|
|
|
|
|
float h = l/sph.w;
|
|
|
|
|
float h2 = h*h;
|
|
|
|
|
float k2 = 1.0 - h2*nl*nl;
|
|
|
|
|
|
|
|
|
|
// above/below horizon
|
|
|
|
|
// EXACT: Quilez - https://iquilezles.org/articles/sphereao
|
|
|
|
|
float res = max(0.0,nl)/h2;
|
|
|
|
|
|
|
|
|
|
// intersecting horizon
|
|
|
|
|
if( k2 > 0.001 )
|
|
|
|
|
{
|
|
|
|
|
#if 0
|
|
|
|
|
// EXACT : Lagarde/de Rousiers - https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
|
|
|
|
|
res = nl*acos(-nl*sqrt( (h2-1.0)/(1.0-nl*nl) )) - sqrt(k2*(h2-1.0));
|
|
|
|
|
res = res/h2 + atan( sqrt(k2/(h2-1.0)));
|
|
|
|
|
res /= 3.141593;
|
|
|
|
|
#else
|
|
|
|
|
// APPROXIMATED : Quilez - https://iquilezles.org/articles/sphereao
|
|
|
|
|
res = (nl*h+1.0)/h2;
|
|
|
|
|
res = 0.33*res*res;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1 -res;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 20:42:09 +08:00
|
|
|
float CalcCapsuleOcclusionByIndexV2(float3 p, float3 n, uint s, uint e, float intensity, float intensity1)
|
|
|
|
|
{
|
|
|
|
|
float ao = 1.0;
|
|
|
|
|
for (uint i = s; i < e; ++i)
|
|
|
|
|
{
|
|
|
|
|
Capsule capsule = _CapsuleData[i];
|
|
|
|
|
float3 ba = capsule.b - capsule.a;
|
|
|
|
|
float3 pa = p - capsule.a;
|
|
|
|
|
float l = dot(ba, ba);
|
|
|
|
|
// p 在 ba 上投影长度与 ba 长度比值
|
|
|
|
|
float t = /* abs(l) < 1e-8f ? 0.0 : */ saturate(dot(pa, ba) / l);
|
|
|
|
|
float3 positionToRay = capsule.a + t * ba;
|
2026-01-30 17:57:41 +08:00
|
|
|
ao *= CalcSphereOcclusion2_(p, n, float4(positionToRay, capsule.radius));
|
2025-09-11 20:42:09 +08:00
|
|
|
}
|
|
|
|
|
ao = saturate(lerp(1, ao, intensity));
|
|
|
|
|
return saturate(lerp(1, ao, intensity1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsInBounds(float3 p, float3 c, float b, out float intensity)
|
|
|
|
|
{
|
|
|
|
|
float3 diff = abs(p - c);
|
|
|
|
|
if (diff.x < b && diff.y < b && diff.z < b)
|
|
|
|
|
{
|
|
|
|
|
intensity = saturate(dot(b, b) / dot(diff.xz, diff.xz));
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
intensity = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 GetConeProperties(float3 lightDir, float coneAngle)
|
|
|
|
|
{
|
|
|
|
|
float4 cone = float4(lightDir.xyz, radians(coneAngle) * 0.5);
|
|
|
|
|
return cone;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 19:05:14 +08:00
|
|
|
void CalcCapsuleShadow(float3 worldPos, float3 worldNormal, out float shadow, out float occlusion)
|
2025-09-11 20:42:09 +08:00
|
|
|
{
|
2025-09-25 19:05:14 +08:00
|
|
|
shadow = 1.0;
|
|
|
|
|
occlusion = 1.0;
|
2025-09-11 20:42:09 +08:00
|
|
|
#if _MultiCapsule_Shadow_AO_ON || _MultiCapsule_AO_ON
|
|
|
|
|
for (uint i = 0; i < _CharactersCount; ++i)
|
|
|
|
|
{
|
|
|
|
|
float intensity = 1;
|
|
|
|
|
Character c = _CharacterData[i];
|
|
|
|
|
if (IsInBounds(worldPos, c.position, c.radius * 3, intensity))
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
#ifdef _MultiCapsule_Shadow_AO_ON
|
|
|
|
|
uint mask = c.capsuleAOSettingMask;
|
|
|
|
|
uint areaCnt = countbits(mask);
|
|
|
|
|
for (uint a = 0; a < areaCnt; ++a)
|
|
|
|
|
{
|
|
|
|
|
uint index = firstbitlow(mask);
|
|
|
|
|
mask ^= 1 << index;
|
|
|
|
|
CapsuleShadowArea area = _CapsuleShadowData[index];
|
|
|
|
|
for (uint b = area.lightStartID; b < area.lightEndID; ++b)
|
|
|
|
|
{
|
2025-09-25 19:05:14 +08:00
|
|
|
float4 lightDir = _CapsuleLightsDir[b];
|
2025-09-28 19:40:35 +08:00
|
|
|
//float4 cone = GetConeProperties(lightDir.xyz, area.ConeAngle);
|
|
|
|
|
float4 cone = GetConeProperties(normalize(lightDir.xyz - worldPos), area.ConeAngle);
|
|
|
|
|
float tempIntensity = intensity / smoothstep(0.1, 2, lightDir.w);
|
2025-09-11 20:42:09 +08:00
|
|
|
float tempShadow = CalcCapsuleShadowByIndex(worldPos, cone.xyz, c.capsuleStartID, c.capsuleEndID, area.ShadowSharpness, tempIntensity, area.ShadowIntensity);
|
|
|
|
|
// float tempShadow = CalcCapsuleShadowByIndexV2(worldPos, cone, c.capsuleStartID, c.capsuleEndID, tempIntensity, area.ShadowIntensity);
|
|
|
|
|
shadow = min(shadow, tempShadow);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-30 17:49:21 +08:00
|
|
|
// occlusion *= CalcCapsuleOcclusionByIndex(worldPos, worldNormal, c.capsuleStartID, c.capsuleEndID, intensity, _CapsuleShadowData[0].AmbientIntensity);
|
|
|
|
|
occlusion *= CalcCapsuleOcclusionByIndexV2(worldPos, worldNormal, c.capsuleStartID, c.capsuleEndID, intensity, _CapsuleShadowData[0].AmbientIntensity);
|
2025-09-11 20:42:09 +08:00
|
|
|
#elif defined(_MultiCapsule_AO_ON)
|
2025-09-25 19:05:14 +08:00
|
|
|
occlusion *= CalcCapsuleOcclusionByIndex(worldPos, worldNormal, c.capsuleStartID, c.capsuleEndID, intensity, _CapsuleShadowData[0].AmbientIntensity);
|
2025-09-11 20:42:09 +08:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endif
|