Unity Shader base texture

The original purpose of textures is to use an image to control the appearance of a model. Using texture mapping technology, we can stick an image on the surface of the model and control the color of the model on a texel-by-texel basis.

Here is a brief summary of single texture, bump mapping, gradient texture, mask texture.

Leaflet texture

	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}

Does the above declare a texture named _MainTex, the way to declare the 2D mobile phone texture properties. The string is followed by a curly brace as her initial value, "white" is the name of the built-in texture, which is an all-white texture, and a _Color property is also declared to control the overall color tone.

	Tags { "LightMode"="ForwardBase" }
		
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Specular;
			float _Gloss;

The LightMode tag is a type of Psaa tag, which is used to define the role of the Pass in Unity's lighting pipeline.

Use the #pragma directive to tell Unity the names of the vertex and fragment shaders we defined.

In order to use some variables built into unity, such as _LightColor0, you also need to include unity's built-in file Lighting.cginc

We need to declare variables in the CG code that match the above property types in order to establish a connection with the properties in the material panel.

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

In the code above, we first declare a new variable texcoord in the a2v structure using TEXCOORD0 semantics, so that unity will store the first set of texture coordinates of the model into this variable. Then we added a variable nv to the v2f struct to store the texture coordinates so that we can use that coordinate for texture sampling in the fragment shader.

			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

				
				return o;
			}

In the vertex shader, we use the attribute value _MainTex_ST of the texture to transform the vertex texture coordinates to get the final texture coordinates.

	fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				
		
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				return fixed4(ambient + diffuse + specular, 1.0);
			}

The above code first calculates the normal direction and lighting direction in world space. The texture is then sampled using CG's tex2D function. Its first parameter is the texture to be sampled, and the second parameter is a texture coordinate of type float2, which will return the calculated texel value. We use the product of the sampling result and the color attribute _Color as the material reflectance albedo, and multiply it with the ambient light to get the ambient light part. Then, we use albedo to calculate the diffuse lighting result, add it to the ambient lighting, and the specular lighting and return it.

full code

Shader "UnityShaders/SingleTexture" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
	}
	SubShader {		
		Pass { 
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			
			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Specular;
			float _Gloss;
			
			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

				
				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
			
				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
				
				return fixed4(ambient + diffuse + specular, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

bump mapping

Another common application of textures is bump mapping. The purpose of bump mapping is to use a texture to modify the normals of the model's surface in order to provide more detail to the model.

There are two main methods for bump mapping: one is to use a height texture to simulate surface displacement, and then get a modified normal value, this method is also called height mapping; another method It uses a normal texture to directly store the surface normal, which is also called normal mapping.

In general, the normal texture in the model space is more in line with the intuitive understanding of human beings, and the normal texture is also very intuitive and easy to adjust, because different normal directions represent different colors.

In general, the advantages of using model space to store normals are as follows.

1. The implementation is simple and more intuitive.

2. The seams and tips of the texture coordinates have fewer visible mutations, which can provide smooth boundaries.

3. High degree of freedom.

4. Can do UV animation.

5. Normal textures can be reused.

6. Compressible.

    Properties
    {
        _Color ("Color Tint",Color)=(1,1,1,1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _BumpMap("Normal Map",2D)="bump"{}
        _BumpScale("Bump Scale" ,Float)=1.0
        _Specular("Specular",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8.0,256))=20
    }

For the normal texture _BumpMap, we use "bump" as her default value. "bump" is the built-in normal texture of uniy. When no normal texture is provided, "bump" corresponds to the normal information that comes with the model. _BumpScale is used to control the degree of bump. When it is 0, the normal texture will not have any effect on the lighting.

  Tags{"LightMode"="ForwardBase"}
        
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"


            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;


            struct a2v{
                float4 vertex :POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
                float4 texcoord:TEXCOORD0;

            };

            struct v2f{
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;

            };

To get the properties of this texture (tile and offset parameters), we define _MainTex_ST and _BumpMap_ST variables for _MainTex and _BumpMap.

We already know that the tangent space is a coordinate space constructed by the vertex normal and tangent, so we need to get the tangent information of the vertex. For this we modify the input struct a2v of the vertex shader

We use TANGENT semantics to describe the tangent variable of type float4 to tell Unity to fill the tangent variable with the tangent direction of the vertex. It should be noted that, unlike the normal direction normal, the type of tangent is float4, not float3, because we need to use the tangent.w component; to determine the third coordinate axis in the tangent space - the directionality of the subtangent.

We need to calculate the lighting and view direction in tangent space in the vertex shader, so we add two variables to the v2f struct to store the transformed lighting and view direction.

   v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                o.uv.xy=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                o.uv.zw=v.texcoord.xy*_BumpMap_ST.xy+_BumpMap_ST.zw;

                TANGENT_SPACE_ROTATION;
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;

                o.viewDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;

                return o;

            }

Since we are using two textures, we need to store two texture coordinates. To this end, let's define the uv variable type in v2f as float4 type, where the xy component stores the texture coordinates of _MainTex, and the zw component stores the texture coordinates of _BumpMap, and then we put the lower tangent direction of the model space, the subtangent direction and The normal directions are arranged in order to get the transformation matrix from model space to tangent space. Unity also provides a built-in macro TANGENT_SPACE_ROTATION to help us directly calculate the rotation transformation matrix. Its implementation is exactly the same as the above code. Then we use Unity's built-in functions ObjSpaceLightDir and ObjSpaceViewDir to get the lighting and view direction in model space, and then use the transformation matrix rotation to transform them from model space to tangent space.

  fixed4 frag(v2f i):SV_Target{
                fixed3 tangentLightDir=normalize(i.lightDir);
                fixed3 tangentViewDir=normalize(i.viewDir);

                fixed4 packedNormal=tex2D(_BumpMap,i.uv.zw);
                fixed3 tangentNormal;
                tangentNormal=UnpackNormal(packedNormal);

                tangentNormal.xy*=_BumpScale;
                tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;

                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;

                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));

                fixed3 halfDir=normalize(tangentLightDir+tangentViewDir);

                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss);

                return fixed4 (ambient+diffuse+specular,1.0);

            }

