This commit is contained in:
StarBeats 2025-07-25 18:05:45 +08:00
parent cd647427ca
commit 532647c7b9
6 changed files with 278 additions and 28 deletions

View File

@ -3270,7 +3270,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
TestLightTrans: {fileID: 634813099}
TestCharacterTrans: {fileID: 1880735404}
TestW: 1
TestW: 5
--- !u!4 &217261640
Transform:
m_ObjectHideFlags: 0
@ -4870,6 +4870,10 @@ PrefabInstance:
propertyPath: m_Name
value: NestedParentArmature_Unpack
objectReference: {fileID: 0}
- target: {fileID: 8508310942573128499, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3}
propertyPath: m_RenderShadows
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8508310942573128499, guid: c708a3b79cd542b42bbfedb17e213bc1, type: 3}
propertyPath: m_RenderPostProcessing
value: 1
@ -20029,8 +20033,8 @@ Transform:
m_GameObject: {fileID: 1215335447}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -10.14, y: -3.394, z: -136.12}
m_LocalScale: {x: 0.36361, y: 0.36361, z: 0.36361}
m_LocalPosition: {x: -10.14, y: -3.394, z: -132.664}
m_LocalScale: {x: 0.36361, y: 2.6302094, z: 5.8246684}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}

View File

@ -142,9 +142,10 @@ MonoBehaviour:
m_Active: 1
settings:
RenderPassEvent: 450
AmbientIntensity: 0.32
ShadowIntensity: 0.28
ShadowSharpness: 0.27
AmbientIntensity: 1.63
ShadowIntensity: 0
ShadowSharpness: 0
ConeAngle: 8.1
CapsuleAOMat: {fileID: 2100000, guid: c90d40f0d9828744b916dbed0eed9db6, type: 2}
--- !u!114 &-5418649131825517062
MonoBehaviour:
@ -610,7 +611,7 @@ MonoBehaviour:
MaskMat: {fileID: 2100000, guid: 1368366248809f24e9a110be3075d1b8, type: 2}
BlurMat: {fileID: 2100000, guid: 0487cfddca141924d8a1736f6c0b9b7b, type: 2}
BlurIterations: 3
BlurSpread: 0.2
BlurSpread: 0.5
--- !u!114 &6334271670068977784
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@ -15,7 +15,7 @@ MonoBehaviour:
version: 7
--- !u!21 &2100000
Material:
serializedVersion: 7
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@ -24,7 +24,8 @@ Material:
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ShaderKeywords:
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
@ -144,8 +145,8 @@ Material:
- _WorkflowMode: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
- _Color: {r: 0.5, g: 0.5, b: 0.5, a: 1}
- _BaseColor: {r: 0.7830189, g: 0.7830189, b: 0.7830189, a: 1}
- _Color: {r: 0.7830188, g: 0.7830188, b: 0.7830188, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []

View File

@ -32,6 +32,8 @@ namespace X.Rendering.Feature
public float AmbientIntensity = 0.2f;
public float ShadowIntensity = 0.4f;
public float ShadowSharpness = 20;
[Range(1, 90)]
public float ConeAngle = 1;
public Material CapsuleAOMat;
}
@ -119,6 +121,7 @@ namespace X.Rendering.Feature
cmd.SetGlobalFloat("_AmbientIntensity", settings.AmbientIntensity);
cmd.SetGlobalFloat("_ShadowIntensity", settings.ShadowIntensity);
cmd.SetGlobalFloat("_ShadowSharpness", settings.ShadowSharpness);
cmd.SetGlobalFloat("_ConeAngle", settings.ConeAngle);
var renderer = renderingData.cameraData.renderer;
cmd.SetRenderTarget(renderer.cameraColorTargetHandle, loadAction: RenderBufferLoadAction.Load, storeAction: RenderBufferStoreAction.Store);

View File

