Text Outline
From Unify Community Wiki
(Difference between revisions)
(correct interior outline color computing + more crisp outlines) |
(UVs extrapolation out of local uvs bounds elimination) |
||
Line 23: | Line 23: | ||
/*[PerRendererData]*/[MaterialToggle] _Exterior ( " Exterior", Float ) = 1.0 | /*[PerRendererData]*/[MaterialToggle] _Exterior ( " Exterior", Float ) = 1.0 | ||
/*[PerRendererData]*/[MaterialToggle] _Interior ( " Interior", Float ) = 0.0 | /*[PerRendererData]*/[MaterialToggle] _Interior ( " Interior", Float ) = 0.0 | ||
+ | |||
+ | [HideInInspector]_StencilComp ( "Stencil Comparison", Float ) = 8 | ||
+ | [HideInInspector]_Stencil ( "Stencil ID", Float ) = 0 | ||
+ | [HideInInspector]_StencilOp ( "Stencil Operation", Float ) = 0 | ||
+ | [HideInInspector]_StencilWriteMask( "Stencil Write Mask", Float ) = 255 | ||
+ | [HideInInspector]_StencilReadMask ( "Stencil Read Mask", Float ) = 255 | ||
+ | [HideInInspector]_ColorMask ( "Color Mask", Float ) = 15 | ||
} | } | ||
SubShader | SubShader | ||
{ | { | ||
+ | //**************************************************************************************************** | ||
+ | // | ||
+ | //**************************************************************************************************** | ||
+ | |||
Tags | Tags | ||
{ | { | ||
− | "Queue" | + | "Queue" = "Transparent" |
− | "RenderType" = " | + | "IgnoreProjector" = "True" |
+ | "RenderType" = "Transparent" | ||
+ | "PreviewType" = "Plane" | ||
+ | "CanUseSpriteAtlas" = "True" | ||
} | } | ||
− | + | //**************************************************************************************************** | |
+ | // | ||
+ | //**************************************************************************************************** | ||
+ | Stencil | ||
+ | { | ||
+ | Ref [_Stencil] | ||
+ | Comp [_StencilComp] | ||
+ | Pass [_StencilOp] | ||
+ | ReadMask [_StencilReadMask] | ||
+ | WriteMask [_StencilWriteMask] | ||
+ | } | ||
+ | |||
+ | //**************************************************************************************************** | ||
+ | // | ||
+ | //**************************************************************************************************** | ||
+ | |||
+ | Cull Off | ||
+ | Lighting Off | ||
+ | ZWrite Off | ||
+ | ZTest [unity_GUIZTestMode] | ||
+ | ColorMask [_ColorMask] | ||
Blend SrcAlpha OneMinusSrcAlpha | Blend SrcAlpha OneMinusSrcAlpha | ||
+ | |||
+ | //**************************************************************************************************** | ||
+ | // | ||
+ | //**************************************************************************************************** | ||
Pass | Pass | ||
Line 56: | Line 94: | ||
struct appdata | struct appdata | ||
{ | { | ||
− | float4 vertex : POSITION; | + | float4 vertex : POSITION; |
− | float2 uv | + | float2 uv : TEXCOORD0; |
− | float4 color | + | float4 color : COLOR; |
}; | }; | ||
struct v2f | struct v2f | ||
{ | { | ||
− | float4 vertex : SV_POSITION; | + | float4 vertex : SV_POSITION; |
− | float2 uv | + | float2 uv : TEXCOORD0; |
− | float4 color | + | float4 color : COLOR; |
+ | float2 uv_local : TEXCOORD1; | ||
}; | }; | ||
− | static const fixed alpha_threshold = 0.25f; | + | static const fixed alpha_threshold = 0.25f; |
+ | |||
+ | static const fixed2 quads_uvs[ 4 ] = { fixed2( 0.0f, 1.0f ), fixed2( 1.0f, 1.0f ), fixed2( 1.0f, 0.0f ), fixed2( 0.0f, 0.0f ), }; | ||
//******************************************************************************************** | //******************************************************************************************** | ||
Line 86: | Line 127: | ||
//******************************************************************************************** | //******************************************************************************************** | ||
− | fixed | + | fixed GetOutlineAlpha( v2f i, fixed a, bool exterior ) |
{ | { | ||
bool valid_context = exterior ? ( a < alpha_threshold ) : ( a > alpha_threshold ); | bool valid_context = exterior ? ( a < alpha_threshold ) : ( a > alpha_threshold ); | ||
if ( valid_context ) | if ( valid_context ) | ||
{ | { | ||
− | fixed texel_w = _MainTex_TexelSize.x; | + | fixed texel_w = _MainTex_TexelSize.x; |
− | fixed texel_h = _MainTex_TexelSize.y; | + | fixed texel_h = _MainTex_TexelSize.y; |
− | + | fixed2 uv_local_min = fixed2( texel_w, texel_h ); | |
− | fixed | + | fixed2 uv_local_max = fixed2( 1.0f - ( texel_w * 5.0f ), 1.0f - ( texel_h * 10.0f ) ); |
+ | fixed min_dst = 1.0e38f; | ||
+ | |||
fixed2 uv; | fixed2 uv; | ||
+ | fixed2 uv_local; | ||
+ | fixed2 uv_offset; | ||
fixed4 sampling; | fixed4 sampling; | ||
+ | fixed dst; | ||
for( int x = -_Thickness; x <= _Thickness; ++x ) | for( int x = -_Thickness; x <= _Thickness; ++x ) | ||
{ | { | ||
+ | uv_offset.x = x * texel_w; | ||
+ | uv_local.x = i.uv_local.x + uv_offset.x; | ||
+ | if( ( uv_local.x < uv_local_min.x ) || ( uv_local.x > uv_local_max.x ) ) continue; | ||
+ | |||
for( int y = -_Thickness; y <= _Thickness; ++y ) | for( int y = -_Thickness; y <= _Thickness; ++y ) | ||
{ | { | ||
− | uv.x = i.uv.x + | + | uv_offset.y = y * texel_h; |
− | uv.y = i.uv.y + | + | uv_local.y = i.uv_local.y + uv_offset.y; |
+ | if( ( uv_local.y < uv_local_min.y ) || ( uv_local.y > uv_local_max.y ) ) continue; | ||
+ | |||
+ | uv.x = i.uv.x + uv_offset.x; | ||
+ | uv.y = i.uv.y + uv_offset.y; | ||
sampling = tex2Dlod( _MainTex, fixed4( uv.x, uv.y, 0, 0 ) ); | sampling = tex2Dlod( _MainTex, fixed4( uv.x, uv.y, 0, 0 ) ); | ||
− | |||
bool outline = exterior ? ( sampling.a >= alpha_threshold ) : ( sampling.a <= alpha_threshold ); | bool outline = exterior ? ( sampling.a >= alpha_threshold ) : ( sampling.a <= alpha_threshold ); | ||
Line 130: | Line 183: | ||
//******************************************************************************************** | //******************************************************************************************** | ||
− | v2f vert( appdata v ) | + | v2f vert( appdata v, uint id : SV_VertexID ) |
{ | { | ||
v2f o; | v2f o; | ||
− | o.vertex = UnityObjectToClipPos( v.vertex ); | + | o.vertex = UnityObjectToClipPos( v.vertex ); |
− | o.uv | + | o.uv = TRANSFORM_TEX( v.uv, _MainTex ); |
− | o.color | + | o.color = v.color; |
+ | o.uv_local = quads_uvs[ id & 3 ]; | ||
+ | |||
return o; | return o; | ||
} | } | ||
Line 152: | Line 207: | ||
if( _Exterior > 0.0f ) | if( _Exterior > 0.0f ) | ||
{ | { | ||
− | fixed outline_alpha = | + | fixed outline_alpha = GetOutlineAlpha( i, output.a, true ); |
if( outline_alpha > 0.0f ) return fixed4( _OutlineColor.r, _OutlineColor.g, _OutlineColor.b, _OutlineColor.a * outline_alpha * i.color.a ); | if( outline_alpha > 0.0f ) return fixed4( _OutlineColor.r, _OutlineColor.g, _OutlineColor.b, _OutlineColor.a * outline_alpha * i.color.a ); | ||
} | } | ||
Line 158: | Line 213: | ||
if( _Interior > 0.0f ) | if( _Interior > 0.0f ) | ||
{ | { | ||
− | fixed outline_alpha = | + | fixed outline_alpha = GetOutlineAlpha( i, output.a, false ); |
if( outline_alpha > 0.0f ) | if( outline_alpha > 0.0f ) | ||
{ | { | ||
Line 173: | Line 228: | ||
} | } | ||
} | } | ||
+ | |||
+ | Fallback "UI/Default" | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 10:25, 31 October 2020
Author: Serge Billault
Contents |
Description
Text outlining with independantly selectable interior and exterior. The pixel being rendered sample its surrounding according to outline _Thickness (in pixels) and deduce the outline alpha from the distance with the nearest outline inducing texture texel.
Preview
Code ( UI_TextOutline.shader )
Shader "Unlit/UITextOutline" { Properties { /* */ _MainTex ( " Texture", 2D ) = "white" {} /*[PerRendererData]*/ _OutlineColor( " Outline Color", Color ) = ( 0.0, 0.0, 0.0, 1.0 ) /*[PerRendererData]*/ _Thickness ( " Thickness", Range( 1.0, 16.0 ) ) = 2.1 /*[PerRendererData]*/[MaterialToggle] _Exterior ( " Exterior", Float ) = 1.0 /*[PerRendererData]*/[MaterialToggle] _Interior ( " Interior", Float ) = 0.0 [HideInInspector]_StencilComp ( "Stencil Comparison", Float ) = 8 [HideInInspector]_Stencil ( "Stencil ID", Float ) = 0 [HideInInspector]_StencilOp ( "Stencil Operation", Float ) = 0 [HideInInspector]_StencilWriteMask( "Stencil Write Mask", Float ) = 255 [HideInInspector]_StencilReadMask ( "Stencil Read Mask", Float ) = 255 [HideInInspector]_ColorMask ( "Color Mask", Float ) = 15 } SubShader { //**************************************************************************************************** // //**************************************************************************************************** Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } //**************************************************************************************************** // //**************************************************************************************************** Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } //**************************************************************************************************** // //**************************************************************************************************** Cull Off Lighting Off ZWrite Off ZTest [unity_GUIZTestMode] ColorMask [_ColorMask] Blend SrcAlpha OneMinusSrcAlpha //**************************************************************************************************** // //**************************************************************************************************** Pass { CGPROGRAM //******************************************************************************************** // //******************************************************************************************** #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" //******************************************************************************************** // //******************************************************************************************** struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float4 color : COLOR; }; struct v2f { float4 vertex : SV_POSITION; float2 uv : TEXCOORD0; float4 color : COLOR; float2 uv_local : TEXCOORD1; }; static const fixed alpha_threshold = 0.25f; static const fixed2 quads_uvs[ 4 ] = { fixed2( 0.0f, 1.0f ), fixed2( 1.0f, 1.0f ), fixed2( 1.0f, 0.0f ), fixed2( 0.0f, 0.0f ), }; //******************************************************************************************** // //******************************************************************************************** sampler2D _MainTex; fixed4 _MainTex_TexelSize; fixed4 _MainTex_ST; fixed4 _OutlineColor; fixed _Thickness; fixed _Exterior; fixed _Interior; //******************************************************************************************** // //******************************************************************************************** fixed GetOutlineAlpha( v2f i, fixed a, bool exterior ) { bool valid_context = exterior ? ( a < alpha_threshold ) : ( a > alpha_threshold ); if ( valid_context ) { fixed texel_w = _MainTex_TexelSize.x; fixed texel_h = _MainTex_TexelSize.y; fixed2 uv_local_min = fixed2( texel_w, texel_h ); fixed2 uv_local_max = fixed2( 1.0f - ( texel_w * 5.0f ), 1.0f - ( texel_h * 10.0f ) ); fixed min_dst = 1.0e38f; fixed2 uv; fixed2 uv_local; fixed2 uv_offset; fixed4 sampling; fixed dst; for( int x = -_Thickness; x <= _Thickness; ++x ) { uv_offset.x = x * texel_w; uv_local.x = i.uv_local.x + uv_offset.x; if( ( uv_local.x < uv_local_min.x ) || ( uv_local.x > uv_local_max.x ) ) continue; for( int y = -_Thickness; y <= _Thickness; ++y ) { uv_offset.y = y * texel_h; uv_local.y = i.uv_local.y + uv_offset.y; if( ( uv_local.y < uv_local_min.y ) || ( uv_local.y > uv_local_max.y ) ) continue; uv.x = i.uv.x + uv_offset.x; uv.y = i.uv.y + uv_offset.y; sampling = tex2Dlod( _MainTex, fixed4( uv.x, uv.y, 0, 0 ) ); bool outline = exterior ? ( sampling.a >= alpha_threshold ) : ( sampling.a <= alpha_threshold ); if ( outline ) { dst = ( x * x ) + ( y * y ); if( min_dst > dst ) min_dst = dst; } } } if( min_dst < 1.0e38f ) { fixed alpha = 1.0f - ( clamp( ( min_dst ) / ( _Thickness * _Thickness ), 0.0f, 1.0f ) ); return alpha; } } return -1.0f; } //******************************************************************************************** // //******************************************************************************************** v2f vert( appdata v, uint id : SV_VertexID ) { v2f o; o.vertex = UnityObjectToClipPos( v.vertex ); o.uv = TRANSFORM_TEX( v.uv, _MainTex ); o.color = v.color; o.uv_local = quads_uvs[ id & 3 ]; return o; } //******************************************************************************************** // //******************************************************************************************** fixed4 frag( v2f i ) : SV_Target { fixed4 output = tex2D( _MainTex, i.uv ); output = fixed4( i.color.r, i.color.g, i.color.b, output.a ); if( _Exterior > 0.0f ) { fixed outline_alpha = GetOutlineAlpha( i, output.a, true ); if( outline_alpha > 0.0f ) return fixed4( _OutlineColor.r, _OutlineColor.g, _OutlineColor.b, _OutlineColor.a * outline_alpha * i.color.a ); } if( _Interior > 0.0f ) { fixed outline_alpha = GetOutlineAlpha( i, output.a, false ); if( outline_alpha > 0.0f ) { fixed4 material_color = fixed4( i.color.r, i.color.g, i.color.b, 1.0f ); fixed4 result = lerp( material_color, _OutlineColor, outline_alpha ); return fixed4( result.r, result.g, result.b, result.a * i.color.a ); } } return output; } ENDCG } } Fallback "UI/Default" }
License
Well, it's one of my first shaders, so... . Planet free.