Advertisement

【Unity Shader入门精要学习笔记】基础纹理

阅读量:

概念

纹素即为纹理像素的简写形式
每个顶点都存储着纹理映射坐标的(u,v值),其中u代表横向位置而v代表纵向位置。
在Unity环境下,默认采用与OpenGL一致的纹理坐标系统...

单张纹理

在Unity Shader中,使用二维(2D)属性来表示纹理贴图,并以采样器变量类型来实现

复制代码
    	Properties {
    		_MainTex ("Main Tex", 2D) = "white" {}
    	}
    	// ...
    	CGPROGRAM
    	sampler2D _MainTex;
    	float4 _MainTex_ST;
    	// ...
    	v2f vert(a2v v) {
    	v2f o;
    	//o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    	o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    	return o;
    	}
    	ENDCG

在一个代码片段中定义了一个 named _MainTex 的二维贴图,在图形着色器程序中通过 _MainTex_ST 来定义贴图属性(其中 ST 用于表示缩放和平移参数)。具体来说,
_MainTex_ST.xy 获取缩放因子,
_MainTex_ST.zw 获取平移向量。
随后,在顶点处理单元(Vertex shader)中通过 TRANSFORM_TEX 运算符来计算贴图坐标。注释中的代码实现了这一功能,
而其具体实现则定义于 UnityCG.cginc 文件之中。
完整且完整的 shader 程序如下所示:

复制代码
    Shader "Unity Shaders Book/Chapter 7/Single 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
    	}
    
    	SubShader
    	 {		
    		Pass { 
    			Tags { "LightMode"="ForwardBase" }		
    			CGPROGRAM
    			#include "Lighting.cginc"
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			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 = mul(UNITY_MATRIX_MVP, 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;
    				// o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
    				return o;
    			}
    
    			fixed4 frag(v2f i) : SV_Target
    			{
    				fixed3 worldNormal = normalize(i.worldNormal);
    				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
    				// 计算纹理材质的反射系数, tex2D来计算纹理的纹素值
    				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. 纹理类型
    支持以下几种常见类型:TextureNormal map以及更高层次的Cubemap等。
    2.Wrap Mode
    它决定了当纹理坐标超出[0,1]范围时的处理方式。
  • Repeat模式下,在发生形变时如何进行缩放以选择合适的滤波器模式。
  • 支持依次为Point、Bilinear和Trilinear三种滤波器模式。
    3.Filter Mode
    它决定了在发生形变时如何进行缩放以选择合适的滤波器模式。
    4.多级渐远纹理(mipmapping)
    通过预先进行滤波处理生成一系列不同分辨率的图像。
    其中每一层都是基于上一层图像经过降采样处理得到的结果。
    5.Wrap Mode(续)
    它决定了当纹理坐标超出[0,1]范围时的具体实现方式:
  • 在这一范围内会直接使用对应的值;
  • 如果坐标超过边界,则会采用相应的截取策略;
  • 这种设置能够有效避免出现异常值的情况;

凹凸映射

凹凸映射采用一张纹理来调整模型表面的法线向量,在视觉效果上达到让模型呈现“凹凸不平”的效果。

在进行基于法线纹理的操作时需要特别注意:由于其存储范围为[-1,1]而像素值范围为[0,1]之间存在对应关系,则存在如下的映射函数:pixel = (normal + 1) / 2。因此,在着色器中采样后需进行反向解码得到原始方向:normal = pixel * 2 - 1

使用模型空间存储法线的优点:
1.实现起来简单,也很直观。
2.在纹理坐标的缝合处和尖锐的边角部分,可见的突变/裂缝较少,它能提供较为平滑的边界。这是因为模型空间下的法线纹理促成农户的是同一个坐标系下的法线信息,因此在边界出用过插值可以得到的法线可以平滑变换,而切线空间下的法线纹理中的法线信息是依靠纹理坐标的方向得到的结果,在边缘处或尖锐处部分会造成更多的可见的缝合现象。
使用切线空间存储法线的有点
1.自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅仅可用于创建它时的那个模型,换到其他模型上效果就错了。而切线空间下的法线纹理记录的是相对法线信息,即便是不同的模型,它也能得到一个合理的结果。
2.可进行UV动画。可以移动一个纹理的UV坐标来实现一个凹凸移动的效果。
3.可重用法线纹理。比如一个立方体,仅使用一张法线纹理就能用到6个面上。
4.可压缩 。因为切线空间下的法线纹理中法线的Z方向总是正方向,因此我们可以只存储XY方向,Z方向可以用XY来进行推导。而模型空间下法线纹理每个方向都有可能,因此必须存储3个值,不可压缩。

在切线空间下计算

基于切线空间的光照模型计算过程中,在片元着色器中通过纹理采样获得的是一个特定区域内的法线向量,并对该向量与该处切线空间中的观察方向和照明方向进行计算。因此,在顶点程序中必须将观察者方向和照明光向量从模型空间转换为切向(即视补片)坐标系以便后续运算。为了实现这一目标我们需要确定一个能够将这些三维坐标系之间的转换关系表示出来的方法——也就是找到一个能够将原始坐标系映射回目标坐标系的逆变换矩阵。由于这个逆矩阵实际上是从切向(视补片)坐标系转换回原始模型坐标的变换基础矩阵所以我们可以将其存储在一个4×4的标准齐次变换矩阵中以便后续的数据处理工作。此外需要注意的是由于大多数几何变换仅包含平移、旋转和平移反射等操作这些特殊的仿射变换其对应的逆矩阵可以通过其转置矩阵来快速求得而无需复杂的算法支持

默认情况下,默认法线纹理采用'bump'模式来进行细节表现设置而BumpScale参数则用于调节法线上细节部分的程度从而实现对表面凹凸性的模拟效果

复制代码
    Shader "Unity Shaders Book/Chapter 7/Normal Map In Tangent Space"
    {
    	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
    		_Specualr("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 "UnityCG.cginc"
    			#include "Lighting.cginc"
    			fixed4 _Color;
    			sampler2D _MainTex;
    			float4 _MainTex_ST;
    			sampler2D _BumpMap;
    			float4 _BumpMap_ST;
    			float _BumpScale;
    			fixed4 _Specular;
    			float _Gloss;
    
    			// tangent存储顶点的切线方向
    			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 = mul(UNITY_MATRIX_MVP, 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;
    				// 计算副切线方向
    				float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
    				// 3x3矩阵,将向量从模型空间转换到切线空间
    				float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
    				//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);
    
    				// 纹理采样
    				fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
    				fixed3 tangentNormal;
    				// 像素到法线的映射(法线到像素值公式的逆公式)
    				tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
    				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));				
    				// 用UnpackNormal(packedNormal); 来把法线的纹理设置成NormalMap
    				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"
    }

在v2f中定义uv为float4类型,在其xyzw字段中分别存放不同类型的纹理坐标信息:其中uv.xy字段用于存放MainTex纹理坐标的x和y分量;而uv.zw字段则用于存放_BumpMap纹理坐标的z和w分量。
将模型空间中的各向异性(即主切向量、副切向量以及法向量)按顺序排列形成一个矩阵,在该矩阵下完成从模型空间到切线空间的转换。
在确定辅助切向量的方向时,默认情况下会基于顶点位置属性v.position来进行计算。
随后,在片元着色器中首先通过tex2D函数获取法向量,并对其进行逆映射以获得实际的空间法向量。

在世界空间下计算

通过顶点着色器,在切线空间到世界坐标系的变换矩阵被用来完成该过程。随后,在片元着色器中将法线方向从切线空间转换为世界坐标系。该变换矩阵由顶点处的主切向量、副切向量以及法向量在全局坐标系中的表示来确定。

复制代码
    Shader "Unity Shaders Book/Chapter 7/Normal Map In World Space"
     {
    	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
    		_Specualr("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 "UnityCG.cginc"
    			#include "Lighting.cginc"
    			
    			fixed4 _Color;
    			sampler2D _MainTex;
    			float4 _MainTex_ST;
    			sampler2D _BumpMap;
    			float4 _BumpMap_ST;
    			float _BumpScale;
    			fixed4 _Specular;
    			float _Gloss;
    
    			// tangent存储顶点的切线方向
    			struct a2v
    			{
    				float4 vertex : POSITION;
    				float3 normal : NORMAL;
    				float4 tangent : TANGENT;
    				float4 texcoord : TEXCOORD0;
    			};
    
    			struct v2f
    			{
    				float4 pos : SV_POSITION;
    				float4 uv : TEXCOORD0;
    				float4 tangentToWorld0 : TEXCOORD1;
    				float4 tangentToWorld1 : TEXCOORD2;
    				float4 tangentToWorld2 : TEXCOORD3;
    			};
    
    			v2f vert(a2v v) 
    			{
    				v2f o;
    				o.pos = mul(UNITY_MATRIX_MVP, 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;
    
    				// 计算世界空间下的顶点的切线,副切线和法线方向
    				float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    				fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
    				fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    				fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
    
    				o.tangentToWorld0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    				o.tangentToWorld1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    				o.tangentToWorld2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
    				return o;
    			}
    
    			fixed4 frag(v2f i) : SV_Target
    			{				
    				float3 worldPos = float3(i.tangentToWorld0.w, i.tangentToWorld1.w, i.tangentToWorld2.w);
    				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
    
    				fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
    				bump.xy *= _BumpScale;
    				bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
    
    				// 将法线变换到世界坐标下,变换矩阵的每一行和法线进行点积进行变换后得到的
    				bump = normalize(half3(dot(i.tangentToWorld0.xyz, bump), dot(i.tangentToWorld1.xyz, bump), dot(i.tangentToWorld2.xyz, bump)));
    
    				fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
    				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
    				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
    
    				fixed3 halfDir = normalize(lightDir + viewDir);
    				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
    				return fixed4(ambient + diffuse + specular, 1.0);	
    			}
    			ENDCG
    		}
    	}
    	Fallback "Specular"
    }

tangentToWorld0、tangentToWorld1、tangentToWorld2表示从切线空间到世界空间的变换矩阵。充分地利用插值寄存器的能力,在世界空间中的顶点坐标xyz被各自放置到了三个插值寄存器对应的w分量中。

Unity中的法线纹理类型

在上述的 shader 代码中,我们应用了 UnpackNormal 内置函数以获取正确的法线方向。当我们在 shader 中集成包含法线映射的内置功能时,则必须将法线纹理标识为 Normal map 才能获得正确结果。如此操作,则能够实现 Unity 根据不同平台对纹理进行亚索(例如 DXT5nm 格式)的支持。

在这里插入图片描述

按照DXT5nm格式规定,在纹素编码中a通道(即w分量)对应的正是法线x坐标值,g通道则代表y坐标值,而通常情况下r和b两色信息会被舍弃.由于其z坐标值可以通过x与y坐标值得出,因此在实际应用中我们只需保留两个颜色通道即可实现三维法向信息的有效编码.这种数据压缩策略不仅能够显著减少存储空间的需求,还能保证所需的几何细节得以完整呈现.

渐变纹理

复制代码
    Shader "Unity Shaders Book/Chapter 7/Ramp Texture"
    {
    	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
    	}
    	
    	SubShader
    	{
    		Pass
    		{
    			Tags { "LightMode" = "ForwardBase" }
    			CGPROGRAM
    			#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;
    			};
    
    			v2f vert (a2v v)
    			{
    				v2f o;
    				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    				o.worldNormal = UnityObjectToWorldNormal(v.vertex);
    				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    				o.uv = v.texcoord.xy * _RampTex_ST.xy + _RampTex_ST.zw;
    				return o;
    			}
    			
    			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);
    			}
    			ENDCG
    		}
    	}
    	Fallback "Specular"
    }

