Unity Shader 基础纹理

纹理最初的目的就是使用一张图片来控制模型的外观。使用纹理映射技术,我们可以把一张图片黏在模型表面,逐纹素地控制模型的颜色。

这里简单的总结一下单张纹理,凹凸映射,渐变纹理,遮罩纹理。

单张纹理

	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
	}

上面到吗声明了一个名为_MainTex的纹理,2D手机纹理属性的声明方式。字符串后面跟一个花括号作为她的初始值,“white”是内置纹理的名字,也就是一个全白的纹理,为了控制整体的色调还声明了一个_Color属性。

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

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

LightMode标签是Psaa标签的一种,它用于定义该Pass在unity的光照流水线中的角色。

使用#pragma指令来告诉unity,我们定义的顶点和片元着色器的名字。

为了使用unity内置的一些变量,如_LightColor0,还需要包含unity的内置文件Lighting.cginc

我们需要在CG代码片中声明和上述属性类型匹配的变量,以便和材质面板中的属性建立联系。

			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;
			};

在上边的代码中,我们首先在a2v结构体中使用TEXCOORD0语义声明了一个新的变量texcoord,这样unity就会将模型的第一组纹理坐标存储到该变量中。然后我们在v2f结构体中添加了用于存储纹理坐标的变量nv,以便在片元着色器中使用该坐标进行纹理采样。

			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;
			}

在顶点着色器中,我们使用纹理的属性值_MainTex_ST来对顶点纹理坐标进行转换,得到最后的纹理坐标。

	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);
			}

上面代码首先计算了世界空间下的法线方向和光照方向。然后使用CG的tex2D函数对纹理进行采样。他的第一个参数是需要被采样的纹理,第二个参数是一个float2类型的纹理坐标,她将返回计算得到纹素值。我们使用采样结果和颜色属性_Color的乘积来作为材质反射率albedo,并把他和环境光照相乘得到环境光部分。随后,我们使用albedo来计算漫反射光照的结果,并和环境光照,高光反射光照相加后返回。

完整代码

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"
}

凹凸映射

纹理的另一种常见的应用就是凹凸映射。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。

有两种主要的方法可以用来进行凹凸映射:一种是使用一张高度纹理来模拟表面位移,然后得到一个修改后的法线值,这种方法也被称为高度映射;另一种方法则是使用一张法线纹理来直接存储表面法线,这种方法又被称为法线映射。

总的来说,模型空间下的法线纹理更符合人类的直观认识,而且法线纹理本事也很直观,容易调整,因为不同的法线方向就代表了不同的颜色。

总的来说使用模型空间来存储法线的优点如下。

1.实现简单,更加直观。

2.纹理坐标的缝合处和尖端部分,可见的突变较少,即可以提供平滑的边界。

3.自由度高。

4.可以进行UV动画。

5.可以重用法线纹理。

6.可压缩。

    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
    }

对于法线纹理_BumpMap,我们使用“bump”作为她的默认值。“bump”是uniy内置的法线纹理,当没有提供任何法线纹理时,“bump”就对应模型自带的法线信息。_BumpScale则是用于控制凹凸程度的,当他为0的时候,法线纹理不会对光照产生任何影响。

  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;

            };

为了得到该纹理的属性(平铺和偏移参数),我们为_MainTex和_BumpMap定义了_MainTex_ST和_BumpMap_ST变量。

我们已经知道,切线空间是由顶点法线和切线构建出的一个坐标空间,因此我们需要得到顶点的切线信息。为此我们修改顶点着色器的输入结构体a2v

我们使用TANGENT语义来描述float4类型的tangent变量,以告诉unity把顶点的切线方向填充到tangent变量中。需要注意的是,和法线方向normal不同,tangent的类型是float4,而非float3,因为我们需要使用tangent.w分量;来决定切线空间中的第三个坐标轴——副切线的方向性。

我们需要在顶点着色器中计算切线空间先的光照和视角方向,因此我们在v2f结构体中添加了两个变量来存储变换后的光照和视角方向。

   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;

            }

由于我们使用两张纹理,因此需要存储两个纹理坐标。为此我们吧v2f中的uv变量类型定义为float4类型,其中xy分量存储了_MainTex的纹理坐标,而zw分量存储了_BumpMap的纹理坐标,然后我们把模型空间下切线方向,副切线方向和法线方向按照排列来得到从模型空间到切线空间的变换矩阵。Unity也提供了一个内置宏TANGENT_SPACE_ROTATION来帮助我们直接计算得到rotation变换矩阵,它的实现和上述代码完全一样。然后我们使用Unity的内置函数ObjSpaceLightDir和ObjSpaceViewDir来得到模型空间下的光照和视角方向,再利用变换矩阵rotation把他们从模型空间变换到切线空间中。

  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);

            }