In the above code, we first use tex2D to sample the normal texture _BumpMap. The normal texture stores the pixel values ​​obtained by mapping the normal, so we need to demap them back. If we don't set the normal texture type to Normal map in Unity, we need to do this process manually in the code. We first map the xy component of packedNormal back to the normal direction as mentioned earlier, and then multiply by _BumpScale to get the tangentNormal.xy component. Since normals are all unit vectors, the tangentNormal.z component can be calculated by tangentNormal.xy. Since we are using a normal texture in tangent space, the z component of the normal direction is guaranteed to be positive. In Unity, in order to facilitate Unity to optimize the storage of normal textures, we usually mark the texture type of the normal texture as Normal map, and Unity will choose different compression methods according to the platform. At this time, if we use the above method to calculate, we will get wrong results, because the rgb component of _BumpMap is no longer the xyz value of the direction of the normal in the tangent space. In this case, we can Use Unity's built-in function UnpackNormal to get the correct normal direction.

full code

Shader "Unlit/NormalMapTangentSpace"
{
    Properties
    {
        _Color ("Color Tint",Color)=(1,1,1,1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _BumpMap("Normal Map",2D)="bump"{}
        _BumpScale("Bump Scale" ,Float)=1.0
        _Specular("Specular",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8.0,256))=20
    }
    SubShader
    {

        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"


            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;


            struct a2v{
                float4 vertex :POSITION;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
                float4 texcoord:TEXCOORD0;

            };

            struct v2f{
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;

            };

            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                o.uv.xy=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                o.uv.zw=v.texcoord.xy*_BumpMap_ST.xy+_BumpMap_ST.zw;

                TANGENT_SPACE_ROTATION;
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;

                o.viewDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;

                return o;

            }

            fixed4 frag(v2f i):SV_Target{
                fixed3 tangentLightDir=normalize(i.lightDir);
                fixed3 tangentViewDir=normalize(i.viewDir);

                fixed4 packedNormal=tex2D(_BumpMap,i.uv.zw);
                fixed3 tangentNormal;
                tangentNormal=UnpackNormal(packedNormal);

                tangentNormal.xy*=_BumpScale;
                tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;

                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;

                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));

                fixed3 halfDir=normalize(tangentLightDir+tangentViewDir);

                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss);

                return fixed4 (ambient+diffuse+specular,1.0);

            }
            
            ENDCG
        }
    }
    Fallback "Specular"
}

