// 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