// SPDX-License-Identifier: (Not available for this version, you are only allowed to use this software if you have express permission from the copyright holder and agreed to the latest NiloToonURP EULA) // Copyright (c) 2021 Kuroneko ShaderLab Limited // Pure fur utility functions — no struct dependencies. // Adapted from lilToon's fur implementation (lil_common_vert_fur.hlsl, lil_common_frag.hlsl). #pragma once //--------------------------------------------------------------------- // Barycentric interpolation helper //--------------------------------------------------------------------- // Interpolate a value across 3 triangle vertices using barycentric weights. // factor.x = weight for vertex A, factor.y = weight for B, factor.z = weight for C. // Sum of factor components should be 1.0. float NiloLerp3(float a, float b, float c, float3 f) { return a * f.x + b * f.y + c * f.z; } float2 NiloLerp3(float2 a, float2 b, float2 c, float3 f) { return a * f.x + b * f.y + c * f.z; } float3 NiloLerp3(float3 a, float3 b, float3 c, float3 f) { return a * f.x + b * f.y + c * f.z; } float4 NiloLerp3(float4 a, float4 b, float4 c, float3 f) { return a * f.x + b * f.y + c * f.z; } half NiloLerp3(half a, half b, half c, float3 f) { return a * f.x + b * f.y + c * f.z; } half2 NiloLerp3(half2 a, half2 b, half2 c, float3 f) { return a * f.x + b * f.y + c * f.z; } half3 NiloLerp3(half3 a, half3 b, half3 c, float3 f) { return a * f.x + b * f.y + c * f.z; } half4 NiloLerp3(half4 a, half4 b, half4 c, float3 f) { return a * f.x + b * f.y + c * f.z; } //--------------------------------------------------------------------- // Normal blending helper // Matches lilToon lilBlendNormal(). //--------------------------------------------------------------------- float3 NiloBlendNormal(float3 dstNormal, float3 srcNormal) { return float3(dstNormal.xy + srcNormal.xy, dstNormal.z * srcNormal.z); } //--------------------------------------------------------------------- // Compute fur extrusion vector in world space // Adapted from lilToon lil_common_vert_fur.hlsl lines 166-200 //--------------------------------------------------------------------- // furVectorTS: tangent-space fur direction (xyz) — from _NiloFurVector.xyz // furLength: world-space fur length — from _NiloFurLength // furCutoutLength: depth-prepass length scale — from _NiloFurCutoutLength or 1 // normalOS: object-space normal // tangentOS: object-space tangent (xyz + w sign) // gravity: gravity amount (0 = none, 1 = full) // vertexColor: vertex color RGB (optional blend source) // vc2fur: toggle for vertex color -> fur direction blend // furVectorTexTS: tangent-space vector texture sample // useFurVectorTex: toggle for vector texture blend float3 NiloComputeFurVectorWS( float3 furVectorTS, float furLength, float furCutoutLength, float3 normalOS, float4 tangentOS, float gravity, float3 vertexColor, float vc2fur, float3 furVectorTexTS, float useFurVectorTex) { // Build TBN in object space (same as lilToon) float3 bitangentOS = normalize(cross(normalOS, tangentOS.xyz)) * (tangentOS.w * length(normalOS)); float3x3 tbnOS = float3x3(tangentOS.xyz, bitangentOS, normalOS); // Start with base direction (ensure nonzero Z to avoid degenerate direction) float3 furDir = furVectorTS + float3(0, 0, 0.001); // Optionally blend with vertex color (lilToon's _VertexColor2FurVector approach) if (vc2fur > 0.5) { furDir = NiloBlendNormal(furDir, vertexColor); } // Optional vector texture follows lilToon: blend in tangent space before length/gravity. if (useFurVectorTex > 0.5) { furDir = NiloBlendNormal(furDir, furVectorTexTS); } // Transform direction from tangent space to object space, then scale by length. furDir = mul(normalize(furDir), tbnOS); furDir *= furLength * furCutoutLength; // Transform to world space (direction only, no translation) furDir = TransformObjectToWorldDir(furDir, false); // Apply gravity: pull down in world Y float len = length(furDir); furDir.y -= gravity * len; return furDir; } //--------------------------------------------------------------------- // Fur alpha — noise-based cutout per shell layer // Adapted from lilToon's OVERRIDE_FUR / LIL_FUR_LAYER_ALPHA //--------------------------------------------------------------------- // furLayer: 0 = root (base mesh), 1 = tip (geometry shader sets this) // rootOffset: shifts the cutout threshold toward roots (negative value, range [-1, 0]) // furNoise: noise texture sample [0,1] // furMask: mask texture sample [0,1] (1 = full fur, 0 = no fur) // Color pass alpha — transparent mode (matches lilToon FurTwoPass COLOR pass, LIL_RENDER == 2). // Smooth cubic falloff from root to tip. Alpha blending creates the soft fuzzy look. // See lil_common_frag.hlsl lines 416-420 (LIL_FUR_LAYER_ALPHA transparent variant). float NiloFurAlpha(float furLayer, float rootOffset, float furNoise, float furMask) { float furLayerShift = furLayer - furLayer * rootOffset + rootOffset; float furLayerAbs = abs(furLayerShift); float furAlpha = saturate(furNoise - furLayerShift * furLayerAbs * furLayerAbs); furAlpha *= furMask; return furAlpha; } // Depth prepass alpha — cutout mode for Nilo's full-toon fur prepass. // Matches lilToon's FurTwoPass LIL_FUR_PRE path: // - OVERRIDE_FUR computes the fourth-power + 0.25 alpha. // - lil_pass_forward_fur.hlsl then hardens it with saturate(alpha * 5 - 2) // before discarding zero alpha so the prepass writes only the cutout core. // The separate color pass keeps NiloFurAlpha() smooth for the soft outer shell. float NiloFurAlphaCutout(float furLayer, float rootOffset, float furNoise, float furMask) { float furLayerShift = furLayer - furLayer * rootOffset + rootOffset; float furLayerAbs = abs(furLayerShift); float furAlpha = saturate(furNoise - furLayerShift * furLayerAbs * furLayerAbs * furLayerAbs + 0.25); furAlpha *= furMask; furAlpha = saturate(furAlpha * 5.0 - 2.0); return furAlpha; } //--------------------------------------------------------------------- // Fur root ambient occlusion // Adapted from lilToon's LIL_FUR_LAYER_AO //--------------------------------------------------------------------- // color: input lit color // furLayer: 0 = root, 1 = tip // furAO: AO intensity (0 = none, 1 = full darkening at roots) half3 NiloApplyFurAO(half3 color, float furLayer, float furAO, float furLength) { // lilToon formula: color *= saturate(1 - noise + noise * furLayer) * ao * 1.25 + 1 - ao // Simplified: at root (furLayer=0), darkening = (1 - ao). At tip (furLayer=1), no darkening. // Scale AO by fur length: when fur is very short (< 0.01), AO fades out to avoid // visible dark lines on the surface that look like artifacts instead of depth. float lengthScale = saturate(furLength / 0.015); // full AO at length >= 0.015, fades below float scaledAO = furAO * lengthScale; float aoFactor = lerp(1.0 - scaledAO, 1.0, furLayer); return color * aoFactor; } //--------------------------------------------------------------------- // Fur rim fresnel — adds rim glow on outer shell edges //--------------------------------------------------------------------- // furLayer: 0 = root, 1 = tip // normalWS: world-space normal // viewDirWS: world-space view direction (pointing toward camera) // rimColor: rim highlight color (RGB) // fresnelPower: fresnel exponent // antiLight: how much to reduce rim in lit areas (0 = no reduction) // invLightingGray: grayscale inverse lighting, matching lilToon's fd.invLighting half3 NiloFurRim(float furLayer, float3 normalWS, float3 viewDirWS, half4 rimColor, float fresnelPower, float antiLight, half invLightingGray) { float fresnel = pow(saturate(1.0 - abs(dot(normalize(normalWS), viewDirWS))), fresnelPower); float rimMask = furLayer * fresnel; half3 rim = rimColor.rgb * rimMask * lerp(1.0, invLightingGray, antiLight); return rim; }