193 lines
9.2 KiB
HLSL
193 lines
9.2 KiB
HLSL
// NiloToon Character Fur - Geometry Shader
|
|
// Shell-based fur generation (based on lilToon implementation)
|
|
|
|
#ifndef NILOTOON_CHARACTER_FUR_GEOMETRY_INCLUDED
|
|
#define NILOTOON_CHARACTER_FUR_GEOMETRY_INCLUDED
|
|
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
// Barycentric Interpolation Helper
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
float LerpBary(float a, float b, float c, float3 factor) { return a * factor.x + b * factor.y + c * factor.z; }
|
|
float2 LerpBary(float2 a, float2 b, float2 c, float3 factor) { return a * factor.x + b * factor.y + c * factor.z; }
|
|
float3 LerpBary(float3 a, float3 b, float3 c, float3 factor) { return a * factor.x + b * factor.y + c * factor.z; }
|
|
float4 LerpBary(float4 a, float4 b, float4 c, float3 factor) { return a * factor.x + b * factor.y + c * factor.z; }
|
|
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
// Append Single Fur Shell (Base + Tip)
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
void AppendFurShell(
|
|
inout TriangleStream<Varyings> outStream,
|
|
V2G input[3],
|
|
float3 furVectors[3],
|
|
float3 factor,
|
|
float layerProgress // 0 = base, 1 = outer
|
|
)
|
|
{
|
|
Varyings output = (Varyings)0;
|
|
|
|
UNITY_TRANSFER_INSTANCE_ID(input[0], output);
|
|
UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(input[0], output);
|
|
|
|
// Interpolate base attributes
|
|
output.uv = LerpBary(input[0].uv, input[1].uv, input[2].uv, factor);
|
|
output.normalWS = normalize(LerpBary(input[0].normalWS, input[1].normalWS, input[2].normalWS, factor));
|
|
output.tangentWS = LerpBary(input[0].tangentWS, input[1].tangentWS, input[2].tangentWS, factor);
|
|
output.tangentWS.xyz = normalize(output.tangentWS.xyz);
|
|
output.fogFactor = LerpBary(input[0].fogFactor, input[1].fogFactor, input[2].fogFactor, factor);
|
|
|
|
// Base position (furLayer = 0)
|
|
float3 basePositionWS = LerpBary(input[0].positionWS, input[1].positionWS, input[2].positionWS, factor);
|
|
|
|
output.positionWS = basePositionWS;
|
|
float4 basePositionCS_BeforePerspective = TransformWorldToHClip(basePositionWS);
|
|
output.furLayer = 0;
|
|
|
|
// Apply Perspective Removal to base
|
|
float4 basePositionCS_AfterPerspective = NiloDoPerspectiveRemoval(
|
|
basePositionCS_BeforePerspective,
|
|
basePositionWS,
|
|
_NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID],
|
|
_PerspectiveRemovalRadius,
|
|
_PerspectiveRemovalAmount,
|
|
_PerspectiveRemovalStartHeight,
|
|
_PerspectiveRemovalEndHeight
|
|
);
|
|
|
|
output.positionCS = basePositionCS_AfterPerspective;
|
|
|
|
// Apply per-character Z offset
|
|
if (_PerCharacterZOffset != 0)
|
|
{
|
|
output.positionCS.z += _PerCharacterZOffset * output.positionCS.w;
|
|
}
|
|
|
|
outStream.Append(output);
|
|
|
|
// Tip position (furLayer = 1)
|
|
// CRITICAL FIX: Transform fur vector to clip space using the SAME perspective removal parameters as base
|
|
|
|
// Get the interpolated fur vector in world space
|
|
float3 mixedFurVector = LerpBary(furVectors[0], furVectors[1], furVectors[2], factor);
|
|
|
|
// Calculate tip in world space (for lighting and shading)
|
|
float3 tipPositionWS = basePositionWS + mixedFurVector;
|
|
output.positionWS = tipPositionWS;
|
|
|
|
// Transform fur vector to clip space by transforming both endpoints
|
|
// IMPORTANT: Don't apply perspective removal to tip separately!
|
|
// Instead, transform the fur vector in clip space relative to the base
|
|
|
|
// Convert the fur vector direction to clip space
|
|
// We approximate this by transforming a point at base + furVector
|
|
float4 furVectorEndCS_BeforePerspective = TransformWorldToHClip(tipPositionWS);
|
|
|
|
// Calculate the fur vector in clip space BEFORE perspective removal
|
|
float4 furVectorCS_BeforePerspective = furVectorEndCS_BeforePerspective - basePositionCS_BeforePerspective;
|
|
|
|
// Apply the same perspective removal transformation to the fur vector as we did to the base
|
|
// This maintains the correct relative offset in perspective-removed space
|
|
// The key insight: we want to preserve the fur direction/length relative to the base's transformation
|
|
|
|
// Transform the fur vector's XY component using the base's perspective removal scale
|
|
// Perspective removal formula: newXY = lerp(originalXY, originalXY * w / centerPosVSz, amount)
|
|
// For the fur vector, we need to apply the same scale change
|
|
|
|
float3 centerPosWS = _NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID];
|
|
float centerPosVSz = mul(UNITY_MATRIX_V, float4(centerPosWS,1)).z;
|
|
|
|
// Calculate perspective removal amount for base position
|
|
float perspectiveRemovalAreaSphere = saturate(_PerspectiveRemovalRadius - distance(basePositionWS, centerPosWS) / _PerspectiveRemovalRadius);
|
|
float perspectiveRemovalAreaWorldHeight = saturate(invLerp(_PerspectiveRemovalStartHeight, _PerspectiveRemovalEndHeight, basePositionWS.y));
|
|
float perspectiveRemovalFinalAmount = _PerspectiveRemovalAmount * perspectiveRemovalAreaSphere * perspectiveRemovalAreaWorldHeight;
|
|
|
|
// Apply perspective removal scale to fur vector
|
|
// The scale factor is: lerp(1, w / centerPosVSz, amount)
|
|
float basePerspectiveScale = lerp(1.0, abs(basePositionCS_BeforePerspective.w) * rcp(abs(centerPosVSz)), perspectiveRemovalFinalAmount);
|
|
|
|
// Scale the fur vector's XY by the same factor
|
|
float2 furVectorCS_XY = furVectorCS_BeforePerspective.xy * basePerspectiveScale;
|
|
|
|
// Construct the final tip position in clip space
|
|
output.positionCS = basePositionCS_AfterPerspective;
|
|
output.positionCS.xy += furVectorCS_XY;
|
|
output.positionCS.z = furVectorEndCS_BeforePerspective.z; // Keep original Z depth for tip
|
|
output.positionCS.w = furVectorEndCS_BeforePerspective.w; // Keep original W for tip
|
|
|
|
output.furLayer = layerProgress;
|
|
|
|
// Apply per-character Z offset
|
|
if (_PerCharacterZOffset != 0)
|
|
{
|
|
output.positionCS.z += _PerCharacterZOffset * output.positionCS.w;
|
|
}
|
|
|
|
outStream.Append(output);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
// Geometry Shader - Shell Generation
|
|
// Based on lilToon's fur geometry shader
|
|
//------------------------------------------------------------------------------------------------------------------------------
|
|
[maxvertexcount(40)]
|
|
void geom_fur(triangle V2G input[3], inout TriangleStream<Varyings> outStream)
|
|
{
|
|
UNITY_SETUP_INSTANCE_ID(input[0]);
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input[0]);
|
|
|
|
// Get fur vectors for each vertex
|
|
float3 furVectors[3];
|
|
furVectors[0] = input[0].furVector;
|
|
furVectors[1] = input[1].furVector;
|
|
furVectors[2] = input[2].furVector;
|
|
|
|
// Apply procedural randomization using vertex IDs (from lilToon)
|
|
uint3 n0 = (input[0].vertexID * 3 + input[1].vertexID * 1 + input[2].vertexID * 1) * uint3(1597334677U, 3812015801U, 2912667907U);
|
|
uint3 n1 = (input[0].vertexID * 1 + input[1].vertexID * 3 + input[2].vertexID * 1) * uint3(1597334677U, 3812015801U, 2912667907U);
|
|
uint3 n2 = (input[0].vertexID * 1 + input[1].vertexID * 1 + input[2].vertexID * 3) * uint3(1597334677U, 3812015801U, 2912667907U);
|
|
|
|
float3 noise0 = normalize(float3(n0) * (2.0 / float(0xffffffffU)) - 1.0);
|
|
float3 noise1 = normalize(float3(n1) * (2.0 / float(0xffffffffU)) - 1.0);
|
|
float3 noise2 = normalize(float3(n2) * (2.0 / float(0xffffffffU)) - 1.0);
|
|
|
|
furVectors[0] += noise0 * _FurVector.w * _FurRandomize;
|
|
furVectors[1] += noise1 * _FurVector.w * _FurRandomize;
|
|
furVectors[2] += noise2 * _FurVector.w * _FurRandomize;
|
|
|
|
// Generate shells based on layer count
|
|
// Layer pattern from lilToon: spreads shells across triangle vertices
|
|
|
|
if (_FurLayerNum >= 1)
|
|
{
|
|
// 3 shells at each vertex
|
|
AppendFurShell(outStream, input, furVectors, float3(1.0, 0.0, 0.0), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.0, 1.0, 0.0), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.0, 0.0, 1.0), 1.0);
|
|
}
|
|
|
|
if (_FurLayerNum >= 2)
|
|
{
|
|
// 3 more shells at edge midpoints
|
|
AppendFurShell(outStream, input, furVectors, float3(0.0, 0.5, 0.5), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.5, 0.0, 0.5), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.5, 0.5, 0.0), 1.0);
|
|
}
|
|
|
|
if (_FurLayerNum >= 3)
|
|
{
|
|
// 6 more shells for dense fur
|
|
AppendFurShell(outStream, input, furVectors, float3(1.0/6.0, 4.0/6.0, 1.0/6.0), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.0, 0.5, 0.5), 0.8);
|
|
AppendFurShell(outStream, input, furVectors, float3(1.0/6.0, 1.0/6.0, 4.0/6.0), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.5, 0.0, 0.5), 0.8);
|
|
AppendFurShell(outStream, input, furVectors, float3(4.0/6.0, 1.0/6.0, 1.0/6.0), 1.0);
|
|
AppendFurShell(outStream, input, furVectors, float3(0.5, 0.5, 0.0), 0.8);
|
|
}
|
|
|
|
// Final shell at first vertex to close the strip
|
|
AppendFurShell(outStream, input, furVectors, float3(1.0, 0.0, 0.0), 1.0);
|
|
|
|
outStream.RestartStrip();
|
|
}
|
|
|
|
#endif // NILOTOON_CHARACTER_FUR_GEOMETRY_INCLUDED
|