How to make a Custom TMP Shader and Inspector UI?

Am struggling to make a custom TMP Shader from the default TMP_SDF.shader with a correspondingly custom TMP_SDFShaderGUI

TMP_SDFShaderGUI inherits from TMP_BaseShaderGUI, which inherits from ShaderGUI

I thought it would be as simple as making a new TMP_SDFShaderGUI_mine, and making a few modifications.

This doesn’t work, all the formatting in the Unity Material Inspector is lost.

So then I made a copy of TMP_BaseShaderGUI, and inherited from that… not at all modded, just renamed to TMP_BaseShader_mine

This doesn’t work, either - same problem, all the Unity Material Inspector formatting is lost.

There’s something about global packages and the npm and a cache that I don’t understand, too.

What is the exact process, please, for making a custom TMP Shader with an easily customisable TMP_SDFShaderGUI of its own?

Copy code:

Shader "CustomFont" {

Properties {
    _FaceTex            ("Face Texture", 2D) = "white" {}
    _FaceUVSpeedX        ("Face UV Speed X", Range(-5, 5)) = 0.0
    _FaceUVSpeedY        ("Face UV Speed Y", Range(-5, 5)) = 0.0
    _FaceColor            ("Face Color", Color) = (1,1,1,1)
    _FaceDilate            ("Face Dilate", Range(-1,1)) = 0

    _OutlineColor        ("Outline Color", Color) = (0,0,0,1)
    _OutlineTex            ("Outline Texture", 2D) = "white" {}
    _OutlineUVSpeedX    ("Outline UV Speed X", Range(-5, 5)) = 0.0
    _OutlineUVSpeedY    ("Outline UV Speed Y", Range(-5, 5)) = 0.0
    _OutlineWidth        ("Outline Thickness", Range(0, 1)) = 0
    _OutlineSoftness    ("Outline Softness", Range(0,1)) = 0

    _Bevel                ("Bevel", Range(0,1)) = 0.5
    _BevelOffset        ("Bevel Offset", Range(-0.5,0.5)) = 0
    _BevelWidth            ("Bevel Width", Range(-.5,0.5)) = 0
    _BevelClamp            ("Bevel Clamp", Range(0,1)) = 0
    _BevelRoundness        ("Bevel Roundness", Range(0,1)) = 0

    _LightAngle            ("Light Angle", Range(0.0, 6.2831853)) = 3.1416
    _SpecularColor        ("Specular", Color) = (1,1,1,1)
    _SpecularPower        ("Specular", Range(0,4)) = 2.0
    _Reflectivity        ("Reflectivity", Range(5.0,15.0)) = 10
    _Diffuse            ("Diffuse", Range(0,1)) = 0.5
    _Ambient            ("Ambient", Range(1,0)) = 0.5

    _BumpMap             ("Normal map", 2D) = "bump" {}
    _BumpOutline        ("Bump Outline", Range(0,1)) = 0
    _BumpFace            ("Bump Face", Range(0,1)) = 0

    _ReflectFaceColor    ("Reflection Color", Color) = (0,0,0,1)
    _ReflectOutlineColor("Reflection Color", Color) = (0,0,0,1)
    _Cube                 ("Reflection Cubemap", Cube) = "black" { /* TexGen CubeReflect */ }
    _EnvMatrixRotation    ("Texture Rotation", vector) = (0, 0, 0, 0)
       

    _UnderlayColor        ("Border Color", Color) = (0,0,0, 0.5)
    _UnderlayOffsetX    ("Border OffsetX", Range(-1,1)) = 0
    _UnderlayOffsetY    ("Border OffsetY", Range(-1,1)) = 0
    _UnderlayDilate        ("Border Dilate", Range(-1,1)) = 0
    _UnderlaySoftness    ("Border Softness", Range(0,1)) = 0

    _GlowColor            ("Color", Color) = (0, 1, 0, 0.5)
    _GlowOffset            ("Offset", Range(-1,1)) = 0
    _GlowInner            ("Inner", Range(0,1)) = 0.05
    _GlowOuter            ("Outer", Range(0,1)) = 0.05
    _GlowPower            ("Falloff", Range(1, 0)) = 0.75

    _WeightNormal        ("Weight Normal", float) = 0
    _WeightBold            ("Weight Bold", float) = 0.5

    _ShaderFlags        ("Flags", float) = 0
    _ScaleRatioA        ("Scale RatioA", float) = 1
    _ScaleRatioB        ("Scale RatioB", float) = 1
    _ScaleRatioC        ("Scale RatioC", float) = 1

    _MainTex            ("Font Atlas", 2D) = "white" {}
    _TextureWidth        ("Texture Width", float) = 512
    _TextureHeight        ("Texture Height", float) = 512
    _GradientScale        ("Gradient Scale", float) = 5.0
    _ScaleX                ("Scale X", float) = 1.0
    _ScaleY                ("Scale Y", float) = 1.0
    _PerspectiveFilter    ("Perspective Correction", Range(0, 1)) = 0.875
    _Sharpness            ("Sharpness", Range(-1,1)) = 0

    _VertexOffsetX        ("Vertex OffsetX", float) = 0
    _VertexOffsetY        ("Vertex OffsetY", float) = 0
   
    _MaskCoord            ("Mask Coordinates", vector) = (0, 0, 32767, 32767)
    _ClipRect            ("Clip Rect", vector) = (-32767, -32767, 32767, 32767)
    _MaskSoftnessX        ("Mask SoftnessX", float) = 0
    _MaskSoftnessY        ("Mask SoftnessY", float) = 0

    _StencilComp        ("Stencil Comparison", Float) = 8
    _Stencil            ("Stencil ID", Float) = 0
    _StencilOp            ("Stencil Operation", Float) = 0
    _StencilWriteMask    ("Stencil Write Mask", Float) = 255
    _StencilReadMask    ("Stencil Read Mask", Float) = 255

    _ColorMask            ("Color Mask", Float) = 15
}

SubShader {

    Tags
    {
        "Queue"="Transparent"
        "IgnoreProjector"="True"
        "RenderType"="Transparent"
    }

    Stencil
    {
        Ref [_Stencil]
        Comp [_StencilComp]
        Pass [_StencilOp]
        ReadMask [_StencilReadMask]
        WriteMask [_StencilWriteMask]
    }

    Cull [_CullMode]
    ZWrite Off
    Lighting Off
    Fog { Mode Off }
    ZTest [unity_GUIZTestMode]
    Blend One OneMinusSrcAlpha
    ColorMask [_ColorMask]

    Pass {
        CGPROGRAM
        #pragma target 3.0
        #pragma vertex VertShader
        #pragma fragment PixShader
        #pragma shader_feature __ BEVEL_ON
        #pragma shader_feature __ UNDERLAY_ON UNDERLAY_INNER
        #pragma shader_feature __ GLOW_ON

        #pragma multi_compile __ UNITY_UI_CLIP_RECT
        #pragma multi_compile __ UNITY_UI_ALPHACLIP

        #include "UnityCG.cginc"
        #include "UnityUI.cginc"
        #include "TMPro_Properties.cginc"
        #include "TMPro.cginc"

        struct vertex_t {
            UNITY_VERTEX_INPUT_INSTANCE_ID
            float4    position        : POSITION;
            float3    normal            : NORMAL;
            fixed4    color            : COLOR;
            float2    texcoord0        : TEXCOORD0;
            float2    texcoord1        : TEXCOORD1;
        };


        struct pixel_t {
            UNITY_VERTEX_INPUT_INSTANCE_ID
            UNITY_VERTEX_OUTPUT_STEREO
            float4    position        : SV_POSITION;
            fixed4    color            : COLOR;
            float2    atlas            : TEXCOORD0;        // Atlas
            float4    param            : TEXCOORD1;        // alphaClip, scale, bias, weight
            float4    mask            : TEXCOORD2;        // Position in object space(xy), pixel Size(zw)
            float3    viewDir            : TEXCOORD3;
           
        #if (UNDERLAY_ON || UNDERLAY_INNER)
            float4    texcoord2        : TEXCOORD4;        // u,v, scale, bias
            fixed4    underlayColor    : COLOR1;
        #endif
            float4 textures            : TEXCOORD5;
        };

        // Used by Unity internally to handle Texture Tiling and Offset.
        float4 _FaceTex_ST;
        float4 _OutlineTex_ST;

        pixel_t VertShader(vertex_t input)
        {
            pixel_t output;

            UNITY_INITIALIZE_OUTPUT(pixel_t, output);
            UNITY_SETUP_INSTANCE_ID(input);
            UNITY_TRANSFER_INSTANCE_ID(input,output);
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

            float bold = step(input.texcoord1.y, 0);

            float4 vert = input.position;
            vert.x += _VertexOffsetX;
            vert.y += _VertexOffsetY;

            float4 vPosition = UnityObjectToClipPos(vert);

            float2 pixelSize = vPosition.w;
            pixelSize /= float2(_ScaleX, _ScaleY) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy));
            float scale = rsqrt(dot(pixelSize, pixelSize));
            scale *= abs(input.texcoord1.y) * _GradientScale * (_Sharpness + 1);
            if (UNITY_MATRIX_P[3][3] == 0) scale = lerp(abs(scale) * (1 - _PerspectiveFilter), scale, abs(dot(UnityObjectToWorldNormal(input.normal.xyz), normalize(WorldSpaceViewDir(vert)))));

            float weight = lerp(_WeightNormal, _WeightBold, bold) / 4.0;
            weight = (weight + _FaceDilate) * _ScaleRatioA * 0.5;

            float bias =(.5 - weight) + (.5 / scale);

            float alphaClip = (1.0 - _OutlineWidth * _ScaleRatioA - _OutlineSoftness * _ScaleRatioA);
       
        #if GLOW_ON
            alphaClip = min(alphaClip, 1.0 - _GlowOffset * _ScaleRatioB - _GlowOuter * _ScaleRatioB);
        #endif

            alphaClip = alphaClip / 2.0 - ( .5 / scale) - weight;

        #if (UNDERLAY_ON || UNDERLAY_INNER)
            float4 underlayColor = _UnderlayColor;
            underlayColor.rgb *= underlayColor.a;

            float bScale = scale;
            bScale /= 1 + ((_UnderlaySoftness*_ScaleRatioC) * bScale);
            float bBias = (0.5 - weight) * bScale - 0.5 - ((_UnderlayDilate * _ScaleRatioC) * 0.5 * bScale);

            float x = -(_UnderlayOffsetX * _ScaleRatioC) * _GradientScale / _TextureWidth;
            float y = -(_UnderlayOffsetY * _ScaleRatioC) * _GradientScale / _TextureHeight;
            float2 bOffset = float2(x, y);
        #endif

            // Generate UV for the Masking Texture
            float4 clampedRect = clamp(_ClipRect, -2e10, 2e10);
            float2 maskUV = (vert.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy);

            // Support for texture tiling and offset
            float2 textureUV = UnpackUV(input.texcoord1.x);
            float2 faceUV = TRANSFORM_TEX(textureUV, _FaceTex);
            float2 outlineUV = TRANSFORM_TEX(textureUV, _OutlineTex);

           
            output.position = vPosition;
            output.color = input.color;
            output.atlas =    input.texcoord0;
            output.param =    float4(alphaClip, scale, bias, weight);
            output.mask = half4(vert.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_MaskSoftnessX, _MaskSoftnessY) + pixelSize.xy));
            output.viewDir =    mul((float3x3)_EnvMatrix, _WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, vert).xyz);
            #if (UNDERLAY_ON || UNDERLAY_INNER)
            output.texcoord2 = float4(input.texcoord0 + bOffset, bScale, bBias);
            output.underlayColor =    underlayColor;
            #endif
            output.textures = float4(faceUV, outlineUV);
   
            return output;
        }

        fixed4 PixShader(pixel_t input) : SV_Target
        {
            UNITY_SETUP_INSTANCE_ID(input);

            float c = tex2D(_MainTex, input.atlas).a;
       
            #ifndef UNDERLAY_ON
                clip(c - input.param.x);
            #endif

            float    scale    = input.param.y;
            float    bias    = input.param.z;
            float    weight    = input.param.w;
            float    sd = (bias - c) * scale;

            float4 rgba = float4(1.0, 0.0, 0.0, 1.0);
           
            return rgba;
        }

        ENDCG
    }
}

