跳转至

name: liltoon-source-analysis category: research description: Systematic approach for analyzing lilToon shader source code — tracing the alpha/dissolve pipeline execution order, finding property definitions, and understanding how shader features interact. Based on lil_common_frag.hlsl + lil_pass_forward_normal.hlsl + lil_common_functions.hlsl analysis. trigger: When you need to understand how lilToon processes alpha, dissolve, main2ndTexture, main3rdTexture, alphamask, or any other fragment-shader feature; when debugging why a dissolve or alpha animation isn't working as expected


lilToon Source Code Analysis Guide

hy a dissolve or alpha animation isn't working as expected

lilToon Source Code Analysis Guide## Core Files (in order of relevance)

File Purpose
lil_pass_forward_normal.hlsl Main fragment shader — defines the execution order of all OVERRIDE_* macros
lil_common_frag.hlsl Fragment helper functions — lilGetMain2nd(), lilGetMain3rd(), OVERRIDE_DISSOLVE, OVERRIDE_ALPHAMASK, OVERRIDE_DITHER
lil_common_frag_alpha.hlsl SubPass 透明系统 — 第二个 Fragment Pass 的 alpha 处理,仅 LIL_RENDER==2 时启用。包含独立的 AlphaMask + Dissolve + Cutoff 处理,使用 _SubpassCutoff(独立于主 Pass 的 _Cutoff
lil_common_functions.hlsl Low-level calculate functions — lilCalcDissolve(), lilCalcDissolveWithNoise()
lil_common_input_opt.hlsl Shader property declarations — all _Main2nd*, _Main3rd*, dissolve params, _AlphaMaskMode
lil_common_input_base.hlsl Base property declarations
lil_common_macro.hlsl Macros, defines, LIL_RENDER values, 管线检测宏, LIL_SUBPASS_TRANSPARENT_MODE 定义(默认 0=Cutout, 1=Dither)
lil_pipeline_hdrp.hlsl HDRP 管线定义 — 仅 30 行,定义 LIL_HDRP 宏和阴影模式
ltspass_transparent.shader Transparent 模式的 SubPass 实现 — 包含 FORWARD + FORWARD Alpha 双 Pass,#define LIL_RENDER 2
lts_trans.shader / lts_o.shader / lts_cutout.shader 实际 shader 选择文件(通过 UsePass 引用 pass 实现)
lil_common.hlsl 顶层 include,自动包含对应管线的 pipeline 文件

Repository location: /root/lilToon/Assets/lilToon/Shader/Includes/./Shader/ltspass_*.shader Repository location**: /root/lilToon/Assets/lilToon/Shader/Includes/./Shader/ltspass_*.shader### SubPass 分析要点

SubPass 的 Fragment Shader 在 lil_common_frag_alpha.hlsl 中,包含的步骤: 1. UDIM Discard 2. Main UV 动画 3. Main Color / Outline Color 4. 2nd/3rd Layer Color 5. AlphaMask(与主 Pass 相同逻辑:OVERRIDE_ALPHAMASK) 6. Dissolve(与主 Pass 相同逻辑:OVERRIDE_DISSOLVE) 7. Dither(与主 Pass 条件不同) 8. Cutout:clip(fd.col.a - _Cutoff)clip(alphaRef - _SubpassCutoff)

关键配置lil_common_macro.hlsl 第 16 行 #define LIL_SUBPASS_TRANSPARENT_MODE 0 — 模式 0=Cutout(硬切),模式 1=Dither(抖动)。需要修改时在包含前 #undef 重新定义。

Shader 选择与 LIL_RENDER 的关系

Shader 文件 Shader 名称 LIL_RENDER RenderType Queue SubPass
lts_o.shader lilToon 0 (Opaque) Opaque Geometry
lts_cutout.shader Hidden/lilToonCutout 1 (Cutout) TransparentCutout AlphaTest
lts_trans.shader Hidden/lilToonTransparent 2 (Transparent) TransparentCutout AlphaTest+10 ✅(UsePass 引用 ltspass_transparent)
lts_twotrans.shader Hidden/lilToonTwoPassTransparent 2 Transparent Transparent
ltsmulti.shader lilToonMulti 0 (默认) Opaque Geometry

_TransparentMode 是编辑器属性,不是编译期条件。它由 lilToon Inspector 脚本(C#)用于自动切换材质的 shader 文件。修改 _TransparentMode → 自动调用 mat.shader = Shader.Find("Hidden/lilToonCutout") → 改变 LIL_RENDER。 文件。修改 _TransparentMode → 自动调用 mat.shader = Shader.Find("Hidden/lilToonCutout") → 改变 LIL_RENDER。### HDRP 分析要点

lil_pipeline_hdrp.hlsl/root/lilToon/Assets/lilToon/Shader/Includes/)内容: - 定义 LIL_HDRP 宏 - 设置 LIGHTLOOP_DISABLE_TILE_AND_CLUSTER - 默认 SHADOW_LOW - Include 多个 HDRP 内置包 - 阴影通过 SHADOW_LOW/MEDIUM/HIGH 宏控制 - VRChat 使用 BRP — HDRP 适配主要是独立 Unity 项目场景

管线自动检测:lilToon 通过检查 Unity 定义的内置宏自动选择 BRP/URP/HDRP,不需要手动修改。

Step-by-Step Analysis Method

Step 1: Find the execution order

In lil_pass_forward_normal.hlsl, search for the frag() function. All processing happens through OVERRIDE_* macros in a fixed sequence:

grep -n 'OVERRIDE_' /root/lilToon/Assets/lilToon/Shader/Includes/lil_pass_forward_normal.hlsl | grep -v '^#\|defined\|#if'

This reveals the exact pipeline order. For the main (non-outline) path, you'll see something like:

OVERRIDE_MAIN → OVERRIDE_NORMAL_1ST → OVERRIDE_NORMAL_2ND → OVERRIDE_AUDIOLINK →
OVERRIDE_MAIN2ND → OVERRIDE_MAIN3RD → OVERRIDE_ALPHAMASK → OVERRIDE_DISSOLVE →
OVERRIDE_DITHER → [Alpha/Cutoff] → LIL_PREMULTIPLY → OVERRIDE_DISSOLVE_ADD

Key insight: Each OVERRIDE_ is a macro defined in lil_common_frag.hlsl that calls specific functions. sight: Each OVERRIDE_ is a macro defined in lil_common_frag.hlsl that calls specific functions.### Step 2: Understand how OVERRIDE macros work

Most OVERRIDE macros are defined as function calls in lil_common_frag.hlsl:

// Pattern:
#if !defined(OVERRIDE_MAIN2ND)
    #define OVERRIDE_MAIN2ND \
        lilGetMain2nd(fd, color2nd, main2ndDissolveAlpha LIL_SAMP_IN(sampler_MainTex));
#endif

The actual function body is directly in the same file (e.g., void lilGetMain2nd(...)).

Step 3: Trace special wrappers around OVERRIDE_DISSOLVE

The dissolve macro in lil_pass_forward_normal.hlsl is NOT called directly — it's wrapped with special logic:

BEFORE_DISSOLVE
#if defined(LIL_FEATURE_DISSOLVE) && LIL_RENDER != 0
    float dissolveAlpha = 0.0;
    if (fd.dissolveActive) {
        float priorAlpha = fd.col.a;
        fd.col.a = 1.0f;
        OVERRIDE_DISSOLVE     // ← this is defined in lil_common_frag.hlsl
        if (fd.dissolveInvert) fd.col.a = 1.0f - fd.col.a;
        fd.col.a *= priorAlpha;
    }
#endif

This means dissolve always: 1. Saves current fd.col.a (which may have been modified by AlphaMask or Main2nd/3rd) 2. Sets fd.col.a = 1.0 (dissolve works on fully opaque) 3. Calls the dissolve function which multiplies fd.col.a *= dissolveMaskVal 4. Multiplies result back into the original alpha ion which multiplies fd.col.a *= dissolveMaskVal 4. Multiplies result back into the original alpha### Step 4: Trace the dissolve function itself

In lil_common_functions.hlsl, both lilCalcDissolve() and lilCalcDissolveWithNoise() follow the same pattern:

void lilCalcDissolve(inout float alpha, inout float dissolveAlpha, ...) {
    dissolveParams.xy = round(dissolveParams.xy); // mode, shape
    if(dissolveParams.r) {  // r > 0 means dissolve is enabled
        // ... calculate dissolveMaskVal based on shape
        alpha *= dissolveMaskVal;    // ← modifies alpha directly
    }
}

Critical insight: The alpha parameter passed is whatever variable was fed in: - Main dissolve → fd.col.a (the accumulated final alpha) - Main2nd dissolve → color2nd.a (only the 2nd texture alpha) - Main3rd dissolve → color3rd.a (only the 3rd texture alpha)

Step 5: Check guarding macros

Many features are conditionally compiled. Always check:

# See what guards exist around a feature
grep -B5 -A2 '_DissolveParams' lil_common_frag.hlsl | grep '#if\|#endif'
ards exist around a feature
grep -B5 -A2 '_DissolveParams' lil_common_frag.hlsl | grep '#if\|#endif'# Example: Main2nd dissolve requires LIL_FEATURE_LAYER_DISSOLVE

Common feature guards: - LIL_FEATURE_DISSOLVE — main dissolve enabled - LIL_FEATURE_LAYER_DISSOLVE — Main2nd/Main3rd dissolve enabled - LIL_FEATURE_ALPHAMASK — AlphaMask enabled - LIL_FEATURE_MAIN2ND / LIL_FEATURE_MAIN3RD — 2nd/3rd texture layers - LIL_LITE — if defined, Main2nd/Main3rd/Shadow/etc are completely removed - LIL_RENDER != 0 — non-Opaque modes only (dissolve/alphamask skip Opaque) - LIL_RENDER == 0 — Opaque: fd.col.a = 1.0 (ignores ALL alpha processing)

Step 6: Property definition locations

Properties are split across lil_common_input_opt.hlsl and lil_common_input_base.hlsl:

# Find where a property is declared
grep -n '_DissolveParams\|_Main2ndTex' lil_common_input_opt.hlsl

Note that some properties (like _UseMain2ndTex) use the lilBool type, which is a float under the hood. t some properties (like _UseMain2ndTex) use the lilBool type, which is a float under the hood.## Alpha Pipeline Reference (complete order)

For non-outline, non-Opaque (LIL_RENDER != 0) rendering:

Order Step What happens to fd.col.a
1 OVERRIDE_MAIN Sets fd.col.a = _MainTex.a * _Color.a
2 OVERRIDE_MAIN2ND Can overwrite/multiply fd.col.a via _Main2ndTexAlphaMode (1-4). Also: if LIL_FEATURE_LAYER_DISSOLVE, modifies color2nd.a independently
3 OVERRIDE_MAIN3RD Same as step 2 but for 3rd texture
4 OVERRIDE_ALPHAMASK fd.col.a = saturate(alphaMask * scale + value) — 5 modes (0=off, 1=overwrite, 2=multiply, 3=add, 4=subtract)
5 OVERRIDE_DISSOLVE Saves prior alpha → fd.col.a=1.0 → modifies fd.col.a via dissolve → restores prior
6 OVERRIDE_DITHER (Cutout only) dither comparison
7 Alpha cutoff Cutout: saturate((a - _Cutoff) / fwidth + 0.5); Transparent: clip(a - _Cutoff)
8 LIL_PREMULTIPLY fd.col.rgb *= fd.col.a (Transparent only)
9 (SubPass only) SubPass clip(alphaRef - _SubpassCutoff) 仅 LIL_RENDER==2 模式,在 SubPass 中再次执行 AlphaMask + Dissolve 后用 _SubpassCutoff 裁切
9 (forward only) OVERRIDE_DISSOLVE_ADD fd.emissionColor += _DissolveColor.rgb * dissolveAlpha
10 (forward only) Layer dissolve add fd.emissionColor += _Main2ndDissolveColor.rgb * main2ndDissolveAlpha (if LIL_FEATURE_LAYER_DISSOLVE)

Critical Insights

dDissolveColor.rgb * main2ndDissolveAlpha(ifLIL_FEATURE_LAYER_DISSOLVE`) |

Critical Insights### 2nd/3rd Dissolve vs Main Dissolve — Different alpha targets

Main dissolve passes fd.col.a directly → multiplies fd.col.a *= dissolveMaskVal. 2nd/3rd dissolve passes color2nd.a / color3rd.a → multiplies texture layer alpha, then this modified alpha is applied via _Main2ndTexAlphaMode/_Main3rdTexAlphaMode.

This means you can independently dissolve texture layers without affecting the base texture's dissolve. For example: main texture fully visible, 2nd texture dissolving in for a "pattern appears" effect.

Opaque mode kills all alpha

When LIL_RENDER == 0:

fd.col.a = 1.0;  // ← after ALL processing, including dissolve
This means dissolve on Opaque mode renders but ONLY the dissolve emission color shows — the alpha-cut portion is invisible. Dissolve requires Cutout or Transparent.

Main2nd/Main3rd require LIL_FEATURE_LAYER_DISSOLVE for their own dissolve

Without this compile-time macro, _Main2ndDissolveParams / _Main3rdDissolveParams are completely ignored — the dissolve code path is not compiled in.

Both outlines and main path process dissolve the same way

In lil_pass_forward_normal.hlsl, there are two code paths: #if defined(LIL_OUTLINE) and #else (main). Both apply the same OVERRIDE_ALPHAMASK → OVERRIDE_DISSOLVE → OVERRIDE_DITHER → Alpha sequence.

Bash Command Cheatsheet

# Full execution order
grep -n 'OVERRIDE_' lil_pass_forward_normal.hlsl | grep -v 'defined\\|#if\\|^$\\|ONLY\\|LIL_OUTLINE'
grep -n 'OVERRIDE_' lil_pass_forward_normal.hlsl | grep -v 'defined\\|#if\\|^$\\|ONLY\\|LIL_OUTLINE'# Find a property in all input files
grep -rn '_Main2ndTex\\b' lil_common_input*.hlsl

# Check which guard macros protect a section
sed -n '/lilGetMain2nd/,/^void lilGetMain3rd/p' lil_common_frag.hlsl | grep '#if\\|#else\\|#endif'

# See all dissolve-related code in functions file
grep -n 'Dissolve\\|dissolve' lil_common_functions.hlsl

# Check if a feature is available in Lite
grep -n 'LIL_LITE\\|MAIN2ND\\|MAIN3RD\\|LAYER_DISSOLVE' lil_common_frag.hlsl

# Count lines of each type of processing
grep -c 'OVERRIDE_' lil_pass_forward_normal.hlsl

# Find LIL_RENDER values for Transparent mode files
grep -n '#define LIL_RENDER' lts_trans.shader lts_twotrans.shader ltspass_transparent.shader

# Check SubPass configuration
grep -n 'LIL_SUBPASS_TRANSPARENT_MODE' lil_common_macro.hlsl

# Check pipeline detection and attachment light mode
grep -n '#elif defined(LIL_HDRP)\\|LIL_ADDITIONAL_LIGHT_MODE' lil_common_macro.hlsl

# Read the HDRP pipeline file (only 30 lines)
cat /root/lilToon/Assets/lilToon/Shader/Includes/lil_pipeline_hdrp.hlsl
ine file (only 30 lines) cat /root/lilToon/Assets/lilToon/Shader/Includes/lil_pipeline_hdrp.hlsl ```## Verification Checklist

  • Found the frag() function in lil_pass_forward_normal.hlsl
  • Identified OVERRIDE macro execution order
  • Checked LIL_RENDER guards — is the feature available in Opaque/Cutout/Transparent?
  • Checked LIL_LITE guards — is the feature available in Lite mode?
  • Checked feature macros (LIL_FEATURE_*) for variant availability
  • Checked what variable (fd.col.a vs color2nd.a vs color3rd.a) is passed to each dissolve call
  • Verified property names in lil_common_input_opt.hlsl match the ones used in AnimationClip bindings