455 lines
23 KiB
GLSL
455 lines
23 KiB
GLSL
Shader "Custom/ToonBilal"
|
|
{
|
|
Properties
|
|
{
|
|
[Header(Global)]
|
|
[Toggle] _ToonEnabled ("Enable Toon Filter", Float) = 1
|
|
_ShaderWeight ("Shader Weight", Range(0,1)) = 1.0
|
|
|
|
[Header(Toon Shading)]
|
|
_ToonSteps ("Toon Steps", Float) = 4
|
|
_ShadowStrength ("Shadow Strength", Range(0,1)) = 0.15
|
|
_HighlightBoost ("Highlight Boost", Range(1,3)) = 1.6
|
|
_HighlightThreshold ("Highlight Threshold", Range(0,1)) = 0.65
|
|
|
|
[Header(Pencil Outlines)]
|
|
_OutlineThickness ("Outline Thickness", Float) = 2.0
|
|
_OutlineThicknessVar ("Outline Thickness Variation", Range(0,1)) = 0.4
|
|
_DepthThreshold ("Depth Outline Threshold", Range(0,2)) = 0.3
|
|
_ColorThreshold ("Color Outline Threshold", Range(0,1)) = 0.2
|
|
_DepthOutlineStrength ("Depth Outline Strength", Range(0,1)) = 1.0
|
|
_ColorOutlineStrength ("Color Outline Strength", Range(0,1)) = 0.5
|
|
_OutlineSoftness ("Outline Softness", Range(0,1)) = 0.6
|
|
_OutlineColorBlend ("Outline Color From Surface", Range(0,1)) = 0.5
|
|
_OutlineDarken ("Outline Darken Amount", Range(0,1)) = 0.7
|
|
_OutlineDistanceFade ("Outline Distance Fade", Range(0,100)) = 30.0
|
|
|
|
[Header(Outline Brightness Fade)]
|
|
_OutlineFadeStart ("Outline Fade Start", Range(0,1)) = 0.4
|
|
_OutlineFadeEnd ("Outline Fade End", Range(0,1)) = 0.9
|
|
_OutlineFadeStrength ("Outline Fade Strength", Range(0,1)) = 1.0
|
|
_OutlineBrightMin ("Outline Bright Min Visibility", Range(0,1)) = 0.2
|
|
|
|
[Header(Hatching)]
|
|
_HatchTex ("Hatching Texture", 2D) = "white" {}
|
|
_HatchScale ("Hatch Scale", Float) = 0.3
|
|
_HatchStrength ("Hatch Strength", Range(0,1)) = 0.95
|
|
_HatchThreshold ("Hatch Shadow Threshold", Range(0,1)) = 0.75
|
|
_CurvatureStrength ("Curvature Strength", Range(0,5)) = 2.0
|
|
_FlatSurface ("Flat Surface Reduction", Range(0,1)) = 0.3
|
|
|
|
[Header(Organic Hatching)]
|
|
_HatchScale2 ("Hatch Scale Layer 2", Float) = 0.17
|
|
_HatchScale3 ("Hatch Scale Layer 3", Float) = 0.22
|
|
_HatchRotation ("Hatch Rotation", Range(0,3.14159)) = 0.785
|
|
_HatchLayerBlend ("Hatch Layer Blend", Range(0,1)) = 0.8
|
|
_HatchOrganicNoise ("Hatch Organic Noise", Range(0,1)) = 0.5
|
|
_HatchOrganicScale ("Hatch Organic Noise Scale", Float) = 2.0
|
|
_HatchDensityCurve ("Hatch Density Curve", Range(0.1,5)) = 1.0
|
|
_TriplanarSharpness ("Triplanar Sharpness", Range(1,8)) = 2.0
|
|
|
|
[Header(Pencil Hatching)]
|
|
_HatchColorBlend ("Hatch Color From Surface", Range(0,1)) = 0.5
|
|
_HatchDarken ("Hatch Darken Amount", Range(0,1)) = 0.85
|
|
_HatchGrainScale ("Hatch Grain Scale", Float) = 120.0
|
|
_HatchGrainStrength ("Hatch Grain Strength", Range(0,1)) = 0.25
|
|
_HatchPressure ("Hatch Pressure Variation", Range(0,1)) = 0.5
|
|
|
|
[Header(Gouache Shadow)]
|
|
_BrushTex ("Brush Texture", 2D) = "white" {}
|
|
_GouacheScale ("Gouache Scale", Float) = 0.8
|
|
_GouacheStrength ("Gouache Strength", Range(0,1)) = 0.7
|
|
_GouacheThreshold ("Gouache Shadow Threshold", Range(0,1)) = 0.35
|
|
_GouacheSaturation ("Gouache Saturation Boost", Range(0,2)) = 1.3
|
|
_GouachePosterize ("Gouache Posterize Steps", Range(1,6)) = 3.0
|
|
_GouacheBlend ("Gouache Blend With Hatch", Range(0,1)) = 0.6
|
|
}
|
|
|
|
SubShader
|
|
{
|
|
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
|
|
ZWrite Off ZTest Always Cull Off
|
|
|
|
Pass
|
|
{
|
|
Name "ToonBilalPass"
|
|
|
|
HLSLPROGRAM
|
|
#pragma vertex Vert
|
|
#pragma fragment Frag
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
|
|
|
float _ToonEnabled;
|
|
float _ShaderWeight;
|
|
float _ToonSteps;
|
|
float _ShadowStrength;
|
|
float _HighlightBoost;
|
|
float _HighlightThreshold;
|
|
float _OutlineThickness;
|
|
float _OutlineThicknessVar;
|
|
float _DepthThreshold;
|
|
float _ColorThreshold;
|
|
float _DepthOutlineStrength;
|
|
float _ColorOutlineStrength;
|
|
float _OutlineSoftness;
|
|
float _OutlineColorBlend;
|
|
float _OutlineDarken;
|
|
float _OutlineDistanceFade;
|
|
float _OutlineFadeStart;
|
|
float _OutlineFadeEnd;
|
|
float _OutlineFadeStrength;
|
|
float _OutlineBrightMin;
|
|
float _HatchScale;
|
|
float _HatchScale2;
|
|
float _HatchScale3;
|
|
float _HatchStrength;
|
|
float _HatchThreshold;
|
|
float _CurvatureStrength;
|
|
float _FlatSurface;
|
|
float _HatchRotation;
|
|
float _HatchLayerBlend;
|
|
float _HatchOrganicNoise;
|
|
float _HatchOrganicScale;
|
|
float _HatchDensityCurve;
|
|
float _TriplanarSharpness;
|
|
float _HatchColorBlend;
|
|
float _HatchDarken;
|
|
float _HatchGrainScale;
|
|
float _HatchGrainStrength;
|
|
float _HatchPressure;
|
|
float _GouacheScale;
|
|
float _GouacheStrength;
|
|
float _GouacheThreshold;
|
|
float _GouacheSaturation;
|
|
float _GouachePosterize;
|
|
float _GouacheBlend;
|
|
|
|
TEXTURE2D(_HatchTex);
|
|
SAMPLER(sampler_HatchTex);
|
|
TEXTURE2D(_BrushTex);
|
|
SAMPLER(sampler_BrushTex);
|
|
|
|
// ─── NOISE ───────────────────────────────────────────────
|
|
float Hash21(float2 p)
|
|
{
|
|
p = frac(p * float2(234.34, 435.345));
|
|
p += dot(p, p + 34.23);
|
|
return frac(p.x * p.y);
|
|
}
|
|
|
|
float SmoothNoise(float2 p)
|
|
{
|
|
float2 i = floor(p);
|
|
float2 f = frac(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
return lerp(
|
|
lerp(Hash21(i), Hash21(i + float2(1,0)), f.x),
|
|
lerp(Hash21(i + float2(0,1)), Hash21(i + float2(1,1)), f.x),
|
|
f.y
|
|
);
|
|
}
|
|
|
|
float FBM(float2 p)
|
|
{
|
|
float v = 0.0;
|
|
float a = 0.5;
|
|
float2 shift = float2(100.0, 100.0);
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
v += a * SmoothNoise(p);
|
|
p = p * 2.0 + shift;
|
|
a *= 0.5;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
// ─── RECONSTRUCTION ──────────────────────────────────────
|
|
float3 ReconstructWorldPos(float2 uv, float depth)
|
|
{
|
|
float4 clipPos = float4(uv * 2.0 - 1.0, depth, 1.0);
|
|
#if UNITY_UV_STARTS_AT_TOP
|
|
clipPos.y = -clipPos.y;
|
|
#endif
|
|
float4 worldPos = mul(UNITY_MATRIX_I_VP, clipPos);
|
|
return worldPos.xyz / worldPos.w;
|
|
}
|
|
|
|
float3 ReconstructNormal(float2 uv, float2 texel)
|
|
{
|
|
float d0 = SampleSceneDepth(uv);
|
|
float dr = SampleSceneDepth(uv + float2(texel.x, 0));
|
|
float du = SampleSceneDepth(uv + float2(0, texel.y));
|
|
float3 p0 = ReconstructWorldPos(uv, d0);
|
|
float3 pr = ReconstructWorldPos(uv + float2(texel.x, 0), dr);
|
|
float3 pu = ReconstructWorldPos(uv + float2(0, texel.y), du);
|
|
return normalize(cross(pr - p0, pu - p0));
|
|
}
|
|
|
|
float ComputeCurvature(float2 uv, float2 texel)
|
|
{
|
|
float3 n0 = ReconstructNormal(uv, texel);
|
|
float3 nr = ReconstructNormal(uv + float2(texel.x * 2.0, 0), texel);
|
|
float3 nu = ReconstructNormal(uv + float2(0, texel.y * 2.0), texel);
|
|
float3 nl = ReconstructNormal(uv - float2(texel.x * 2.0, 0), texel);
|
|
float3 nd = ReconstructNormal(uv - float2(0, texel.y * 2.0), texel);
|
|
return saturate((length(nr-n0)+length(nu-n0)+length(nl-n0)+length(nd-n0)) * _CurvatureStrength);
|
|
}
|
|
|
|
// Linearizes the depth buffer — returns consistent meter values across the scene
|
|
float LinearizeDepth(float rawDepth)
|
|
{
|
|
return LinearEyeDepth(rawDepth, _ZBufferParams);
|
|
}
|
|
|
|
float2 RotateUV(float2 uv, float angle)
|
|
{
|
|
float s = sin(angle);
|
|
float c = cos(angle);
|
|
return float2(uv.x * c - uv.y * s, uv.x * s + uv.y * c);
|
|
}
|
|
|
|
// ─── SATURATION ─────────────────────────────────────────
|
|
half3 AdjustSaturation(half3 col, float saturation)
|
|
{
|
|
float lum = dot(col, float3(0.299, 0.587, 0.114));
|
|
return lerp(half3(lum, lum, lum), col, saturation);
|
|
}
|
|
|
|
// ─── PENCIL COLOR ────────────────────────────────────────
|
|
half3 PencilColor(half3 surfaceCol, float darken, float colorBlend)
|
|
{
|
|
half3 darkSurface = surfaceCol * (1.0 - darken);
|
|
return lerp(half3(0,0,0), darkSurface, colorBlend);
|
|
}
|
|
|
|
// ─── GOUACHE SHADOW ──────────────────────────────────────
|
|
half3 ApplyGouache(half3 col, float3 worldPos, float lum, float curvature)
|
|
{
|
|
float shadowMask = 1.0 - smoothstep(0.0, _GouacheThreshold, lum);
|
|
if (shadowMask <= 0.0) return col;
|
|
|
|
float2 uvBrush1 = worldPos.xz * _GouacheScale;
|
|
float2 uvBrush2 = worldPos.xy * _GouacheScale * 0.7 + float2(3.1, 7.4);
|
|
float2 uvBrush3 = worldPos.yz * _GouacheScale * 1.3 + float2(1.7, 2.9);
|
|
|
|
float b1 = SAMPLE_TEXTURE2D(_BrushTex, sampler_BrushTex, uvBrush1).r;
|
|
float b2 = SAMPLE_TEXTURE2D(_BrushTex, sampler_BrushTex, uvBrush2).r;
|
|
float b3 = SAMPLE_TEXTURE2D(_BrushTex, sampler_BrushTex, uvBrush3).r;
|
|
float brush = (b1 * 0.5 + b2 * 0.3 + b3 * 0.2);
|
|
|
|
float3 gouacheCol = col;
|
|
gouacheCol.r = floor(gouacheCol.r * _GouachePosterize) / _GouachePosterize;
|
|
gouacheCol.g = floor(gouacheCol.g * _GouachePosterize) / _GouachePosterize;
|
|
gouacheCol.b = floor(gouacheCol.b * _GouachePosterize) / _GouachePosterize;
|
|
|
|
gouacheCol = AdjustSaturation(gouacheCol, _GouacheSaturation);
|
|
|
|
float brushMask = 1.0 - brush * 0.5;
|
|
|
|
return lerp(col, gouacheCol * brushMask, shadowMask * _GouacheStrength);
|
|
}
|
|
|
|
// ─── TRIPLANAR HATCHING ───────────────────────────────────
|
|
// Normal-oriented UVs: rotates hatch UVs based on surface normal direction
|
|
// so each face gets its own stroke orientation — anchored to world geometry
|
|
float2 NormalOrientedUV(float3 worldPos, float3 normal, float3 tangent, float3 bitangent, float scale)
|
|
{
|
|
return float2(dot(worldPos, tangent), dot(worldPos, bitangent)) * scale;
|
|
}
|
|
|
|
float OrganicHatch(float3 worldPos, float3 worldNormal, float shadowAmount, float curvature, float2 screenUV, float toonEdge)
|
|
{
|
|
float3 weights = pow(abs(worldNormal), _TriplanarSharpness);
|
|
weights /= (weights.x + weights.y + weights.z + 0.0001);
|
|
|
|
// Organic deformation — world-space only
|
|
float2 uvY = worldPos.xz * _HatchScale;
|
|
float2 off = float2(
|
|
FBM(uvY * _HatchOrganicScale),
|
|
FBM(uvY * _HatchOrganicScale + float2(5.2, 1.3))
|
|
) * _HatchOrganicNoise;
|
|
|
|
// World-space grain and pressure — stable under camera movement
|
|
float grain = SmoothNoise(worldPos.xz * _HatchGrainScale * 0.01);
|
|
float pressure = SmoothNoise(worldPos.xy * _HatchGrainScale * 0.003 + float2(3.7, 8.1));
|
|
float pressureVar = lerp(1.0, pressure * 1.5, _HatchPressure);
|
|
|
|
// ── Layer 1: normal-oriented per face
|
|
// Build per-face tangent frames so strokes follow surface orientation
|
|
float3 upX = abs(worldNormal.x) > 0.9 ? float3(0,1,0) : float3(1,0,0);
|
|
float3 tX = normalize(cross(upX, float3(1,0,0)));
|
|
float3 bX = normalize(cross(float3(1,0,0), tX));
|
|
|
|
float3 upY = float3(1,0,0);
|
|
float3 tY = normalize(cross(upY, float3(0,1,0)));
|
|
float3 bY = normalize(cross(float3(0,1,0), tY));
|
|
|
|
float3 upZ = abs(worldNormal.z) > 0.9 ? float3(0,1,0) : float3(0,0,1);
|
|
float3 tZ = normalize(cross(upZ, float3(0,0,1)));
|
|
float3 bZ = normalize(cross(float3(0,0,1), tZ));
|
|
|
|
float2 uvX1 = NormalOrientedUV(worldPos, worldNormal, tX, bX, _HatchScale) + off;
|
|
float2 uvY1 = NormalOrientedUV(worldPos, worldNormal, tY, bY, _HatchScale) + off;
|
|
float2 uvZ1 = NormalOrientedUV(worldPos, worldNormal, tZ, bZ, _HatchScale) + off;
|
|
|
|
float h1X = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvX1).r;
|
|
float h1Y = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvY1).r;
|
|
float h1Z = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvZ1).r;
|
|
float h1 = h1X * weights.x + h1Y * weights.y + h1Z * weights.z;
|
|
|
|
// ── Layer 2: rotated for cross-hatching
|
|
float2 uvX2 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tX, bX, _HatchScale2), _HatchRotation) + off * 0.5;
|
|
float2 uvY2 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tY, bY, _HatchScale2), _HatchRotation) + off * 0.5;
|
|
float2 uvZ2 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tZ, bZ, _HatchScale2), _HatchRotation) + off * 0.5;
|
|
|
|
float h2X = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvX2).r;
|
|
float h2Y = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvY2).r;
|
|
float h2Z = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvZ2).r;
|
|
float h2 = h2X * weights.x + h2Y * weights.y + h2Z * weights.z;
|
|
|
|
// ── Layer 3: third angle for very dark/curved zones
|
|
float angle3 = _HatchRotation * 1.7;
|
|
float2 uvX3 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tX, bX, _HatchScale3), angle3) + off * 0.3;
|
|
float2 uvY3 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tY, bY, _HatchScale3), angle3) + off * 0.3;
|
|
float2 uvZ3 = RotateUV(NormalOrientedUV(worldPos, worldNormal, tZ, bZ, _HatchScale3), angle3) + off * 0.3;
|
|
|
|
float h3X = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvX3).r;
|
|
float h3Y = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvY3).r;
|
|
float h3Z = SAMPLE_TEXTURE2D(_HatchTex, sampler_HatchTex, uvZ3).r;
|
|
float h3 = h3X * weights.x + h3Y * weights.y + h3Z * weights.z;
|
|
|
|
// Shadow-driven layer blending — more layers in darker/curved zones
|
|
float darkness = 1.0 - shadowAmount;
|
|
float blend12 = lerp(_HatchLayerBlend, 1.0, saturate(darkness * 1.5 + curvature));
|
|
float blend3 = saturate((darkness - 0.4) * 2.0 + curvature * 0.8 + toonEdge);
|
|
|
|
float hatch = lerp(h1, min(h1, h2), blend12);
|
|
hatch = lerp(hatch, min(hatch, h3), blend3);
|
|
hatch = saturate(hatch + grain * _HatchGrainStrength);
|
|
|
|
// Shadow mask — wider in dark zones, boosted at toon edges and curves
|
|
float shadowMask = pow(smoothstep(_HatchThreshold, 0.0, shadowAmount), _HatchDensityCurve);
|
|
float edgeMask = saturate(toonEdge * 2.0 + curvature);
|
|
shadowMask = saturate(shadowMask + edgeMask * 0.6);
|
|
|
|
float curvatureMask = lerp(_FlatSurface, 1.0, saturate(curvature * 2.0));
|
|
float curvatureBoost = 1.0 + curvature * 1.2;
|
|
|
|
// Traits noirs sur fond blanc: hatch value IS the stroke
|
|
return shadowMask * curvatureMask * _HatchStrength * pressureVar * hatch * curvatureBoost;
|
|
}
|
|
|
|
// ─── FRAGMENT ────────────────────────────────────────────
|
|
half4 Frag(Varyings input) : SV_Target
|
|
{
|
|
float2 uv = input.texcoord;
|
|
half3 col = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv).rgb;
|
|
|
|
float2 texelBase = float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y);
|
|
float lum = dot(col, float3(0.299, 0.587, 0.114));
|
|
|
|
// ── World space — computed early, used throughout
|
|
float rawDepth = SampleSceneDepth(uv);
|
|
float linDepth = LinearizeDepth(rawDepth);
|
|
float3 worldPos = ReconstructWorldPos(uv, rawDepth);
|
|
float3 worldNormal = SampleSceneNormals(uv);
|
|
float curvature = ComputeCurvature(uv, texelBase);
|
|
|
|
// ── Outlines with variable thickness, fading with distance
|
|
float distanceFade = 1.0 - saturate(linDepth / _OutlineDistanceFade);
|
|
float noiseThick = SmoothNoise(worldPos.xz * 2.0);
|
|
float thicknessVar = lerp(1.0, noiseThick * 2.0, _OutlineThicknessVar) * distanceFade;
|
|
float2 texelThin = texelBase * _OutlineThickness * 0.5 * thicknessVar;
|
|
float2 texelThick = texelBase * _OutlineThickness * 1.5 * thicknessVar;
|
|
|
|
half3 cn = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv + float2(0, texelThin.y)).rgb;
|
|
half3 cs = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv - float2(0, texelThin.y)).rgb;
|
|
half3 ce = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv + float2(texelThin.x, 0)).rgb;
|
|
half3 cw = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv - float2(texelThin.x, 0)).rgb;
|
|
|
|
float lumN = dot(cn, float3(0.299, 0.587, 0.114));
|
|
float lumS = dot(cs, float3(0.299, 0.587, 0.114));
|
|
float lumE = dot(ce, float3(0.299, 0.587, 0.114));
|
|
float lumW = dot(cw, float3(0.299, 0.587, 0.114));
|
|
float lumMax = max(max(lumN, lumS), max(lumE, lumW));
|
|
float brightnessForFade = max(lum, lumMax);
|
|
|
|
half3 cn_n = normalize(cn + 0.001);
|
|
half3 cs_n = normalize(cs + 0.001);
|
|
half3 ce_n = normalize(ce + 0.001);
|
|
half3 cw_n = normalize(cw + 0.001);
|
|
float colorEdgeInner = length(cn_n - cs_n) + length(ce_n - cw_n);
|
|
|
|
half3 cnO = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv + float2(0, texelThick.y)).rgb;
|
|
half3 csO = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv - float2(0, texelThick.y)).rgb;
|
|
half3 ceO = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv + float2(texelThick.x, 0)).rgb;
|
|
half3 cwO = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv - float2(texelThick.x, 0)).rgb;
|
|
float colorEdgeOuter = length(normalize(cnO+0.001) - normalize(csO+0.001)) + length(normalize(ceO+0.001) - normalize(cwO+0.001));
|
|
|
|
float colorEdge = lerp(colorEdgeInner, colorEdgeOuter * 0.5, _OutlineSoftness);
|
|
colorEdge = step(_ColorThreshold, colorEdge) * _ColorOutlineStrength;
|
|
|
|
// Linearized depth outlines — consistent threshold in meters, not raw buffer
|
|
float2 texelOutline = texelBase * _OutlineThickness * thicknessVar;
|
|
float dn = LinearizeDepth(SampleSceneDepth(uv + float2(0, texelOutline.y)));
|
|
float ds = LinearizeDepth(SampleSceneDepth(uv - float2(0, texelOutline.y)));
|
|
float de = LinearizeDepth(SampleSceneDepth(uv + float2(texelOutline.x, 0)));
|
|
float dw = LinearizeDepth(SampleSceneDepth(uv - float2(texelOutline.x, 0)));
|
|
float depthRaw = abs(dn-ds) + abs(de-dw);
|
|
float depthEdge = smoothstep(_DepthThreshold * 0.5, _DepthThreshold * 2.0, depthRaw) * _DepthOutlineStrength;
|
|
depthEdge *= 1.0 - smoothstep(0.5, 0.85, brightnessForFade);
|
|
depthEdge *= distanceFade;
|
|
|
|
float edge = saturate(depthEdge + colorEdge);
|
|
|
|
// ── Toon shading with highlight boost (Bilal: near-white highlights)
|
|
float toon = floor(lum * _ToonSteps) / _ToonSteps;
|
|
toon = lerp(_ShadowStrength, 1.0, toon);
|
|
half3 toonCol = col * toon;
|
|
|
|
// Blow out highlights — bright zones go nearly white like Bilal
|
|
float highlightMask = smoothstep(_HighlightThreshold, 1.0, lum);
|
|
toonCol = lerp(toonCol, toonCol * _HighlightBoost, highlightMask);
|
|
toonCol = min(toonCol, 1.2); // allow slight overexposure
|
|
|
|
// ── Gouache in dark areas
|
|
toonCol = ApplyGouache(toonCol, worldPos, lum, curvature);
|
|
|
|
// ── Hatching — fully world-space, stable under camera movement
|
|
float3 lightDir = normalize(float3(1, -1, 1));
|
|
float toonEdge = saturate(fwidth(floor(lum * _ToonSteps)) * 10.0);
|
|
float hatchBoost = saturate(toonEdge + curvature * 0.5);
|
|
float shadowAmount = saturate(dot(worldNormal, -lightDir) + hatchBoost * 0.4);
|
|
float hatchBlend = OrganicHatch(worldPos, worldNormal, shadowAmount, curvature, uv, toonEdge);
|
|
|
|
float gouacheMask = 1.0 - smoothstep(0.0, _GouacheThreshold, lum);
|
|
hatchBlend *= lerp(1.0, 1.0 - _GouacheBlend, gouacheMask);
|
|
|
|
// Hatch fades in bright zones — highlights stay clean
|
|
hatchBlend *= 1.0 - highlightMask * 0.8;
|
|
|
|
half3 hatchCol = PencilColor(col, _HatchDarken, _HatchColorBlend);
|
|
toonCol = lerp(toonCol, hatchCol, hatchBlend);
|
|
|
|
// ── Outline fade on bright surfaces
|
|
float outlineFade = lerp(_OutlineBrightMin, 1.0, 1.0 - smoothstep(_OutlineFadeStart, _OutlineFadeEnd, brightnessForFade));
|
|
outlineFade = lerp(1.0, outlineFade, _OutlineFadeStrength);
|
|
|
|
half3 outlineCol = PencilColor(col, _OutlineDarken, _OutlineColorBlend);
|
|
half3 finalCol = lerp(toonCol, outlineCol, edge * outlineFade);
|
|
|
|
finalCol = lerp(col, finalCol, _ShaderWeight * _ToonEnabled);
|
|
|
|
return half4(finalCol, 1);
|
|
}
|
|
ENDHLSL
|
|
}
|
|
}
|
|
|
|
CustomEditor "ToonBilalEditor"
|
|
}
|