上面的代码中,我们首先利用tex2D对法线纹理_BumpMap进行采样。法线纹理中存储的是把法线经过映射后得到的像素值,因此我们需要把他们反映射回来。如果我们没有再unity里把法线纹理的类型设置成Normal map就需要在代码中手动进行这个过程。我们首先把packedNormal的xy分量按之前提到的公示映射回法线方向,然后乘以_BumpScale来得到tangentNormal.xy分量。由于法线都是单位矢量,因此tangentNormal.z分量可以由tangentNormal.xy计算得到。由于我们使用的是切线空间下的法线纹理,因此可以保证法线方向的z分量为正。在unity中,为了方便unity对法线纹理的存储进行优化,我们通常会吧法线纹理的纹理类型标识成Normal map,unity会根据平台来选择不同的压缩方式。这时如果我们在使用上面的方法来计算就会得到错误的结果,因为此时的_BumpMap的rgb分量并不再是切线空间下法线的方向的xyz值了,这种情况下,我们可以使用unity的内置函数UnpackNormal来得到正确的法线方向。

完整代码

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"
}

渐变纹理

纹理可以储存任何表面属性。常见的一种用法就是使用渐变纹理来控制漫反射光关照的结果。在计算漫反射光照时,我们都是使用表面法线和光照方向的点击结果和材质的反射率相乘来得到表面反射的光照。但是有时候我们需要更加灵活地控制光照结果。

有一种基于冷到暖色调的着色技术,用来得到一种插画风格的渲染效果。使用这种技术,可以保证物体轮廓相比之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。现在很多卡通风格渲染都使用这种技术。

  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
    }

声明一个纹理属性来存储渐变纹理

	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;
            };

为渐变纹理_RanpTex定义了他的纹理属性变量_RanpTex_ST。

            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;
            }

顶点着色器使用内置的TRANSFORM_TEX宏来计算经过平铺和偏移后的纹理坐标。

 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);
            }

上面的代码中,我们使用半兰伯特模型,通过对法线方向和光照方向的点积做一次0.5倍的缩放以及一个0.5大小的偏移来计算半兰伯特部分halfLambert。这样,我们得到halfLamnert的范围被映射到[0,1之间]。之后,我们使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样。由于_RampTex实际就是一个一个维纹理,因此纹理坐标的u和v方向我们都使用balfLambert。然后,把从渐变纹理采样得到的颜色和材质_Color相乘,得到最终的漫反射颜色。剩下的代码就是计算高光反射和环境光,并把他们的结果进行相加。

遮罩纹理

简单来讲遮罩允许我们可以保护某些区域,使他们免于某些修改。

使用遮罩纹理的流程一般是:通过采样得到遮罩纹理的纹素值,然后使用其中某个通道的值来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响。总而言之,使用遮罩纹理可以让美术人员更加精准的控制模型表面的各种性质。

    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
    }

我们需要更多的变量来控制高光反射

_SpecularMask既是我们需要使用的高光反射遮罩纹理,_SprcularScale则是用于控制遮罩影响度的系数。

   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;

我们为主纹理_MainTex,法线纹理_BumpMap和遮罩纹理_SpecularMask定义了他们共同使用色纹理属性变量_MainTex_ST.意味着,在材质面板中修改主纹理的平铺系数和偏移系数会同时影响三个纹理的采样。使用这种方式可以让我们节省需要存储的纹理坐标数目,如果我们为每一个纹理都使用一个单独的属性变量TectureName_ST,那么随着使用纹理数目的增加,我们会迅速占满顶点着色器中可以使用的插值寄存器。而且很多时候,我们不需要对纹理进行平铺和位移操作,或者很多纹理可以使用同一个平铺和位移操作,此时我们就可以对这些纹理使用同一个变换后的纹理坐标进行采样。

  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);

            }

使用遮罩纹理的地方是片元着色器。我么使用它来控制模型表面的高光反射强度。

环境光和漫反射光照和之前使用的代码是一样的。在计算高光反射时,我们首先对遮罩纹理_SpecularMask进行采样。由于本书使用遮罩纹理中每个纹素的rgb分量其实都是一样的,表明了该点对应的高光反射强度,在这里我们选择使用r分量来计算遮码值。然后,我们用得到的遮码值和_SpecularScale相乘,一起来控制高光反射的强度。

需要说明的是,我们使用的这种遮罩纹理其实有很多空间被浪费了——它的rgb分量存储的都是同一个值。在实际的游戏制作中,我们往往会充分利用遮罩纹理中的每一个颜色通道来存储不同的表面属性。

完整代码

// 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"
    
}

猜你喜欢

转载自blog.csdn.net/f402455894/article/details/122835202