在片元着色器架构中,我们采用了半兰伯特模型来处理法线向量与光照向量之间的关系。具体而言,在计算法线与光照向量点积的基础上执行一次缩放(乘以系数0.5)和平移(加上偏移值为0.5),从而得到halfLambert值并将其限制在[0,1]区间内。随后通过halfLambert值生成纹理坐标参数,并从一维渐变纹理_RampTex中提取样本进行插值运算。其中_RampTex是一维纹理,在其纵向延伸方向上颜色保持不变。

遮罩纹理

通过遮罩机制可以实现对需要保护区域的保护,并避免某些修改。 遮罩纹理通常情况下会经历以下流程:首先对遮罩纹理进行采样以获取纹素值;接着利用其中某一特定(或多个)通道与特定表面属性进行相乘运算。 当所选通道的值为零时,则能有效防止表面受到相应属性的影响。

复制代码
    Shader "Unity Shaders Book/Chapter 7/Mask Texture"
    {
    	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 ("Specular 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 "UnityCG.cginc"
    			#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 = mul(UNITY_MATRIX_MVP, v.vertex);
    				o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    				// 计算副切线
    				float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
    				// 3x3矩阵,将向量从模型空间转换到切线空间
    				float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
    				
    				// 把光照方向和视角方向从模型空间变换到切线空间
    				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, tangentLightDir));
    
    				fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
    
    				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"
    }

我们采用的遮罩纹理在每个纹素处的RGB值是相同的;然而这种做法存在一定的空间浪费;在现实游戏制作中,则会充分运用各个通道来编码不同的表面特性:例如将高光反射强度记录在R通道中、将边缘光照强度存放在G通道内、将高光反射指数信息存储于B通道中以及将自发光强度数值放入A通道。

全部评论 (0)

还没有任何评论哟~