Search Unity

TextMesh Pro TextMesh Pro bitmap font outlines and shadows

Discussion in 'UGUI & TextMesh Pro' started by rempelj, Apr 23, 2017.

  1. rempelj

    rempelj

    Joined:
    Aug 3, 2013
    Posts:
    54
    What is the recommended solution for adding outlines or shadows to bitmap fonts (font assets created using the Hinted Raster setting)?

    I made a custom shader to do this in the meantime, but my shader skills are lacking. The shader uses a total of 5 passes to render the text 5 times with different offsets applied to the first 4 passes. Obviously this is not good for performance, and it's very limited in terms of customization.



    Attached is an image of what I'm working with below. I would love to see support for outlines and shadows in the material settings for the default bitmap font shader!

     
  2. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    The ability to dynamically style the text in TextMesh Pro is a bi-product of the use of Signed Distance Field and associated shaders. When using SDF Font Assets, adding outline / shadows is pretty efficient.

    Adding shadow and outline to bitmap fonts is much more complex and not currently supported as it is not very efficient.
     
  3. rempelj

    rempelj

    Joined:
    Aug 3, 2013
    Posts:
    54
    Unfortunately I can't use SDF in this case because I need a crisp 12pt font and Hinted Raster seems to be the only way to achieve that.

    I was using Unity's Outline component with the UGUI Text component before I switched to TMP. I need to replace it with something.

    PS. TMP has been a lifesaver on many levels, especially with the fallback system for localization. Excited to make use of the native Unity integration.

    ---

    To anyone who stumbles into this thread and wants to use the shader: Here it is. Be warned it is a terrible hack job. If you improve it, please share.

    Code (csharp):
    1. Shader "TextMeshPro/BitmapShadow" {
    2.  
    3. Properties {
    4.     _MainTex        ("Font Atlas", 2D) = "white" {}
    5.     _FaceTex        ("Font Texture", 2D) = "white" {}
    6.     _FaceColor        ("Text Color", Color) = (1,1,1,1)
    7.  
    8.     _ShadowColor    ("Shadow Color", Color) = (0,0,0,0)
    9.     _Shadow1X        ("Shadow 1X", Float) = 0
    10.     _Shadow1Y        ("Shadow 1Y", Float) = 0
    11.     _Shadow2X        ("Shadow 2X", Float) = 0
    12.     _Shadow2Y        ("Shadow 2Y", Float) = 0
    13.     _Shadow3X        ("Shadow 1X", Float) = 0
    14.     _Shadow3Y        ("Shadow 1Y", Float) = 0
    15.     _Shadow4X        ("Shadow 2X", Float) = 0
    16.     _Shadow4Y        ("Shadow 2Y", Float) = 0
    17.  
    18.     _VertexOffsetX    ("Vertex OffsetX", float) = 0
    19.     _VertexOffsetY    ("Vertex OffsetY", float) = 0
    20.     _MaskSoftnessX    ("Mask SoftnessX", float) = 0
    21.     _MaskSoftnessY    ("Mask SoftnessY", float) = 0
    22.  
    23.     _ClipRect("Clip Rect", vector) = (-32767, -32767, 32767, 32767)
    24.  
    25.     _StencilComp("Stencil Comparison", Float) = 8
    26.     _Stencil("Stencil ID", Float) = 0
    27.     _StencilOp("Stencil Operation", Float) = 0
    28.     _StencilWriteMask("Stencil Write Mask", Float) = 255
    29.     _StencilReadMask("Stencil Read Mask", Float) = 255
    30.  
    31.     _ColorMask("Color Mask", Float) = 15
    32. }
    33.  
    34. SubShader{
    35.  
    36.     Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
    37.  
    38.     Stencil
    39.     {
    40.         Ref[_Stencil]
    41.         Comp[_StencilComp]
    42.         Pass[_StencilOp]
    43.         ReadMask[_StencilReadMask]
    44.         WriteMask[_StencilWriteMask]
    45.     }
    46.  
    47.  
    48.     Lighting Off
    49.     Cull [_CullMode]
    50.     ZTest [unity_GUIZTestMode]
    51.     ZWrite Off
    52.     Fog { Mode Off }
    53.     Blend SrcAlpha OneMinusSrcAlpha
    54.     ColorMask[_ColorMask]
    55.  
    56.  
    57.     Pass {
    58.         CGPROGRAM
    59.         #pragma vertex vert
    60.         #pragma fragment frag
    61.  
    62.         #include "UnityCG.cginc"
    63.  
    64.  
    65.     #if UNITY_VERSION < 530
    66.         bool _UseClipRect;
    67.     #endif
    68.  
    69.         struct appdata_t {
    70.             float4 vertex        : POSITION;
    71.             fixed4 color        : COLOR;
    72.             float2 texcoord0    : TEXCOORD0;
    73.             float2 texcoord1    : TEXCOORD1;
    74.         };
    75.  
    76.         struct v2f {
    77.             float4    vertex        : POSITION;
    78.             fixed4    color        : COLOR;
    79.             float2    texcoord0    : TEXCOORD0;
    80.             float2    texcoord1    : TEXCOORD1;
    81.             float4    mask        : TEXCOORD2;
    82.         };
    83.  
    84.         uniform    sampler2D     _MainTex;
    85.         uniform    sampler2D     _FaceTex;
    86.         uniform float4        _FaceTex_ST;
    87.         uniform    fixed4        _FaceColor;
    88.  
    89.         uniform    fixed4        _ShadowColor;
    90.         uniform float        _Shadow1X;
    91.         uniform float        _Shadow1Y;
    92.         uniform float        _Shadow2X;
    93.         uniform float        _Shadow2Y;
    94.         uniform float        _Shadow3X;
    95.         uniform float        _Shadow3Y;
    96.         uniform float        _Shadow4X;
    97.         uniform float        _Shadow4Y;
    98.  
    99.         uniform float        _VertexOffsetX;
    100.         uniform float        _VertexOffsetY;
    101.         uniform float4        _ClipRect;
    102.         uniform float        _MaskSoftnessX;
    103.         uniform float        _MaskSoftnessY;
    104.  
    105.         float2 UnpackUV(float uv)
    106.         {
    107.             float2 output;
    108.             output.x = floor(uv / 4096);
    109.             output.y = uv - 4096 * output.x;
    110.  
    111.             return output * 0.001953125;
    112.         }
    113.  
    114.         v2f vert (appdata_t i)
    115.         {
    116.             float4 vert = i.vertex;
    117.             vert.x += _VertexOffsetX + _Shadow1X;
    118.             vert.y += _VertexOffsetY + _Shadow1Y;
    119.  
    120.             vert.xy += (vert.w * 0.5) / _ScreenParams.xy;
    121.  
    122.             float4 vPosition = UnityPixelSnap(mul(UNITY_MATRIX_MVP, vert));
    123.  
    124.             fixed4 faceColor = i.color;
    125.             faceColor *= _ShadowColor;
    126.  
    127.             v2f o;
    128.             o.vertex = vPosition;
    129.             o.color = faceColor;
    130.             o.texcoord0 = i.texcoord0;
    131.             o.texcoord1 = TRANSFORM_TEX(UnpackUV(i.texcoord1), _FaceTex);
    132.             float2 pixelSize = vPosition.w;
    133.             pixelSize /= abs(float2(_ScreenParams.x * UNITY_MATRIX_P[0][0], _ScreenParams.y * UNITY_MATRIX_P[1][1]));
    134.  
    135.             // Clamp _ClipRect to 16bit.
    136.             float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    137.             o.mask = float4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
    138.          
    139.             return o;
    140.         }
    141.  
    142.         fixed4 frag (v2f i) : COLOR
    143.         {
    144.             //fixed4 c = tex2D(_MainTex, i.texcoord0) * tex2D(_FaceTex, i.texcoord1) * i.color;
    145.          
    146.             fixed4 c = tex2D(_MainTex, i.texcoord0);
    147.             c = fixed4 (tex2D(_FaceTex, i.texcoord1).rgb * i.color.rgb, i.color.a * c.a);
    148.  
    149.             #if UNITY_VERSION < 530
    150.                 if (_UseClipRect)
    151.                 {
    152.                     // Alternative implementation to UnityGet2DClipping with support for softness.
    153.                     half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    154.                     c *= m.x * m.y;
    155.                 }
    156.             #else
    157.                 // Alternative implementation to UnityGet2DClipping with support for softness.
    158.                 half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    159.                 c *= m.x * m.y;
    160.             #endif
    161.  
    162.             return c;
    163.         }
    164.         ENDCG
    165.     }
    166.  
    167.  
    168.     Pass {
    169.         CGPROGRAM
    170.         #pragma vertex vert
    171.         #pragma fragment frag
    172.  
    173.         #include "UnityCG.cginc"
    174.  
    175.  
    176.     #if UNITY_VERSION < 530
    177.         bool _UseClipRect;
    178.     #endif
    179.  
    180.         struct appdata_t {
    181.             float4 vertex        : POSITION;
    182.             fixed4 color        : COLOR;
    183.             float2 texcoord0    : TEXCOORD0;
    184.             float2 texcoord1    : TEXCOORD1;
    185.         };
    186.  
    187.         struct v2f {
    188.             float4    vertex        : POSITION;
    189.             fixed4    color        : COLOR;
    190.             float2    texcoord0    : TEXCOORD0;
    191.             float2    texcoord1    : TEXCOORD1;
    192.             float4    mask        : TEXCOORD2;
    193.         };
    194.  
    195.         uniform    sampler2D     _MainTex;
    196.         uniform    sampler2D     _FaceTex;
    197.         uniform float4        _FaceTex_ST;
    198.         uniform    fixed4        _FaceColor;
    199.  
    200.         uniform    fixed4        _ShadowColor;
    201.         uniform float        _Shadow1X;
    202.         uniform float        _Shadow1Y;
    203.         uniform float        _Shadow2X;
    204.         uniform float        _Shadow2Y;
    205.         uniform float        _Shadow3X;
    206.         uniform float        _Shadow3Y;
    207.         uniform float        _Shadow4X;
    208.         uniform float        _Shadow4Y;
    209.  
    210.         uniform float        _VertexOffsetX;
    211.         uniform float        _VertexOffsetY;
    212.         uniform float4        _ClipRect;
    213.         uniform float        _MaskSoftnessX;
    214.         uniform float        _MaskSoftnessY;
    215.  
    216.         float2 UnpackUV(float uv)
    217.         {
    218.             float2 output;
    219.             output.x = floor(uv / 4096);
    220.             output.y = uv - 4096 * output.x;
    221.  
    222.             return output * 0.001953125;
    223.         }
    224.  
    225.         v2f vert (appdata_t i)
    226.         {
    227.             float4 vert = i.vertex;
    228.             vert.x += _VertexOffsetX + _Shadow2X;
    229.             vert.y += _VertexOffsetY + _Shadow2Y;
    230.  
    231.             vert.xy += (vert.w * 0.5) / _ScreenParams.xy;
    232.  
    233.             float4 vPosition = UnityPixelSnap(mul(UNITY_MATRIX_MVP, vert));
    234.  
    235.             fixed4 faceColor = i.color;
    236.             faceColor *= _ShadowColor;
    237.  
    238.             v2f o;
    239.             o.vertex = vPosition;
    240.             o.color = faceColor;
    241.             o.texcoord0 = i.texcoord0;
    242.             o.texcoord1 = TRANSFORM_TEX(UnpackUV(i.texcoord1), _FaceTex);
    243.             float2 pixelSize = vPosition.w;
    244.             pixelSize /= abs(float2(_ScreenParams.x * UNITY_MATRIX_P[0][0], _ScreenParams.y * UNITY_MATRIX_P[1][1]));
    245.  
    246.             // Clamp _ClipRect to 16bit.
    247.             float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    248.             o.mask = float4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
    249.          
    250.             return o;
    251.         }
    252.  
    253.         fixed4 frag (v2f i) : COLOR
    254.         {
    255.             //fixed4 c = tex2D(_MainTex, i.texcoord0) * tex2D(_FaceTex, i.texcoord1) * i.color;
    256.          
    257.             fixed4 c = tex2D(_MainTex, i.texcoord0);
    258.             c = fixed4 (tex2D(_FaceTex, i.texcoord1).rgb * i.color.rgb, i.color.a * c.a);
    259.  
    260.             #if UNITY_VERSION < 530
    261.                 if (_UseClipRect)
    262.                 {
    263.                     // Alternative implementation to UnityGet2DClipping with support for softness.
    264.                     half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    265.                     c *= m.x * m.y;
    266.                 }
    267.             #else
    268.                 // Alternative implementation to UnityGet2DClipping with support for softness.
    269.                 half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    270.                 c *= m.x * m.y;
    271.             #endif
    272.  
    273.             return c;
    274.         }
    275.         ENDCG
    276.     }
    277.  
    278.  
    279.  
    280.     Pass {
    281.         CGPROGRAM
    282.         #pragma vertex vert
    283.         #pragma fragment frag
    284.  
    285.         #include "UnityCG.cginc"
    286.  
    287.  
    288.     #if UNITY_VERSION < 530
    289.         bool _UseClipRect;
    290.     #endif
    291.  
    292.         struct appdata_t {
    293.             float4 vertex        : POSITION;
    294.             fixed4 color        : COLOR;
    295.             float2 texcoord0    : TEXCOORD0;
    296.             float2 texcoord1    : TEXCOORD1;
    297.         };
    298.  
    299.         struct v2f {
    300.             float4    vertex        : POSITION;
    301.             fixed4    color        : COLOR;
    302.             float2    texcoord0    : TEXCOORD0;
    303.             float2    texcoord1    : TEXCOORD1;
    304.             float4    mask        : TEXCOORD2;
    305.         };
    306.  
    307.         uniform    sampler2D     _MainTex;
    308.         uniform    sampler2D     _FaceTex;
    309.         uniform float4        _FaceTex_ST;
    310.         uniform    fixed4        _FaceColor;
    311.  
    312.         uniform    fixed4        _ShadowColor;
    313.         uniform float        _Shadow1X;
    314.         uniform float        _Shadow1Y;
    315.         uniform float        _Shadow2X;
    316.         uniform float        _Shadow2Y;
    317.         uniform float        _Shadow3X;
    318.         uniform float        _Shadow3Y;
    319.         uniform float        _Shadow4X;
    320.         uniform float        _Shadow4Y;
    321.  
    322.         uniform float        _VertexOffsetX;
    323.         uniform float        _VertexOffsetY;
    324.         uniform float4        _ClipRect;
    325.         uniform float        _MaskSoftnessX;
    326.         uniform float        _MaskSoftnessY;
    327.  
    328.         float2 UnpackUV(float uv)
    329.         {
    330.             float2 output;
    331.             output.x = floor(uv / 4096);
    332.             output.y = uv - 4096 * output.x;
    333.  
    334.             return output * 0.001953125;
    335.         }
    336.  
    337.         v2f vert (appdata_t i)
    338.         {
    339.             float4 vert = i.vertex;
    340.             vert.x += _VertexOffsetX + _Shadow3X;
    341.             vert.y += _VertexOffsetY + _Shadow3Y;
    342.  
    343.             vert.xy += (vert.w * 0.5) / _ScreenParams.xy;
    344.  
    345.             float4 vPosition = UnityPixelSnap(mul(UNITY_MATRIX_MVP, vert));
    346.  
    347.             fixed4 faceColor = i.color;
    348.             faceColor *= _ShadowColor;
    349.  
    350.             v2f o;
    351.             o.vertex = vPosition;
    352.             o.color = faceColor;
    353.             o.texcoord0 = i.texcoord0;
    354.             o.texcoord1 = TRANSFORM_TEX(UnpackUV(i.texcoord1), _FaceTex);
    355.             float2 pixelSize = vPosition.w;
    356.             pixelSize /= abs(float2(_ScreenParams.x * UNITY_MATRIX_P[0][0], _ScreenParams.y * UNITY_MATRIX_P[1][1]));
    357.  
    358.             // Clamp _ClipRect to 16bit.
    359.             float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    360.             o.mask = float4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
    361.          
    362.             return o;
    363.         }
    364.  
    365.         fixed4 frag (v2f i) : COLOR
    366.         {
    367.             //fixed4 c = tex2D(_MainTex, i.texcoord0) * tex2D(_FaceTex, i.texcoord1) * i.color;
    368.          
    369.             fixed4 c = tex2D(_MainTex, i.texcoord0);
    370.             c = fixed4 (tex2D(_FaceTex, i.texcoord1).rgb * i.color.rgb, i.color.a * c.a);
    371.  
    372.             #if UNITY_VERSION < 530
    373.                 if (_UseClipRect)
    374.                 {
    375.                     // Alternative implementation to UnityGet2DClipping with support for softness.
    376.                     half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    377.                     c *= m.x * m.y;
    378.                 }
    379.             #else
    380.                 // Alternative implementation to UnityGet2DClipping with support for softness.
    381.                 half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    382.                 c *= m.x * m.y;
    383.             #endif
    384.  
    385.             return c;
    386.         }
    387.         ENDCG
    388.     }
    389.  
    390.  
    391.  
    392.     Pass {
    393.         CGPROGRAM
    394.         #pragma vertex vert
    395.         #pragma fragment frag
    396.  
    397.         #include "UnityCG.cginc"
    398.  
    399.  
    400.     #if UNITY_VERSION < 530
    401.         bool _UseClipRect;
    402.     #endif
    403.  
    404.         struct appdata_t {
    405.             float4 vertex        : POSITION;
    406.             fixed4 color        : COLOR;
    407.             float2 texcoord0    : TEXCOORD0;
    408.             float2 texcoord1    : TEXCOORD1;
    409.         };
    410.  
    411.         struct v2f {
    412.             float4    vertex        : POSITION;
    413.             fixed4    color        : COLOR;
    414.             float2    texcoord0    : TEXCOORD0;
    415.             float2    texcoord1    : TEXCOORD1;
    416.             float4    mask        : TEXCOORD2;
    417.         };
    418.  
    419.         uniform    sampler2D     _MainTex;
    420.         uniform    sampler2D     _FaceTex;
    421.         uniform float4        _FaceTex_ST;
    422.         uniform    fixed4        _FaceColor;
    423.  
    424.         uniform    fixed4        _ShadowColor;
    425.         uniform float        _Shadow1X;
    426.         uniform float        _Shadow1Y;
    427.         uniform float        _Shadow2X;
    428.         uniform float        _Shadow2Y;
    429.         uniform float        _Shadow3X;
    430.         uniform float        _Shadow3Y;
    431.         uniform float        _Shadow4X;
    432.         uniform float        _Shadow4Y;
    433.  
    434.         uniform float        _VertexOffsetX;
    435.         uniform float        _VertexOffsetY;
    436.         uniform float4        _ClipRect;
    437.         uniform float        _MaskSoftnessX;
    438.         uniform float        _MaskSoftnessY;
    439.  
    440.         float2 UnpackUV(float uv)
    441.         {
    442.             float2 output;
    443.             output.x = floor(uv / 4096);
    444.             output.y = uv - 4096 * output.x;
    445.  
    446.             return output * 0.001953125;
    447.         }
    448.  
    449.         v2f vert (appdata_t i)
    450.         {
    451.             float4 vert = i.vertex;
    452.             vert.x += _VertexOffsetX + _Shadow4X;
    453.             vert.y += _VertexOffsetY + _Shadow4Y;
    454.  
    455.             vert.xy += (vert.w * 0.5) / _ScreenParams.xy;
    456.  
    457.             float4 vPosition = UnityPixelSnap(mul(UNITY_MATRIX_MVP, vert));
    458.  
    459.             fixed4 faceColor = i.color;
    460.             faceColor *= _ShadowColor;
    461.  
    462.             v2f o;
    463.             o.vertex = vPosition;
    464.             o.color = faceColor;
    465.             o.texcoord0 = i.texcoord0;
    466.             o.texcoord1 = TRANSFORM_TEX(UnpackUV(i.texcoord1), _FaceTex);
    467.             float2 pixelSize = vPosition.w;
    468.             pixelSize /= abs(float2(_ScreenParams.x * UNITY_MATRIX_P[0][0], _ScreenParams.y * UNITY_MATRIX_P[1][1]));
    469.  
    470.             // Clamp _ClipRect to 16bit.
    471.             float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    472.             o.mask = float4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
    473.          
    474.             return o;
    475.         }
    476.  
    477.         fixed4 frag (v2f i) : COLOR
    478.         {
    479.             //fixed4 c = tex2D(_MainTex, i.texcoord0) * tex2D(_FaceTex, i.texcoord1) * i.color;
    480.          
    481.             fixed4 c = tex2D(_MainTex, i.texcoord0);
    482.             c = fixed4 (tex2D(_FaceTex, i.texcoord1).rgb * i.color.rgb, i.color.a * c.a);
    483.  
    484.             #if UNITY_VERSION < 530
    485.                 if (_UseClipRect)
    486.                 {
    487.                     // Alternative implementation to UnityGet2DClipping with support for softness.
    488.                     half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    489.                     c *= m.x * m.y;
    490.                 }
    491.             #else
    492.                 // Alternative implementation to UnityGet2DClipping with support for softness.
    493.                 half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    494.                 c *= m.x * m.y;
    495.             #endif
    496.  
    497.             return c;
    498.         }
    499.         ENDCG
    500.     }
    501.  
    502.  
    503.     Pass {
    504.         CGPROGRAM
    505.         #pragma vertex vert
    506.         #pragma fragment frag
    507.  
    508.         #include "UnityCG.cginc"
    509.  
    510.  
    511.     #if UNITY_VERSION < 530
    512.         bool _UseClipRect;
    513.     #endif
    514.  
    515.         struct appdata_t {
    516.             float4 vertex        : POSITION;
    517.             fixed4 color        : COLOR;
    518.             float2 texcoord0    : TEXCOORD0;
    519.             float2 texcoord1    : TEXCOORD1;
    520.         };
    521.  
    522.         struct v2f {
    523.             float4    vertex        : POSITION;
    524.             fixed4    color        : COLOR;
    525.             float2    texcoord0    : TEXCOORD0;
    526.             float2    texcoord1    : TEXCOORD1;
    527.             float4    mask        : TEXCOORD2;
    528.         };
    529.  
    530.         uniform    sampler2D     _MainTex;
    531.         uniform    sampler2D     _FaceTex;
    532.         uniform float4        _FaceTex_ST;
    533.         uniform    fixed4        _FaceColor;
    534.  
    535.         uniform float        _VertexOffsetX;
    536.         uniform float        _VertexOffsetY;
    537.         uniform float4        _ClipRect;
    538.         uniform float        _MaskSoftnessX;
    539.         uniform float        _MaskSoftnessY;
    540.  
    541.         float2 UnpackUV(float uv)
    542.         {
    543.             float2 output;
    544.             output.x = floor(uv / 4096);
    545.             output.y = uv - 4096 * output.x;
    546.  
    547.             return output * 0.001953125;
    548.         }
    549.  
    550.         v2f vert (appdata_t i)
    551.         {
    552.             float4 vert = i.vertex;
    553.             vert.x += _VertexOffsetX;
    554.             vert.y += _VertexOffsetY;
    555.  
    556.             vert.xy += (vert.w * 0.5) / _ScreenParams.xy;
    557.  
    558.             float4 vPosition = UnityPixelSnap(mul(UNITY_MATRIX_MVP, vert));
    559.  
    560.             fixed4 faceColor = i.color;
    561.             faceColor *= _FaceColor;
    562.  
    563.             v2f o;
    564.             o.vertex = vPosition;
    565.             o.color = faceColor;
    566.             o.texcoord0 = i.texcoord0;
    567.             o.texcoord1 = TRANSFORM_TEX(UnpackUV(i.texcoord1), _FaceTex);
    568.             float2 pixelSize = vPosition.w;
    569.             pixelSize /= abs(float2(_ScreenParams.x * UNITY_MATRIX_P[0][0], _ScreenParams.y * UNITY_MATRIX_P[1][1]));
    570.  
    571.             // Clamp _ClipRect to 16bit.
    572.             float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
    573.             o.mask = float4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
    574.          
    575.             return o;
    576.         }
    577.  
    578.         fixed4 frag (v2f i) : COLOR
    579.         {
    580.             //fixed4 c = tex2D(_MainTex, i.texcoord0) * tex2D(_FaceTex, i.texcoord1) * i.color;
    581.          
    582.             fixed4 c = tex2D(_MainTex, i.texcoord0);
    583.             c = fixed4 (tex2D(_FaceTex, i.texcoord1).rgb * i.color.rgb, i.color.a * c.a);
    584.  
    585.             #if UNITY_VERSION < 530
    586.                 if (_UseClipRect)
    587.                 {
    588.                     // Alternative implementation to UnityGet2DClipping with support for softness.
    589.                     half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    590.                     c *= m.x * m.y;
    591.                 }
    592.             #else
    593.                 // Alternative implementation to UnityGet2DClipping with support for softness.
    594.                 half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(i.mask.xy)) * i.mask.zw);
    595.                 c *= m.x * m.y;
    596.             #endif
    597.  
    598.             return c;
    599.         }
    600.         ENDCG
    601.     }
    602. }
    603.  
    604. }
    605.  
     
    rayaneflx, Avalin, tosiabunio and 2 others like this.
  4. Francoimora

    Francoimora

    Joined:
    Aug 1, 2013
    Posts:
    68
    Same problem here, we have to use Hinted Raster param to have a good looking pixel art font with TM Pro. Why don't you make a shader to have some parameters with Bitmap Fonts (like outline, underlay...) ? I understand it's not "very efficient" but when it's the only solution...

    Thanks rempelj for your shader, I will try it on my project.
     
    Last edited: May 2, 2018
  5. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    The ability to dynamically add an Outline and Shadow (underlay) using the shader in TMP is a by product of using Signed Distance Field (SDF). This is why this functionality is not available on the bitmap shaders.
     
  6. Francoimora

    Francoimora

    Joined:
    Aug 1, 2013
    Posts:
    68
    Ok, but it is possible to have outlines and shadows by customizing the TMP_Bitmap shader, so why not adding them ? I'm not good at all when it comes to write shaders so I would love to have this included in TextMesh Pro. I love it so much, I really miss it on my pixel art fonts.

    PS : I'm really upset to see that I'm paying a subscription for Unity every month and the answer is always "we won't do anything", even when it's a critical bug for us (it's not the first time I experience that).
     
    Last edited: May 4, 2018
  7. Hi_ImTemmy

    Hi_ImTemmy

    Joined:
    Jul 8, 2015
    Posts:
    174
    Consider your thread stumbled upon! Did you ever discover any other ways to do this, or is this shader still the best approach?
     
  8. Scott-Steffes

    Scott-Steffes

    Joined:
    Dec 31, 2013
    Posts:
    59
    Other than a shader it is also possible to get the same outline look using a mesh effect where you add additional vertices with the vertex color set to black.
    The old Unity text component supported mesh effects, but Textmesh Pro doesn't support them out of the box.
    This project is attempting to enable mesh effects with TextMesh Pro. It works, but it's a bit buggy and breaks in certain situations. The project comes with an example mesh effect that creates an outline using the method I mentioned above.
    https://github.com/mob-sakai/MeshEffectForTextMeshPro
    The developer is responsive. If more people ping them, maybe they will get around to fixing the remaining issues.
     
    Rock360 likes this.
  9. Clonze

    Clonze

    Joined:
    Dec 15, 2012
    Posts:
    26
    when i try to use their unitypackage, it says type or namespace 'UIEffect' could not be found. Any idea why? It seems like it doesn't exist in Unity2019.3.0f6, is it a custom class?
     
  10. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Has the been any improvement to adding pixel text outlines without using SDF?
     
    Feezen and Eater_Games like this.
  11. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    Yeah we need outline for bitmap font.
     
  12. Zebbi

    Zebbi

    Joined:
    Jan 17, 2017
    Posts:
    521
    Please?
     
  13. AldeRoberge

    AldeRoberge

    Joined:
    Jun 23, 2017
    Posts:
    60
    Has anyone ever found a solution?
     
  14. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Although I don't have a solution to propose at this time, I wanted to reply to let you all know that I am reading all these posts / threads and keep tracking of all these feature requests.
     
    NeatWolf and AldeRoberge like this.
  15. Hugosslade1

    Hugosslade1

    Joined:
    Aug 2, 2021
    Posts:
    1
    I found this post while looking for a solution. I ended up following what Stephan_B suggests here - https://forum.unity.com/threads/how-to-achieve-raster-font-with-outline.859342/

    I extracted the atlas and used Photopea.com (online Photoshop) to create a special variation of the atlas with the following rules:
    • Red = Font Face (default)
    • Green = Outline
    • Blue = Drop Shadow
    • Background is fully black, uses R/G/B in custom shader as alpha.
    I've attached a PNG & PSD of this.
    PressStart2P SDF Atlas - ColorMask.png

    The shader then uses 1 texture read to handle the default face/outline/drop shadow. You use the Outline/Shadow color picker alpha to turn on/off the feature. You can also use a Face texture for things such as gradient etc.

    Here is a link to the shader: https://gist.github.com/Hugosslade/a7fbbbd45757b8367add5156e767b51e

    You must change the materials of your font to use the new shader + atlas texture. This can be done in the inspector.
     

    Attached Files:

  16. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    The proposed solution seems like a decent attempt, I am guessing that the process should be able to automated in the textmeshpro.
     
    AldeRoberge likes this.
  17. ccioanca

    ccioanca

    Joined:
    Nov 12, 2018
    Posts:
    4
    Also looking for a better solution to this. This is specifically useful for pixel art games.

    I've been using a custom solution where I edit the atlas to add outlines of different colors and a custom shader that repaints the texture based on the new atlas colors. But this feels clunky and it shouldn't be this much of a hassle to add a custom shader to TMPro
     
  18. NeatWolf

    NeatWolf

    Joined:
    Sep 27, 2013
    Posts:
    924
    I believe this still is the "best" approach, as doesn't require extra render passes.
    I'd say that applying a 1 pixel offset/stroke is well within the capabilities of TextMeshPro.

    We probably don't even need an RGB texture, as we could use specific grayscale ranges to encode the zones:
    font face(255), outline(+128), drop shadow (+64). Of course it has to be an 8bit grayscale image.

    The "lazy" (but more versatile) way would be to just use more passes. Again, TMPro could do this natively, baking the contour in the atlas, without requiring us to use a 3rd party program.

    ---

    In both cases these would be useful examples that could be included in the Samples package of TMPro.

    With so many pixel art/mobile/retro style games, would make sense to have this natively integrated.

    ...unless there's another more straightforward way to do this that I'm missing...
     
  19. ccioanca

    ccioanca

    Joined:
    Nov 12, 2018
    Posts:
    4
    I was able to eventually get this to work in a....passable way. Using a shader and some tricks, I was able to Make a customizable pixel outline.

    Some examples:
    upload_2022-9-24_15-40-47.png

    The shader is still pretty messy, but I can give a general overview of how it works.

    First of all, we need to extract the text atlas, and pass it into our shader (PS. make sure the Texture2D node does NOT have a reference of "_MainTex". I was burned by that for a while).

    To Extract the Atlas, go to your font asset and:
    upload_2022-9-24_15-31-22.png
    (Make sure to click the kebab icon in the font asset, not the unity ui window)

    Then we create a general pixel outline shader, with whatever properties you might want. Here's a basic one someone made:


    Here's my default preview in the shader:
    upload_2022-9-24_15-34-19.png

    The only gotcha, is that the shader needs to get the atlas passed to it to know what to outline. Unfortunately, I don't think there's any magic reference to pass the atlas through like "_FontAtlas" as far as I know (like there is for "_MainTex")

    So we just treat it as a 2d texture instead.

    When we apply our shader, we have to pass in that font's atlas:
    upload_2022-9-24_15-38-3.png

    There's also a few other things i have set here, like the outline color, and the font override color.

    So the whole "font atlas" thing means that it's still a little more tedious than it should be, but i think this solution beats having to extract the font into another editor/program and mess with it there first. It would be a much simpler solution if somehow TMP passed the atlas as part of the _MainTex property, but oh well.
     
  20. castor76

    castor76

    Joined:
    Dec 5, 2011
    Posts:
    2,517
    TMP should really tackle this better, since it is only viable text rendering option we currently have.
     
    AldeRoberge likes this.
  21. Lucas_Lima_14

    Lucas_Lima_14

    Joined:
    Oct 30, 2019
    Posts:
    1
    Found a good solution by Zac Marvin, you simply need switch the auto-generated bitmap with a custom one and change the TextMeshPro Font Asset Material to TextMeshPro/Bitmap Custom Atlas.

     
  22. JKarkkainen

    JKarkkainen

    Joined:
    Mar 5, 2013
    Posts:
    11
    Thank you so much @Hugosslade1, this really helped me. The other options didn't have all the options I was looking for, but with this I can do different colors and different outline colors, and still be in pixel perfectness. Thanks!