Files
adminandClaude Fable 5 2de153d8fe Add toon shader with outline for pose icon stage
Cel-shaded white body with tinted shadow band plus inverted-hull
clip-space outline (constant screen width, works in deferred via
SRPDefaultUnlit). Key light added to the Pose Icon scene, material
wired to the Paintman.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:59:15 +02:00

142 lines
5.1 KiB
Plaintext

// Minimal cel/toon shader with an inverted-hull outline for the Pose Icon stage
// (icon screenshots of the staged Paintman). Deliberately NOT a URP/Lit clone:
// the stage is a controlled studio setup, so main-light cel banding + ambient is
// all the lighting it needs. Outline runs as a second pass (SRPDefaultUnlit, so
// URP renders it without a Render Feature); width is screen-space (pixel-ish)
// and distance-independent for consistent icon thickness.
Shader "Custom/PoseIconToon"
{
Properties
{
[MainTexture] _BaseMap("Albedo", 2D) = "white" {}
[MainColor] _BaseColor("Color", Color) = (1,1,1,1)
[Header(Toon Shading)]
_ShadeThreshold("Shade Threshold", Range(0.0, 1.0)) = 0.5
_ShadeSoftness("Shade Softness", Range(0.001, 0.5)) = 0.05
_ShadowTint("Shadow Tint", Color) = (0.55, 0.5, 0.65, 1)
[Header(Outline)]
_OutlineColor("Outline Color", Color) = (0.1, 0.08, 0.2, 1)
_OutlineWidth("Outline Width (px at 1080p)", Range(0.0, 20.0)) = 6.0
}
SubShader
{
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" }
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
half4 _BaseColor;
half _ShadeThreshold;
half _ShadeSoftness;
half4 _ShadowTint;
half4 _OutlineColor;
half _OutlineWidth;
CBUFFER_END
ENDHLSL
Pass
{
Name "ToonForward"
// UniversalForwardOnly (not UniversalForward): drawn by BOTH the Forward and the
// Deferred renderer (the deferred path only picks up ForwardOnly/SRPDefaultUnlit
// passes for materials without a GBuffer pass - plain UniversalForward is skipped).
Tags { "LightMode" = "UniversalForwardOnly" }
HLSLPROGRAM
#pragma vertex ToonVertex
#pragma fragment ToonFragment
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normalWS : TEXCOORD1;
};
Varyings ToonVertex(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
return output;
}
half4 ToonFragment(Varyings input) : SV_Target
{
half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
Light mainLight = GetMainLight();
half ndl = dot(normalize(input.normalWS), mainLight.direction) * 0.5h + 0.5h;
half lit = smoothstep(_ShadeThreshold - _ShadeSoftness, _ShadeThreshold + _ShadeSoftness, ndl);
half3 litColor = albedo.rgb * mainLight.color;
half3 shadeColor = albedo.rgb * _ShadowTint.rgb;
half3 ambient = albedo.rgb * SampleSH(input.normalWS) * 0.25h;
return half4(lerp(shadeColor, litColor, lit) + ambient, albedo.a);
}
ENDHLSL
}
Pass
{
Name "Outline"
Tags { "LightMode" = "SRPDefaultUnlit" }
Cull Front
HLSLPROGRAM
#pragma vertex OutlineVertex
#pragma fragment OutlineFragment
struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
};
Varyings OutlineVertex(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
// Extrude in clip space: constant on-screen width regardless of distance.
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
float3 normalCS = normalize(mul((float3x3)UNITY_MATRIX_VP, normalWS).xyz);
float2 offset = normalCS.xy * _OutlineWidth * 2.0 / 1080.0 * output.positionCS.w;
offset.x *= _ScreenParams.y / _ScreenParams.x; // keep width uniform on non-square aspect
output.positionCS.xy += offset;
return output;
}
half4 OutlineFragment(Varyings input) : SV_Target
{
return _OutlineColor;
}
ENDHLSL
}
}
}