556 lines
21 KiB
HLSL

// NiloToon Character Fur - Shared Definitions
// Full-featured structs, CBUFFER, and common functions
#ifndef NILOTOON_CHARACTER_FUR_SHARED_INCLUDED
#define NILOTOON_CHARACTER_FUR_SHARED_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
// Include NiloToon utility functions (order matters - InvLerpRemapUtil must come before PerspectiveRemovalUtil)
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloInvLerpRemapUtil.hlsl"
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloBlendEquationUtil.hlsl"
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloDitherFadeoutClipUtil.hlsl"
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloPerspectiveRemovalUtil.hlsl"
#include "../../ShaderLibrary/NiloUtilityHLSL/NiloUVCalculateUtil.hlsl"
//------------------------------------------------------------------------------------------------------------------------------
// NiloToon Global Light Override Variables (from NiloToonCharacterMainLightOverrider)
//------------------------------------------------------------------------------------------------------------------------------
half4 _GlobalUserOverriddenFinalMainLightDirWSParam; // xyz: direction, w: 1 if enabled
half4 _GlobalUserOverriddenFinalMainLightColorParam; // rgb: color, w: 1 if enabled
//------------------------------------------------------------------------------------------------------------------------------
// Helper function for view direction (compatible with all URP versions)
//------------------------------------------------------------------------------------------------------------------------------
float3 GetWorldSpaceViewDirSafe(float3 positionWS)
{
return normalize(_WorldSpaceCameraPos.xyz - positionWS);
}
//------------------------------------------------------------------------------------------------------------------------------
// Helper function for shadow coord (compatible with all URP versions)
//------------------------------------------------------------------------------------------------------------------------------
float4 GetShadowCoordSafe(VertexPositionInputs vertexInput)
{
#if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)
return ComputeScreenPos(vertexInput.positionCS);
#else
return TransformWorldToShadowCoord(vertexInput.positionWS);
#endif
}
//------------------------------------------------------------------------------------------------------------------------------
// Get Main Light with NiloToon Override Support
//------------------------------------------------------------------------------------------------------------------------------
Light GetMainLightWithNiloToonOverride()
{
Light mainLight = GetMainLight();
// Apply NiloToon MainLightOverrider if enabled
if (_GlobalUserOverriddenFinalMainLightDirWSParam.w > 0.5)
{
mainLight.direction = _GlobalUserOverriddenFinalMainLightDirWSParam.xyz;
}
if (_GlobalUserOverriddenFinalMainLightColorParam.w > 0.5)
{
mainLight.color = _GlobalUserOverriddenFinalMainLightColorParam.rgb;
}
return mainLight;
}
//------------------------------------------------------------------------------------------------------------------------------
// CBUFFER for SRP Batcher
//------------------------------------------------------------------------------------------------------------------------------
CBUFFER_START(UnityPerMaterial)
// Base
float4 _BaseMap_ST;
half4 _BaseColor;
half _Cutoff;
// Normal Map
float4 _BumpMap_ST;
half _BumpScale;
// Fur Shape
float4 _FurNoiseMask_ST;
float4 _FurMask_ST;
float4 _FurLengthMask_ST;
float4 _FurVector;
float4 _FurVectorTex_ST;
half _FurVectorScale;
half _FurGravity;
half _FurRandomize;
half _FurAO;
half _FurLayerNum;
half _FurRootOffset;
// Fur Rim
half4 _FurRimColor;
half _FurRimFresnelPower;
half _FurRimAntiLight;
// Cel Shading
half _CelShadeMidPoint;
half _CelShadeSoftness;
// Shadow Color
half4 _ShadowColor;
half _ShadowBrightness;
half _ShadowHueShift;
half _ShadowSaturationBoost;
half _ShadowValueMultiplier;
// MatCap Add
float4 _MatCapAddMap_ST;
half4 _MatCapAddColor;
half _MatCapAddIntensity;
float4 _MatCapAddMask_ST;
// MatCap Multiply
float4 _MatCapMulMap_ST;
half _MatCapMulIntensity;
// Rim Light
half4 _RimLightColor;
half _RimLightPower;
half _RimLightIntensity;
// Emission
float4 _EmissionMap_ST;
half4 _EmissionColor;
half _EmissionIntensity;
// Occlusion
float4 _OcclusionMap_ST;
half _OcclusionStrength;
// Outline
half _OutlineWidth;
half4 _OutlineColor;
float4 _OutlineWidthMask_ST;
// Rendering
half _Cull;
half _FurCull;
// Dissolve
float _DissolveAmount;
float _DissolveMode;
float _DissolveThresholdMapTilingX;
float _DissolveThresholdMapTilingY;
float _DissolveNoiseStrength;
float _DissolveBorderRange;
half4 _DissolveBorderTintColor;
float _AllowPerCharacterDissolve;
// Per-Character Color Controls
half _PerCharacterBaseColorMultiply;
half4 _PerCharacterBaseColorTint;
half3 _PerCharEffectTintColor; // Set by NiloToonPerCharacterRenderController
half3 _PerCharEffectAddColor; // Set by NiloToonPerCharacterRenderController
half _PerCharEffectDesaturatePercentage;
half4 _PerCharEffectLerpColor;
// Per-Character Rim Light
half _UsePerCharacterRimLightIntensity;
half _PerCharacterRimLightIntensity;
half4 _PerCharacterRimLightColor;
half _PerCharacterRimLightSharpnessPower;
// BaseMap Override
half _PerCharacterBaseMapOverrideAmount;
half4 _PerCharacterBaseMapOverrideTintColor;
float4 _PerCharacterBaseMapOverrideMap_ST;
half _PerCharacterBaseMapOverrideBlendMode;
half _PerCharacterBaseMapOverrideUVOption;
// Character Area Color Fill
half _ShouldRenderCharacterAreaColorFill;
half4 _CharacterAreaColorFillColor;
float4 _CharacterAreaColorFillTexture_ST;
half _CharacterAreaColorFillUVOption;
// Dither Opacity
half _DitherOpacity;
// Dither Fadeout
half _DitherFadeoutAmount;
half _DitherFadeoutNormalScaleFix;
half _AllowPerCharacterDitherFadeout;
// Perspective Removal
half _PerspectiveRemovalAmount;
half _PerspectiveRemovalRadius;
half _PerspectiveRemovalStartHeight;
half _PerspectiveRemovalEndHeight;
// ZOffset
half _PerCharacterZOffset;
// Character Bound (for dissolve)
float _CharacterBoundRadius;
// Character ID (for accessing global arrays)
uint _CharacterID;
CBUFFER_END
//------------------------------------------------------------------------------------------------------------------------------
// NiloToon Global Per-Character Arrays
// For properties that change every frame, use global arrays instead of CBUFFER for performance
//------------------------------------------------------------------------------------------------------------------------------
#ifndef MAX_CHARACTER_COUNT
#define MAX_CHARACTER_COUNT 256
#endif
float3 _NiloToonGlobalPerCharHeadBonePosWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharFaceForwardDirWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharFaceUpwardDirWSArray[MAX_CHARACTER_COUNT];
float3 _NiloToonGlobalPerCharBoundCenterPosWSArray[MAX_CHARACTER_COUNT];
//------------------------------------------------------------------------------------------------------------------------------
// Texture Declarations (all textures declared unconditionally to avoid compilation issues)
//------------------------------------------------------------------------------------------------------------------------------
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);
// Fur textures
TEXTURE2D(_FurNoiseMask); SAMPLER(sampler_FurNoiseMask);
TEXTURE2D(_FurMask); SAMPLER(sampler_FurMask);
TEXTURE2D(_FurLengthMask); SAMPLER(sampler_FurLengthMask);
TEXTURE2D(_FurVectorTex); SAMPLER(sampler_FurVectorTex);
// MatCap
TEXTURE2D(_MatCapAddMap); SAMPLER(sampler_MatCapAddMap);
TEXTURE2D(_MatCapAddMask); SAMPLER(sampler_MatCapAddMask);
TEXTURE2D(_MatCapMulMap); SAMPLER(sampler_MatCapMulMap);
// Emission
TEXTURE2D(_EmissionMap); SAMPLER(sampler_EmissionMap);
// Occlusion
TEXTURE2D(_OcclusionMap); SAMPLER(sampler_OcclusionMap);
// Outline
TEXTURE2D(_OutlineWidthMask); SAMPLER(sampler_OutlineWidthMask);
// Dissolve
#if _NILOTOON_DISSOLVE
TEXTURE2D(_DissolveThresholdMap); SAMPLER(sampler_DissolveThresholdMap);
#endif
// BaseMap Override
TEXTURE2D(_PerCharacterBaseMapOverrideMap); SAMPLER(sampler_PerCharacterBaseMapOverrideMap);
// Character Area Color Fill
TEXTURE2D(_CharacterAreaColorFillTexture); SAMPLER(sampler_CharacterAreaColorFillTexture);
//------------------------------------------------------------------------------------------------------------------------------
// Vertex Input Structure
//------------------------------------------------------------------------------------------------------------------------------
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float4 color : COLOR;
uint vertexID : SV_VertexID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
//------------------------------------------------------------------------------------------------------------------------------
// Vertex to Geometry Structure (for Fur Shell Pass)
//------------------------------------------------------------------------------------------------------------------------------
struct V2G
{
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 tangentWS : TEXCOORD3;
float3 furVector : TEXCOORD4;
float fogFactor : TEXCOORD5;
uint vertexID : TEXCOORD6;
float4 color : COLOR;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
//------------------------------------------------------------------------------------------------------------------------------
// Fragment Input Structure
//------------------------------------------------------------------------------------------------------------------------------
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 tangentWS : TEXCOORD3;
float fogFactor : TEXCOORD4;
float furLayer : TEXCOORD5; // 0 = base, 1 = outer shell
float4 shadowCoord : TEXCOORD6;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
//------------------------------------------------------------------------------------------------------------------------------
// Color Space Conversion (RGB <-> HSV)
//------------------------------------------------------------------------------------------------------------------------------
float3 RGBToHSV(float3 rgb)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 p = lerp(float4(rgb.bg, K.wz), float4(rgb.gb, K.xy), step(rgb.b, rgb.g));
float4 q = lerp(float4(p.xyw, rgb.r), float4(rgb.r, p.yzx), step(p.x, rgb.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
float3 HSVToRGB(float3 hsv)
{
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www);
return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y);
}
//------------------------------------------------------------------------------------------------------------------------------
// Utility: Grayscale
//------------------------------------------------------------------------------------------------------------------------------
half Grayscale(half3 color)
{
return dot(color, half3(0.299, 0.587, 0.114));
}
//------------------------------------------------------------------------------------------------------------------------------
// Normal Map Unpacking
//------------------------------------------------------------------------------------------------------------------------------
half3 UnpackNormalWithScale(half4 packedNormal, half scale)
{
#if defined(UNITY_NO_DXT5nm)
half3 normal = packedNormal.xyz * 2.0 - 1.0;
#else
half3 normal;
normal.xy = packedNormal.ag * 2.0 - 1.0;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));
#endif
normal.xy *= scale;
return normalize(normal);
}
//------------------------------------------------------------------------------------------------------------------------------
// MatCap UV Calculation
//------------------------------------------------------------------------------------------------------------------------------
float2 GetMatCapUV(float3 normalWS, float3 viewDirWS)
{
float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, normalWS);
return viewNormal.xy * 0.5 + 0.5;
}
//------------------------------------------------------------------------------------------------------------------------------
// Apply HSV Change to Color
//------------------------------------------------------------------------------------------------------------------------------
half3 ApplyHSVChange(half3 color, half hueOffset, half saturationBoost, half valueMul)
{
float3 hsv = RGBToHSV(color);
hsv.x = frac(hsv.x + hueOffset);
hsv.y = saturate(hsv.y * saturationBoost);
hsv.z = hsv.z * valueMul;
return HSVToRGB(hsv);
}
//------------------------------------------------------------------------------------------------------------------------------
// NiloToon Style Cel Shading (Full Version)
//------------------------------------------------------------------------------------------------------------------------------
half3 ApplyNiloToonCelShading(
half3 baseColor,
half3 lightColor,
half3 lightDir,
half3 normalWS,
half3 viewDir,
half shadowAttenuation,
half occlusion
)
{
// Calculate NdotL
half NdotL = dot(normalWS, lightDir);
// Cel shading with configurable mid point and softness
half halfLambert = NdotL * 0.5 + 0.5;
half celShadeResult = smoothstep(
_CelShadeMidPoint + 0.5 - _CelShadeSoftness,
_CelShadeMidPoint + 0.5 + _CelShadeSoftness,
halfLambert
);
// Apply shadow map attenuation
celShadeResult *= shadowAttenuation;
// Apply occlusion
celShadeResult *= occlusion;
half3 finalColor = baseColor;
#if defined(_SHADOW_COLOR)
// Apply HSV adjustment to shadow
half3 shadowAlbedo = ApplyHSVChange(
baseColor,
_ShadowHueShift,
_ShadowSaturationBoost,
_ShadowValueMultiplier
);
// Apply shadow tint and brightness
shadowAlbedo *= _ShadowColor.rgb * _ShadowBrightness;
// Blend between shadow and lit based on cel shade result
finalColor = lerp(shadowAlbedo, baseColor, celShadeResult);
#else
// Simple brightness reduction in shadow
finalColor = lerp(baseColor * 0.5, baseColor, celShadeResult);
#endif
// Apply light color
finalColor *= lightColor;
return finalColor;
}
//------------------------------------------------------------------------------------------------------------------------------
// Fur Vector Calculation (from lilToon)
//------------------------------------------------------------------------------------------------------------------------------
float3 CalculateFurVector(float3 normalOS, float4 tangentOS, float2 uv, float4 vertexColor, bool useVertexColor)
{
// Build TBN matrix
float3 bitangentOS = normalize(cross(normalOS, tangentOS.xyz)) * (tangentOS.w * length(normalOS));
float3x3 tbnOS = float3x3(tangentOS.xyz, bitangentOS, normalOS);
// Start with base fur vector
float3 furVector = _FurVector.xyz + float3(0, 0, 0.001);
// Optional: blend with vertex color
if (useVertexColor)
{
float3 vertexNormal = vertexColor.xyz * 2.0 - 1.0;
furVector = normalize(furVector + vertexNormal);
}
// Transform to tangent space, then apply fur direction texture
#if defined(NILOTOON_FUR_SHELL_PASS)
float4 furDirTex = SAMPLE_TEXTURE2D_LOD(_FurVectorTex, sampler_FurVectorTex, uv * _FurVectorTex_ST.xy + _FurVectorTex_ST.zw, 0);
float3 furDirFromTex = UnpackNormalWithScale(furDirTex, _FurVectorScale);
furVector = normalize(furVector + furDirFromTex);
#endif
// Transform to object space direction
furVector = mul(normalize(furVector), tbnOS);
furVector *= _FurVector.w;
// Apply gravity
float furLength = length(furVector);
furVector.y -= _FurGravity * furLength;
return furVector;
}
//------------------------------------------------------------------------------------------------------------------------------
// Base Pass Vertex Shader
//------------------------------------------------------------------------------------------------------------------------------
#if defined(NILOTOON_FUR_BASE_PASS)
Varyings vert(Attributes input)
{
Varyings output = (Varyings)0;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
// Transform
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.normalWS = normalInput.normalWS;
output.tangentWS = float4(normalInput.tangentWS, input.tangentOS.w);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
// Apply Perspective Removal
output.positionCS = NiloDoPerspectiveRemoval(
output.positionCS,
output.positionWS,
_NiloToonGlobalPerCharHeadBonePosWSArray[_CharacterID],
_PerspectiveRemovalRadius,
_PerspectiveRemovalAmount,
_PerspectiveRemovalStartHeight,
_PerspectiveRemovalEndHeight
);
// Apply per-character Z offset
if (_PerCharacterZOffset != 0)
{
output.positionCS.z += _PerCharacterZOffset * output.positionCS.w;
}
// Shadow coord
output.shadowCoord = GetShadowCoordSafe(vertexInput);
// Fog
output.fogFactor = ComputeFogFactor(vertexInput.positionCS.z);
// Base pass has no fur layer
output.furLayer = -1.0;
return output;
}
#endif
//------------------------------------------------------------------------------------------------------------------------------
// Fur Pass Vertex Shader (outputs to Geometry Shader)
//------------------------------------------------------------------------------------------------------------------------------
#if defined(NILOTOON_FUR_SHELL_PASS)
V2G vert_fur(Attributes input)
{
V2G output = (V2G)0;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
// Transform position and normal to world space
// IMPORTANT: Use GetVertexPositionInputs to match Base Pass transformation exactly
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
output.positionWS = vertexInput.positionWS;
output.normalWS = normalInput.normalWS;
output.tangentWS = float4(normalInput.tangentWS, input.tangentOS.w);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
output.color = input.color;
output.vertexID = input.vertexID;
// Calculate fur vector in world space
float3 furVectorOS = CalculateFurVector(input.normalOS, input.tangentOS, input.uv, input.color, false);
output.furVector = TransformObjectToWorldDir(furVectorOS, false);
// Apply fur length mask
float furLengthMask = SAMPLE_TEXTURE2D_LOD(_FurLengthMask, sampler_FurLengthMask, input.uv * _FurLengthMask_ST.xy + _FurLengthMask_ST.zw, 0).r;
output.furVector *= furLengthMask;
// Note: Perspective Removal and Z offset will be applied in geometry shader
// We don't apply them here in vertex shader for fur shells
// because geometry shader generates multiple shells and needs to apply
// the transformations consistently to all generated vertices
// Fog
float4 posCS = TransformWorldToHClip(output.positionWS);
output.fogFactor = ComputeFogFactor(posCS.z);
return output;
}
#endif
#endif // NILOTOON_CHARACTER_FUR_SHARED_INCLUDED