@ -48,6 +48,26 @@ Shader "XRP/CapsuleAO"
float _ShadowIntensity;
float _ShadowSharpness;
float _ConeAngle;
#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);
}
Varyings vert(uint vertexID: SV_VertexID)
{
@ -78,8 +98,11 @@ Shader "XRP/CapsuleAO"
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);
@ -87,8 +110,37 @@ Shader "XRP/CapsuleAO"
shadow *= s;
}
shadow = saturate(lerp(shadow, 1, intensity));
return saturate(lerp(shadow, 1, _ShadowIntensity));
shadow = saturate(lerp(1, shadow, intensity));
return saturate(lerp(1, shadow, _ShadowIntensity));
}
float _smooth(float x) { return x * x * (3.0 - 2.0 * x); }
float CalcCapsuleOcclusionFast(float3 p, float3 n, float3 a, float3 b, float r, float maxD)
{
float r2 = r*r;
// Original function but in ^2 space
float3 ba = b - a, pa = p - a;
float h = saturate(dot(pa,ba)/dot(ba,ba));
float3 d = pa - h*ba;
float l = dot(d,d);
float o = -dot(d,n)*r2*r/(l*l);
// Max dist
o *= _smooth(saturate(1.0 - (l-r2)/(maxD*maxD)));
return saturate(o);
}
float CalcCapsuleOcclusion(float3 p, float3 n, float3 a, float3 b, float r, float maxD)
{
// closest sphere
float3 ba = b - a, pa = p - a;
float h = saturate(dot(pa,ba)/dot(ba,ba));
float3 d = pa - h*ba;
float l = length(d);
float o = dot(-d,n)*r*r/(l*l*l); // occlusion of closest sphere
o *= 1.0 + r*(l-r)/(l*l); // multiplier
// o *= 1.0 - saturate((l - r) / maxD);
return saturate(1.0 - o);
}
float CalcCapsuleOcclusionByIndex(float3 p, float3 n, uint s, uint e, float intensity)
@ -103,15 +155,196 @@ Shader "XRP/CapsuleAO"
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 o = 1.0 - max(0.0, dot(-d, n)) * 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, _AmbientIntensity), 0.6, 1);
}
ao = saturate(lerp(ao, 1, intensity));
return saturate(lerp(ao, 1, _AmbientIntensity));
// Sphere occlusion
float CalcSphereOcclusion( in float3 pos, in float3 nor, in float4 sph )
{
float3 di = sph.xyz - pos;
float l = length(di);
float nl = (dot(nor,di/l));
float rad = sph.w;
float v = 1 - rad - nl;
if(l < 0.55)
return abs((1 - rad - nl));// + _ShadowSharpness;
float h = l < rad ? 1.0 : l / rad;
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 clamp(1.0 - res, 0.6, 1);
}
float CalcCapsuleOcclusionByIndexV2(float3 p, float3 n, uint s, uint e, float4 cone, float intensity)
{
float ao = 1.0;
for (uint i = s; i < e; ++i)
{
Capsule capsule = _CapsuleData[i + int(_ShadowIntensity)];
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 = min(ao, CalcSphereOcclusion(p, n, float4(positionToRay, capsule.radius)));
}
return ao;
ao = saturate(lerp(1, ao, intensity));
return saturate(lerp(1, ao, _AmbientIntensity));
}
// Approximate the area of intersection of two spherical caps, from 'Ambient Aperture Lighting'
// fRadius0 : First caps radius (arc length in radians)
// fRadius1 : Second caps radius (in radians)
// fDist : Distance between caps (radians between centers of caps)
float SphericalCapIntersectionAreaFast(float fRadius0, float fRadius1, float fDist)
{
float fArea;
if (fDist <= max(fRadius0, fRadius1) - min(fRadius0, fRadius1))
{
// One cap is completely inside the other
fArea = 6.283185308f - 6.283185308f * cos(min(fRadius0, fRadius1));
}
else if (fDist >= fRadius0 + fRadius1)
{
// No intersection exists
fArea = 0;
}
else
{
float fDiff = abs(fRadius0 - fRadius1);
fArea = smoothstep(0.0f,
1.0f,
1.0f - saturate((fDist - fDiff) / (fRadius0 + fRadius1 - fDiff)));
fArea *= 6.283185308f - 6.283185308f * cos(min(fRadius0, fRadius1));
}
return fArea;
}
/*
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);
}
//this sort of work but doesn't support rotations
float DirectionalOcclusionCube(float3 rayPosition, float3 cubePos, float3 cubeSize, float4 cubeRotation, float4 coneProperties)
{
float3 occluder = cubePos.xyz - rayPosition;
occluder /= cubeSize * 0.5f;
float occluderLength2 = dot(cubeRotation.xyz, occluder);
float3 occluderDir = occluder * rsqrt(occluderLength2);
//occluderDir *= cubeRotation;
//occluderDir = cross(occluderDir, cubeRotation);
float cosPhi = dot(occluderDir, coneProperties.xyz);
// sq(sphere.w) should be a uniform --> capsuleRadius^2
float cosTheta = sqrt(occluderLength2 / (sq(1.0f) + 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);
}
float4 GetConeProperties(float3 lightDir)
{
float4 cone = float4(lightDir.xyz, radians(_ConeAngle) * 0.5);
cone.xyz = normalize(cone.xyz);
return cone;
}
float CalcCapsuleShadowByIndexV2(float3 ro, float4 cone, uint s, uint e, in float k, float intensity)
{
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, _ShadowIntensity));
}
bool IsInBounds(float3 p, float3 c, float b, out float intensity)
@ -119,7 +352,7 @@ Shader "XRP/CapsuleAO"
float3 diff = abs(p - c);
if (diff.x < b && diff.y < b && diff.z < b)
{
intensity = saturate(dot(diff, diff) / dot(b, b));
intensity = saturate(dot(b, b) / dot(diff.xz, diff.xz));
return true;
}
intensity = 0;
@ -130,21 +363,26 @@ Shader "XRP/CapsuleAO"
{
float shadow = 1.0;
float occlusion = 1.0;
float4 cone = GetConeProperties(_MainLightPosition.xyz);
for (uint i = 0; i < _CharactersCount; ++i)
{
float intensity;
float intensity = 1;
Character c = _CharacterData[i];
if (IsInBounds(worldPos, c.position, c.radius, intensity))
// if (IsInBounds(worldPos, c.position, c.radius*10, intensity))
{
float tempIntensity = intensity / saturate(1 * smoothstep(0.001, 1, c.lightDir.w));
float tempShadow = CalcCapsuleShadowByIndex(worldPos, _MainLightPosition.xyz, c.startID, c.endID, _ShadowSharpness, tempIntensity);
shadow = min(shadow, tempShadow);
occlusion *= CalcCapsuleOcclusionByIndex(worldPos, worldNormal, c.startID, c.endID, intensity);
float tempIntensity = intensity / saturate(1 * smoothstep(0.1, 2, c.lightDir.w));
// float tempShadow = CalcCapsuleShadowByIndex(worldPos, _MainLightPosition.xyz, c.startID, c.endID, _ShadowSharpness, tempIntensity);
// float tempShadow = CalcCapsuleShadowByIndexV2(worldPos, cone, c.startID, c.endID, _ShadowSharpness, tempIntensity);
// shadow = min(shadow, tempShadow);
// occlusion *= CalcCapsuleOcclusionByIndex(worldPos, worldNormal, c.startID, c.endID, intensity);
occlusion *= CalcCapsuleOcclusionByIndexV2(worldPos, worldNormal, c.startID, c.endID,cone, intensity);
}
}
return shadow * occlusion;
}
float4 frag(Varyings input) : SV_Target
{
const float2 uv = input.texcoord;
@ -160,6 +398,7 @@ Shader "XRP/CapsuleAO"
float4 worldPos = mul(UNITY_MATRIX_I_VP, float4(sceneUV, depth, 1));
worldPos /= worldPos.w;
float3 worldNormal = SampleSceneNormals(uv);
float ao = CalcAmbientOcclusion(worldPos.xyz, worldNormal);
// float4 shadowCoord = TransformWorldToShadowCoord(worldPos.xyz);
@ -167,7 +406,7 @@ Shader "XRP/CapsuleAO"
// //只在阴影处产生AO
// ao = lerp(ao, 1, step(0.5, mainLight.shadowAttenuation));
return half4(1, 0, 0, ao);
return half4(ao, 0, 0, ao);
}
ENDHLSL

View File

@ -83,8 +83,10 @@ namespace X.Rendering.Feature
{
var capsule = item.Value[i];
//capsule.direction
var a = capsule.center - new Vector3(0, capsule.height / 2, 0);
var b = capsule.center + new Vector3(0, capsule.height / 2, 0);
var a = capsule.center - new Vector3(0, capsule.height / 2 - capsule.radius, 0);
var b = capsule.center + new Vector3(0, capsule.height / 2 - capsule.radius, 0);
//var a = capsule.center - new Vector3(0, capsule.height / 2, 0);
//var b = capsule.center + new Vector3(0, capsule.height / 2, 0);
a = capsule.transform.TransformPoint(a);
b = capsule.transform.TransformPoint(b);
CapsuleArray[capsuleArrayIndex++] = new()
@ -101,7 +103,7 @@ namespace X.Rendering.Feature
CharacterArray[characterArrayIndex++] = new()
{
position = transform.position,
radius = 5,
radius = transform.lossyScale.x,
lightDir = -new Vector4(TestLightTrans.position.x, TestLightTrans.position.y, TestLightTrans.position.z, -TestW),
startID = startID,
endID = capsuleArrayIndex,