201 lines
9.3 KiB
HLSL
201 lines
9.3 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
|
||
|
||
// Fur geometry shader for NiloToonCharacter — lilToon-style barycentric fur shell generation.
|
||
// Adapted from lilToon's lil_common_vert_fur.hlsl (AppendFur / geom functions).
|
||
//
|
||
// This file is included by the fur pass blocks in NiloToonCharacter.shader,
|
||
// AFTER NiloToonCharacter_Shared.hlsl (so Varyings struct and all helpers are available).
|
||
|
||
#pragma once
|
||
|
||
// NiloFurUtil.hlsl is already included via NiloAllUtilIncludes.hlsl -> Shared.hlsl.
|
||
// Do NOT include it again here — duplicate include causes "redefinition of NiloLerp3"
|
||
// because Unity may resolve relative vs absolute paths differently, bypassing #pragma once.
|
||
|
||
//=============================================================================
|
||
// InterpolateVaryings — single source of truth for Varyings struct interpolation.
|
||
//
|
||
// ** MAINTENANCE NOTE **
|
||
// When a field is added to or removed from the Varyings struct in
|
||
// NiloToonCharacter_Shared.hlsl, this function MUST be updated to match.
|
||
// A corresponding comment exists at the Varyings struct definition.
|
||
//=============================================================================
|
||
Varyings InterpolateVaryings(Varyings a, Varyings b, Varyings c, float3 f)
|
||
{
|
||
Varyings r = (Varyings)0;
|
||
|
||
// positionCS will be recomputed from interpolated positionWS by the caller
|
||
// r.positionCS is left as 0 here
|
||
|
||
r.uv01 = NiloLerp3(a.uv01, b.uv01, c.uv01, f);
|
||
r.uv23 = NiloLerp3(a.uv23, b.uv23, c.uv23, f);
|
||
|
||
r.positionWS_ZOffsetFinalSum = NiloLerp3(a.positionWS_ZOffsetFinalSum, b.positionWS_ZOffsetFinalSum, c.positionWS_ZOffsetFinalSum, f);
|
||
|
||
r.SH_fogFactor = NiloLerp3(a.SH_fogFactor, b.SH_fogFactor, c.SH_fogFactor, f);
|
||
r.normalWS_averageShadowAttenuation = NiloLerp3(a.normalWS_averageShadowAttenuation, b.normalWS_averageShadowAttenuation, c.normalWS_averageShadowAttenuation, f);
|
||
|
||
r.smoothedNormalWS = NiloLerp3(a.smoothedNormalWS, b.smoothedNormalWS, c.smoothedNormalWS, f);
|
||
|
||
#if VaryingsHasViewDirTS
|
||
r.viewDirTS = NiloLerp3(a.viewDirTS, b.viewDirTS, c.viewDirTS, f);
|
||
#endif
|
||
|
||
#if VaryingsHasTangentWS
|
||
r.tangentWS = NiloLerp3(a.tangentWS, b.tangentWS, c.tangentWS, f);
|
||
#endif
|
||
|
||
r.color = NiloLerp3(a.color, b.color, c.color, f);
|
||
|
||
#if _NILOTOON_DEBUG_SHADING
|
||
r.uv8 = NiloLerp3(a.uv8, b.uv8, c.uv8, f);
|
||
#endif
|
||
|
||
// Fur-specific fields
|
||
r.furVector = NiloLerp3(a.furVector, b.furVector, c.furVector, f);
|
||
r.furLayer = 0; // caller sets this
|
||
r.furVertexID = a.furVertexID; // not consumed after geometry; keep the nointerpolation field initialized
|
||
|
||
// GPU instancing / stereo
|
||
#if !NiloToonCharSelfShadowCasterPass
|
||
UNITY_TRANSFER_INSTANCE_ID(a, r);
|
||
UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(a, r);
|
||
#endif
|
||
|
||
return r;
|
||
}
|
||
|
||
void NiloApplyFurDepthPrepassZBias(inout float4 positionCS, float bias)
|
||
{
|
||
#if UNITY_REVERSED_Z
|
||
positionCS.z -= bias;
|
||
#else
|
||
positionCS.z += bias;
|
||
#endif
|
||
}
|
||
|
||
//=============================================================================
|
||
// AppendFurQuad — emit 2 vertices (base + tip) forming one fur quad edge.
|
||
// Adapted from lilToon's AppendFur() in lil_common_vert_fur.hlsl lines 256-371.
|
||
//=============================================================================
|
||
void AppendFurQuad(
|
||
inout TriangleStream<Varyings> outStream,
|
||
Varyings input[3],
|
||
float3 furVectors[3],
|
||
float3 factor)
|
||
{
|
||
Varyings output = InterpolateVaryings(input[0], input[1], input[2], factor);
|
||
float3 posWS = output.positionWS_ZOffsetFinalSum.xyz;
|
||
|
||
// Base vertex (furLayer = 0) — at original mesh surface
|
||
output.furLayer = 0;
|
||
output.positionCS = TransformWorldToHClip(posWS);
|
||
#if NiloToonFurDepthPrepassPass || NiloToonFurPrepassBufferDepthPass
|
||
NiloApplyFurDepthPrepassZBias(output.positionCS, 0.0000001);
|
||
#endif
|
||
outStream.Append(output);
|
||
|
||
// Tip vertex (furLayer = 1) — displaced by fur vector
|
||
float3 mixVector = NiloLerp3(furVectors[0], furVectors[1], furVectors[2], factor);
|
||
posWS += mixVector;
|
||
output.positionWS_ZOffsetFinalSum.xyz = posWS;
|
||
output.furLayer = 1;
|
||
output.positionCS = TransformWorldToHClip(posWS);
|
||
#if NiloToonFurDepthPrepassPass || NiloToonFurPrepassBufferDepthPass
|
||
NiloApplyFurDepthPrepassZBias(output.positionCS, 0.000001);
|
||
#endif
|
||
outStream.Append(output);
|
||
}
|
||
|
||
//=============================================================================
|
||
// FurShellGeom — geometry shader entry point.
|
||
// Receives one triangle, emits fur shell quads using lilToon's barycentric layout.
|
||
// Adapted from lilToon's geom() in lil_common_vert_fur.hlsl lines 375-503.
|
||
//
|
||
// D3D11 geometry shader limit: maxvertexcount × output_scalars ≤ 1024.
|
||
// NiloToon's Varyings struct is large (40-48 scalars depending on keywords)
|
||
// because fur reuses the full toon lighting pipeline (0% fragment duplication).
|
||
// This limits us to layer 1-2 (max 14 verts). 14 × 48 = 672, safely under 1024.
|
||
// (lilToon supports layer 3 because its v2f struct is much smaller.)
|
||
//
|
||
// maxvertexcount breakdown:
|
||
// LayerNum == 1: 3 AppendFurQuad calls × 2 verts = 6 + closing 2 = 8
|
||
// LayerNum == 2: 6 AppendFurQuad calls × 2 verts = 12 + closing 2 = 14
|
||
//=============================================================================
|
||
[maxvertexcount(14)]
|
||
void FurShellGeom(triangle Varyings input[3], inout TriangleStream<Varyings> outStream)
|
||
{
|
||
// When _NILOTOON_FUR keyword is not enabled on this material, emit nothing.
|
||
// The renderer feature still enqueues the fur pass for all materials,
|
||
// but only materials with the keyword active should actually produce fur shells.
|
||
#if !_NILOTOON_FUR
|
||
return;
|
||
#endif
|
||
|
||
// Per-vertex fur vectors (already computed in vertex shader)
|
||
float3 furVectors[3];
|
||
furVectors[0] = input[0].furVector;
|
||
furVectors[1] = input[1].furVector;
|
||
furVectors[2] = input[2].furVector;
|
||
|
||
// Per-vertex noise randomization using stable vertex ID hashing (from lilToon lines 461-469).
|
||
// Do not hash positionWS here: object rotation changes world positions and makes randomize flicker.
|
||
uint h0 = input[0].furVertexID;
|
||
uint h1 = input[1].furVertexID;
|
||
uint h2 = input[2].furVertexID;
|
||
|
||
uint3 n0 = (h0 * 3u + h1 * 1u + h2 * 1u) * uint3(1597334677u, 3812015801u, 2912667907u);
|
||
uint3 n1 = (h0 * 1u + h1 * 3u + h2 * 1u) * uint3(1597334677u, 3812015801u, 2912667907u);
|
||
uint3 n2 = (h0 * 1u + h1 * 1u + h2 * 3u) * uint3(1597334677u, 3812015801u, 2912667907u);
|
||
|
||
float3 noise0 = normalize(float3(n0) * (2.0 / float(0xFFFFFFFFu)) - 1.0);
|
||
float3 noise1 = normalize(float3(n1) * (2.0 / float(0xFFFFFFFFu)) - 1.0);
|
||
float3 noise2 = normalize(float3(n2) * (2.0 / float(0xFFFFFFFFu)) - 1.0);
|
||
|
||
float randomizedFurLength = _NiloFurLength * _NiloFurStrength * _NiloFurRandomize;
|
||
furVectors[0] += noise0 * randomizedFurLength;
|
||
furVectors[1] += noise1 * randomizedFurLength;
|
||
furVectors[2] += noise2 * randomizedFurLength;
|
||
|
||
// Per-vertex fur length mask (from lilToon lines 471-475)
|
||
// Sample in geometry shader via tex2Dlod (explicit mip 0) to scale fur length per-vertex
|
||
float2 uv0 = input[0].uv01.xy;
|
||
float2 uv1 = input[1].uv01.xy;
|
||
float2 uv2 = input[2].uv01.xy;
|
||
furVectors[0] *= SAMPLE_TEXTURE2D_LOD(_NiloFurLengthMask, sampler_linear_repeat, uv0, 0).r;
|
||
furVectors[1] *= SAMPLE_TEXTURE2D_LOD(_NiloFurLengthMask, sampler_linear_repeat, uv1, 0).r;
|
||
furVectors[2] *= SAMPLE_TEXTURE2D_LOD(_NiloFurLengthMask, sampler_linear_repeat, uv2, 0).r;
|
||
|
||
// _FurCutoutLength is now applied in the vertex shader (only for depth prepass).
|
||
// Color pass gets full-length fur — the outer portion blends softly (lilToon FurTwoPass design).
|
||
|
||
// Emit fur quads using lilToon's barycentric layout (lines 477-502).
|
||
// Layer count limited to 1-2 due to D3D11 geometry shader output limit
|
||
// (maxvertexcount × scalars_per_vertex ≤ 1024). NiloToon's full Varyings
|
||
// struct is too large for layer 3 (would need 26 verts × 39+ scalars > 1024).
|
||
float layerNum = _NiloFurLayerNum;
|
||
|
||
if (layerNum < 1.5) // LayerNum == 1: vertices only (3 quads = 6 verts)
|
||
{
|
||
AppendFurQuad(outStream, input, furVectors, float3(1.0, 0.0, 0.0));
|
||
AppendFurQuad(outStream, input, furVectors, float3(0.0, 1.0, 0.0));
|
||
AppendFurQuad(outStream, input, furVectors, float3(0.0, 0.0, 1.0));
|
||
}
|
||
else // LayerNum == 2: interleaved vertices + midpoints (6 quads = 12 verts)
|
||
{
|
||
// lilToon interleaves: vertex A, midpoint BC, vertex B, midpoint AC, vertex C, midpoint AB
|
||
// This creates a valid continuous triangle strip.
|
||
AppendFurQuad(outStream, input, furVectors, float3(1.0, 0.0, 0.0)); // vertex A
|
||
AppendFurQuad(outStream, input, furVectors, float3(0.0, 1.0, 1.0) / 2.0); // midpoint BC
|
||
AppendFurQuad(outStream, input, furVectors, float3(0.0, 1.0, 0.0)); // vertex B
|
||
AppendFurQuad(outStream, input, furVectors, float3(1.0, 0.0, 1.0) / 2.0); // midpoint AC
|
||
AppendFurQuad(outStream, input, furVectors, float3(0.0, 0.0, 1.0)); // vertex C
|
||
AppendFurQuad(outStream, input, furVectors, float3(1.0, 1.0, 0.0) / 2.0); // midpoint AB
|
||
}
|
||
|
||
// Closing vertex + restart strip (matches lilToon line 502)
|
||
AppendFurQuad(outStream, input, furVectors, float3(1.0, 0.0, 0.0));
|
||
outStream.RestartStrip();
|
||
}
|