Fallback "TextMeshPro/Mobile/Distance Field"
CustomEditor "TMPro.EditorUtilities.TMP_SDFShaderGUI"
}

and save as CustomFont.shader, in directory Assets\TextMesh Pro\Resources\Shaders. Then select shader CustomFont in material. You will see hardcoded red color (line 249), to change color, edit variable “rgba”.

1 Like

@Stephan_B Please, good sir… can you help me get through the second part?

Mr Zaworski has very kindly gotten me over hump number one (the custom shader), but I’m still stuck trying to make a custom editor for the material out of the TMP_SDFShaderGUI.

Lost as in when you close and re-open Unity? If so, this is simply due to your changed being local where they get overwritten since packages are supposed to be immutable. The solution is to make the changes in the Global Package Cache in “…/packages/packages.unity.com/com.unity.textmeshpro@…” where … is the version of the TMP package you are using.

Then you should be able to simply create your own TMP_SDFShaderGUI_mine as you described above. You will also need to make sure that your shader includes
CustomEditor “TMPro.EditorUtilities.TMP_SDFShaderGUI_mine”

This is exactly what I’ve done, multiple times.

And I’ve tried variations of this, across different folders and possible caches and different names and everything I can imagine to try.

However, every single time, it either doesn’t work, or all the Inspector formatting is lost (most things are in the Inspector, but everything is right justified, there’s none of the lovely layout to the inspector (material) that you have created.

And some of the information from the parent class seems to not be coming down into the inspector.

Also, when changing the name of the class in Rider, to TMP_SDFShaderGUI_mine, Rider loses all code formatting, seemingly unable to believe that the file is C#.

I’ve tried multiple restarts, of everything, too.

There is also an issue, sometimes (not always) in the console, saying this:

Read only asset Packages/com.unity.textmeshpro/Scripts/Editor/TMP_SDFShaderGUI_mine.cs has no meta file.

This is not consistent. None of the folders its nested in, nor the file itself are flagged as read only by the operating system - full read/write for all folders and the file itself.

This is a Mac, Unity 2019LTS.

Another alternative is for you to embed the current version of the TMP package in your project. To do so, copy the “com.unity.textmeshpro@2.1.6” from the global cache to the “Project Root/packages/…” folder. Although not necessary, I would rename the package to remove the @2.1.6.

The above will then allow you to modify the package from within Unity where the changes will be persistent. Note that running with an embedded package will prevent you from upgrading to other versions of the TMP package. That is until you remove the embedded package.

Before attacking the above approach, thought I’d try in 2018.LTS, where I’m learning a bit about shaders, due to its vastly faster and more stable handling of… everything … // insert rant about everything added by Unity since… etc

2018, when I do the process of trying to mod the Global cache, gives a slightly more verbose console insight into what might be going wrong:

UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr) (at /Users/bokken/buildslave/unity/build/Modules/IMGUI/GUIUtility.cs:179)```

In answer to those questions... the files are EXACTLY the same as the originals shipped with the package, they're duplicates, with the only things changed being the file name and class name.

Does this make any sense? It's all above me. No idea what's going on.

This approach seems to work, somewhat. I imagine it’s a pain to update after having gone down this route, and probably almost impossible to easily share shaders and GUI editors for them with others, having done this?

It also massively slows the editor update after making any script changes.