164 lines
8.0 KiB
HLSL

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