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>
142 lines
5.1 KiB
Plaintext
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
|
|
}
|
|
}
|
|
}
|