Gradient texture

Textures can store any surface property. A common use is to use gradient textures to control the result of diffuse lighting. When calculating diffuse lighting, we use the click result of the surface normal and light direction to multiply the reflectivity of the material to get the light reflected by the surface. But sometimes we need more flexible control over lighting results.

There is a shading technique based on cool to warm tones to get an illustration-style rendering. Using this technique ensures that object outlines are more pronounced than the traditional diffuse lighting previously used, and it provides a variety of tonal variations. A lot of cartoon style renderings now use this technique.

  Properties
    {
     	_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_RampTex ("Ramp Tex", 2D) = "white" {}
		_Specular ("Specular", Color) = (1, 1, 1, 1)
		_Gloss ("Gloss", Range(8.0, 256)) = 20
    }

Declare a texture property to store the gradient texture

	Tags { "LightMode"="ForwardBase" }

            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"
            
            fixed4 _Color;
            sampler2D _RampTex;
            float4 _RampTex_ST;
            fixed4 _Specular;
            float _Gloss;

            struct a2v{
                float4 vertex :POSITION;
                float3 normal:NORMAL;
                float4 texcoord:TEXCOORD0;
            };
            struct v2f{
                float4 pos:SV_POSITION;
                float3 worldNormal:TEXCOORD0;
                float3 worldPos:TEXCOORD1;
                float2 uv:TEXCOORD2;
            };

His texture attribute variable _RanpTex_ST is defined for the gradient texture _RanpTex.

            v2f vert(a2v v){
             	v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
				
				return o;
            }

The vertex shader uses the built-in TRANSFORM_TEX macro to compute tiled and offset texture coordinates.

 fixed4 frag(v2f i):SV_Target{
                fixed3 worldNormal=normalize(i.worldNormal);
                fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed halfLambert=0.5*dot(worldNormal,worldLightDir)+0.5;
                fixed3 diffuseColor=tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb*_Color.rgb;
                fixed3 diffuse=_LightColor0.rgb*diffuseColor;
                fixed3 viewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir=normalize(worldLightDir+viewDir);
                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot( worldNormal,halfDir)),_Gloss);
                return fixed4 (ambient+diffuse+specular,1.0);
            }

In the above code, we use the half-Lambert model to calculate the half-Lambert part by scaling the dot product of the normal direction and the light direction by a factor of 0.5 and an offset of 0.5. In this way, we get the range of halfLamnert is mapped to [0,1]. After that, we use halfLambert to construct a texture coordinate and use this texture coordinate to sample the ramp texture _RampTex. Since _RampTex is actually a one-dimensional texture, we use balfLambert for the u and v directions of the texture coordinates. Then, multiply the color sampled from the gradient texture by the material_Color to get the final diffuse color. The rest of the code is to calculate the specular and ambient light and add their results.

mask texture

Simply put, masks allow us to protect certain areas from certain modifications.

The process of using a mask texture is generally: get the texel value of the mask texture by sampling, and then use the value of one of the channels to multiply it with a certain surface attribute, so that when the value of the channel is 0, you can Protects the surface from this property. All in all, using a mask texture allows the artist to more precisely control the various properties of the model's surface.

    Properties
    {
        _Color("Color Tint",Color)=(1,1,1,1)
        _MainTex("Main Tex",2D)="white"{}
        _BumpMap("Normal Map",2D)="bump"{}
        _BumpScale("Bump Scale",Float)=1.0
        _SpecularMask("Specular Mask",2D)="white"{}
        _SpecularScale("Specilar Scale",Float)=1.0
        _Specular("Specular",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8.0,256))=20
    }

We need more variables to control specular reflections

_SpecularMask is the specular mask texture we need to use, and _SprcularScale is the coefficient used to control the influence of the mask.

   Tags{ "LightMode"="ForwardBase"}
     
            #pragma vertex vert
            #pragma fragment frag 
            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float _BumpScale;
            sampler2D _SpecularMask;
            float _SpecularScale;
            fixed4 _Specular;
            float _Gloss;

