Home About Contact

SHADER SHOWCASE
UNITY
CUSTOM SHADER

CUSTOM SHADER

In this section, I have some custom shader examples that I created specifically for my personal project that I would like to share. One of these examples is a custom unlit shader that utilizes a technique called 'Splat texture'.

CUSTOM UNLIT SHADER

I created this shader with the intention of having complete control over how the material's color is displayed on the screen. Additionally, I wanted the material to be easily customizable in terms of color scheme without relying heavily on textures. This was to streamline the process of iterating on color selection to match the overall tone of the game and reduce the time it takes to create and import textures. With these goals in mind, my custom unlit shader solves this problem effectively.

TECHNIQUE I USE

SPLAT IMAGE

The primary technique that I used to build this custom unlit shader is called 'Splat texture'. The concept behind it is relatively straightforward. During the process of editing a model's UV map, each face or part of the model is mapped to an RGBA texture. The RGBA texture used is typically a checkerboard pattern consisting of four colors - red, green, blue, and black. By assigning each color to represent a different part of the model, it is then possible to change the color of each part later on as needed.

However, with this technique, we are limited to only four colors to change, which is not enough to create visually interesting objects. To overcome this limitation, another RGBA texture is added into a separate UV channel using the same technique. (This can be easily done using software such as Blender). By storing the RGB texture in two different UV channels (UV0, UV1), we are able to add more detailed color to the previous layer. Additionally, this second layer of detail allows us to adjust the color according to our needs using the same method.

SPLAT EXAMPLE

My actual implementation (From left to right)

TRIPLANAR

One other technique that I often use is called 'TRIPLANAR'. Even though we can change the color of different sides of the mesh using a 'SPLAT' texture, our material still lacks shading that would normally come from a light source when using a default lit material. To address this issue, we use a 'TRIPLANAR' color on top of the splat texture, using different blend modes, to obtain a customizable shade on an unlit object. This creates the illusion of a light source, but we have complete control over how the shader color appears.

My actual implementation (From left to right)

WORLD POSITOIN GRADIENT

The final color detail that I add to my shader is called 'WORLD POSITION GRADIENT'. This additional layer provides a sense of depth to our material, which is particularly useful for my project where the camera angle looks down on a terrain, as in an RTS or city-building game. Using a gradient that shades objects from bottom to top creates a pleasing overall tone.

