164 lines
8.0 KiB
HLSL
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;
|
|
}
|