We define the main texture _MainTex, normal texture _BumpMap and mask texture _SpecularMask to use the color texture attribute variable _MainTex_ST. It means that modifying the tiling coefficient and offset coefficient of the main texture in the material panel will simultaneously Affects the sampling of the three textures. Using this method allows us to save the number of texture coordinates that need to be stored. If we use a separate attribute variable TectureName_ST for each texture, then as the number of textures used increases, we will quickly fill up the vertex shader that can be used. the interpolation register. And in many cases, we don't need to perform tiling and displacement operations on textures, or many textures can use the same tiling and displacement operations. At this time, we can use the same transformed texture coordinates to sample these textures.

  struct a2v{
                float4 vertex :POSITION;
                float3 normal:NORMAL;
                float4 tangent :TANGENT;
                float4 texcoord:TEXCOORD0;
            };

            struct v2f{
                float4 pos:SV_POSITION;
                float2 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;
            };
            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                o.uv.xy=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                TANGENT_SPACE_ROTATION;
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir=mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;

                return o;

            }

In the vertex shader, we transform the light direction and view direction into coordinate space, transforming them from model space to tangent space, in order to perform lighting operations with normals in the fragment shader.

            fixed4 frag(v2f i):SV_Target{
                fixed3 tangentLightDir=normalize(i.lightDir);
                fixed3 tangentViewDir=normalize(i.viewDir);
                fixed3 tangentNormal=UnpackNormal(tex2D(_BumpMap,i.uv));

                tangentNormal.xy*=_BumpScale;
                tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentViewDir));

                fixed3 halfDir=normalize(tangentLightDir+tangentLightDir);
                fixed specularMask=tex2D(_SpecularMask,i.uv).r*_SpecularScale;

                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss)*specularMask;

                return fixed4(ambient+diffuse+specular,1.0);

            }

Where mask textures are used is in the fragment shader. We use this to control the intensity of specular reflections on the surface of the model.

Ambient and diffuse lighting are the same as the code used before. When computing specular reflections, we first sample the mask texture _SpecularMask. Since the rgb component of each texel in the mask texture used in this book is actually the same, indicating the corresponding specular reflection intensity at that point, here we choose to use the r component to calculate the mask value. Then, we multiply the resulting mask value by _SpecularScale to control the intensity of the specular reflection.

It should be noted that the mask texture we are using actually has a lot of wasted space - its rgb components all store the same value. In actual game production, we often make full use of each color channel in the mask texture to store different surface properties.

full code

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/MaskTexture"
{
    Properties
    {
        _Color("Color Tint",Color)=(1,1,1,1)
        _MainTex("Main Tex",2D)="white"{}
        _BumpMap("Normal Map",2D)="bump"{}
        _BumpScale("Bump Scale",Float)=1.0
        _SpecularMask("Specular Mask",2D)="white"{}
        _SpecularScale("Specilar Scale",Float)=1.0
        _Specular("Specular",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8.0,256))=20
    }
    SubShader
    {
        Pass{
            Tags{ "LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag 
            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float _BumpScale;
            sampler2D _SpecularMask;
            float _SpecularScale;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v{
                float4 vertex :POSITION;
                float3 normal:NORMAL;
                float4 tangent :TANGENT;
                float4 texcoord:TEXCOORD0;
            };

            struct v2f{
                float4 pos:SV_POSITION;
                float2 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;
                float3 viewDir:TEXCOORD2;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                o.uv.xy=v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                TANGENT_SPACE_ROTATION;
                o.lightDir=mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir=mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;

                return o;

            }
            fixed4 frag(v2f i):SV_Target{
                fixed3 tangentLightDir=normalize(i.lightDir);
                fixed3 tangentViewDir=normalize(i.viewDir);
                fixed3 tangentNormal=UnpackNormal(tex2D(_BumpMap,i.uv));

                tangentNormal.xy*=_BumpScale;
                tangentNormal.z=sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
                fixed3 albedo=tex2D(_MainTex,i.uv).rgb*_Color.rgb;
                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse=_LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentViewDir));

                fixed3 halfDir=normalize(tangentLightDir+tangentLightDir);
                fixed specularMask=tex2D(_SpecularMask,i.uv).r*_SpecularScale;

                fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss)*specularMask;

                return fixed4(ambient+diffuse+specular,1.0);

            }

            ENDCG
        }

        
    }

    Fallback "Specular"
    
}

Guess you like

Origin blog.csdn.net/f402455894/article/details/122835202