My actual implementation (From left to right)

            Shader "MUGCUP Custom Shaders/Unlit/TestCombineTexture"
            {
                Properties
                {
                    _MainTex   ("Main Map"  , 2D) = "white" {}
                    _DetailTex ("Detail Map", 2D) = "white" {}
                    
                    [Space(10)]
                    _R1 ("Red Channel Color 1"  , color) = (1.0, 1.0, 1.0, 1.0)
                    _G1 ("Green Channel Color 1", color) = (1.0, 1.0, 1.0, 1.0)
                    _B1 ("Blue Channel Color 1" , color) = (1.0, 1.0, 1.0, 1.0)
                    _A1 ("Black Channel Color 1", color) = (1.0, 1.0, 1.0, 1.0)
                     
                    [Space(10)]
                    _R2("Red Channel Color 2"  , color) = (1.0, 1.0, 1.0, 1.0)
                    _G2("Green Channel Color 2", color) = (1.0, 1.0, 1.0, 1.0)
                    _B2("Blue Channel Color 2" , color) = (1.0, 1.0, 1.0, 1.0)
                    _A2("Black Channel Color 2", color) = (1.0, 1.0, 1.0, 1.0)
                    
                    [Space(10)]
                    _Mask1("Red Channel Mask"  , color) = (1.0, 1.0, 1.0, 1.0)
                    _Mask2("Green Channel Mask", color) = (1.0, 1.0, 1.0, 1.0)
                    _Mask3("Blue Channel Mask" , color) = (1.0, 1.0, 1.0, 1.0)
                    _Mask4("Black Channel Mask", color) = (1.0, 1.0, 1.0, 1.0)
                    
                    [Header(Triplanar Color)]
                    [Space(10)]
                    _FrontCol ("Front Color", color) = (1.0, 1.0, 1.0, 1.0)
                    _TopCol   ("Top Color"  , color) = (1.0, 1.0, 1.0, 1.0)
                    _SideCol  ("Side Color" , color) = (1.0, 1.0, 1.0, 1.0)
                    
                    [Toggle(OVERLAY)]   _EnableOverlay   ("Enable Overlay"  , Float) = 0
                    [Toggle(COLORBURN)] _EnableColorBurn ("Enable ColorBurn", Float) = 0
                    [Toggle(HARDLIGHT)] _EnableHardLight ("Enable HardLight", Float) = 0
                    
                    
                    [Header(Gradient Color In World Space from Top to Bottom)]
                    [Space(10)]
                    _GradientCol2 ("Gradient Color 2", color) = (1.0, 1.0, 1.0, 1.0)
                    _GradientCol1 ("Gradient Color 1", color) = (1.0, 1.0, 1.0, 1.0)
                }
                SubShader
                {
                    Tags { "RenderType"="Opaque" }
                    LOD 100
             
                    Pass
                    {
                        CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag
             
                        #include "UnityCG.cginc"
             
                        struct appdata
                        {
                            float4 vertex : POSITION;
                            float2 uv0    : TEXCOORD0;
                            float2 uv1    : TEXCOORD1;
                            float3 normal : NORMAL;
                        };
             
                        struct v2f
                        {
                            float4 vertex   : SV_POSITION;
                            float3 worldPos : TEXCOORD0;
                            float2 uv0      : TEXCOORD1;
                            float2 uv1      : TEXCOORD2;
                            float3 normal   : TEXCOORD3;
                        };
             
                        sampler2D _MainTex;
                        float4 _MainTex_ST;
             
                        sampler2D _DetailTex;
                        float4 _DetailTex_ST;
             
                        float4 _R1, _G1, _B1, _A1;
                        float4 _R2, _G2, _B2, _A2;
                
                        float4 _FrontCol, _TopCol, _SideCol;
             
                        float4 _GradientCol1,  _GradientCol2;
             
                        float overlay(float s, float d)
                        {
                            return (d < 0.5) ? 2.0 * s * d : 1.0 - 2.0 * (1.0 - s) * (1.0 - d);
                        }
                        
                        fixed3 Overlay(fixed3 s, fixed3 d)
                        {
                            fixed3 c;
                            c.x = overlay(s.x, d.x);
                            c.y = overlay(s.y, d.y);
                            c.z = overlay(s.z, d.z);
                            return c;
                        }
             
                        fixed3 ColorBurn(fixed3 s, fixed3 d)
                        {
                            return 1.0 - (1.0 - d) / s;
                        }
             
                        float hardLight(float s, float d)
                        {
                            return (s < 0.5) ? 2.0*s*d : 1.0 - 2.0*(1.0 - s)*(1.0 - d);
                        }
             
                        fixed3 HardLight(fixed3 s, fixed3 d)
                        {
                            fixed3 c;
                            c.x = hardLight(s.x, d.x);
                            c.y = hardLight(s.y, d.y);
                            c.z = hardLight(s.z, d.z);
                            return c;
                        }
             
                        v2f vert (appdata v)
                        {
                            v2f o;
                            
                            o.vertex   = UnityObjectToClipPos(v.vertex);
                            o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                            
                            o.uv0 = TRANSFORM_TEX(v.uv0 , _MainTex  );
                            o.uv1 = TRANSFORM_TEX(v.uv1 , _DetailTex);
             
                            const float3 _worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
                            o.normal = normalize(_worldNormal);
                            
                            return o;
                        }
             
                        fixed4 frag (v2f i) : SV_Target
                        {
                            float4 _mainMap = tex2D(_MainTex, i.uv0);
             
                            float4 _baseColor =
                                _R1 * _mainMap.r +
                                _G1 * _mainMap.g +
                                _B1 * _mainMap.b +
                                _A1 * (1 - _mainMap.r - _mainMap.g - _mainMap.b);
             
                            float4 _detailMap = tex2D(_DetailTex, i.uv1);
             
                            float4 _finalCol = lerp(_baseColor, _R2, _detailMap.r);
                            _finalCol = lerp(_finalCol, _G2, _detailMap.g);
                            _finalCol = lerp(_finalCol, _B2, _detailMap.b);
             
                            float3 _weights = i.normal;
                            
                            _weights = abs(_weights);
                            _weights = _weights / (_weights.x + _weights.y + _weights.z);
             
                            _FrontCol *= _weights.z;
                            _SideCol  *= _weights.x;
                            _TopCol   *= _weights.y;
             
                            const fixed4 _triPlanarCol = _FrontCol + _SideCol + _TopCol;
             
                            _finalCol += _triPlanarCol;
             
                            float4 _gradient = lerp(_GradientCol1, _GradientCol2, i.worldPos.y);
             
                            _finalCol.xyz = ColorBurn(_finalCol.xyz, _gradient.xyz);
             
                            return _finalCol;
                            
                            #ifdef OVERLAY
                            _finalCol.xyz = Overlay(_finalCol.xyz, _triPlanarCol.xyz);
                            return _finalCol;
                            #elseif COLORBURN
                            _finalCol.xyz = ColorBurn(_finalCol.xyz, _triPlanarCol.xyz);
                            return _finalCol;
                            #elseif HARDLIGHT
                            _finalCol.xyz = HardLight(_finalCol.xyz, _triPlanarCol.xyz);
                            return _finalCol;
                            #endif
                         
                            return _finalCol + _triPlanarCol;
                        }
                        ENDCG
                    }
                }
            }