276 lines
9.7 KiB
HLSL
Raw Normal View History

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);
}
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;
ao *= CalcSphereOcclusion2(p, n, float4(positionToRay, capsule.radius));
}
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-11 20:42:09 +08:00
float4 cone = GetConeProperties(lightDir.xyz, area.ConeAngle);
float tempIntensity = intensity / saturate(1 * smoothstep(0.1, 2, lightDir.w));
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);
}
}
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
//occlusion *= CalcCapsuleOcclusionByIndexV2(worldPos, worldNormal, c.capsuleStartID, c.capsuleEndID, intensity, _CapsuleShadowData[0].AmbientIntensity);
#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