2025-12-15 01:52:59 +09:00

150 lines
6.8 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;
output.positionCS = TransformWorldToHClip(basePositionWS);
output.furLayer = 0;
// Clipping canceller for near plane
#if defined(UNITY_REVERSED_Z)
if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0)
{
output.positionCS.z = output.positionCS.z * 0.0001 + output.positionCS.w * 0.999;
}
#else
if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0)
{
output.positionCS.z = output.positionCS.z * 0.0001 - output.positionCS.w * 0.999;
}
#endif
outStream.Append(output);
// Tip position (furLayer = 1)
float3 mixedFurVector = LerpBary(furVectors[0], furVectors[1], furVectors[2], factor);
float3 tipPositionWS = basePositionWS + mixedFurVector;
output.positionWS = tipPositionWS;
output.positionCS = TransformWorldToHClip(tipPositionWS);
output.furLayer = layerProgress;
// Clipping canceller for near plane
#if defined(UNITY_REVERSED_Z)
if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0)
{
output.positionCS.z = output.positionCS.z * 0.0001 + output.positionCS.w * 0.999;
}
#else
if(output.positionCS.w < _ProjectionParams.y * 1.01 && output.positionCS.w > 0)
{
output.positionCS.z = output.positionCS.z * 0.0001 - output.positionCS.w * 0.999;
}
#endif
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