Unity Shader入门精要学习——基础纹理

1 单张纹理

Shader "Shader Learning/07 Basic Texture/01 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

	#pragma vertex vert
	#pragma fragment frag

	#include "Lighting.cginc"

	fixed4 _Color;
	sampler2D _MainTex;
	float4 _MainTex_ST;   //纹理类型还需加上float4变量,并且名称为  XXX_ST, S是scale,T是translation的缩写,代表缩放平移,_MainTex_ST.xy可以获取到缩放值,_MainTex_ST.zw可以获取到偏移值
	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(_Object2World, 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));

	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;  //Cg/HLSL的tex2D函数对纹理进行采样
	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"
}

2 凹凸映射

凹凸映射(bump mapping)的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。
主要有两种方法进行凹凸映射:高度映射(height mapping),使用高度纹理(height map)和法线映射(normal mapping),使用法线纹理(normal map)。

2.1 高度纹理

使用一张高度图来实现凹凸映射,颜色越浅表示该位置的表面越向外凸起,颜色越深越向里凹。

优点 清晰直观
可以从高度图中明确地知道一个模型表面的凹凸情况
 
缺点 更费性能
计算更加复杂,实时计算时不能直接得到表面法线,需要由像素的灰度值计算得到,需要消耗更多的性能
 

2.2 法线纹理

法线纹理存储的是表面的法线方向。
存储时,由于法线方向的分量范围在[-1, 1],而像素的分量范围在[0, 1],因此我们会做一个映射:
pixel= (normal+1)/2
所以在使用时我们需要反映射:
normal=pixel×2−1

存储的法线方向用哪个坐标空间?

使用的坐标空间 对应的纹理
模型空间 模型空间的法线纹理
object-space normal map
切线空间
tangent space
切线空间的法线纹理
tangent-space normal map


两种空间比较

比较项 模型空间 切线空间
实现难易
直观?
计算更少,生成也简单
非常直观。
甚至都不需要模型原始的法线和切线信息。
 
计算更复杂,生成也更复杂
如果要生成切线空间下的法线纹理,由于模型的切线一般是和UV方向相同,因此想要得到效果比较好的法线映射就要求纹理映射也是连续的。

边界平滑

可以提供平滑的边界
模型空间下的法线纹理存储的是同一坐标系下的法线信息,因此在边界处通过怕插值得到的法线可以平滑变换。
 
可能会有缝合迹象
切线空间下的法线纹理中的法线信息是依靠纹理坐标的方向得到的结果,可能会在边缘处或尖锐的部分造成更多可见的缝合迹象
 
自由度
 
自由度低,仅可用于创建的模型
模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型,而应用到其他模型上效果就完全错误了。
自由度高,可以用到别的模型上
切线空间下的法线纹理记录的是相对法线信息,即便把纹理应用到一个完全不同的网格上,也可以得到一个合理的结果
 
UV动画 无法进行UV动画
原因同上
 
可以进行UV动画
我们可以移动纹理的UV坐标来实现一个凹凸移动的效果,这种UV动画在水或者火山熔岩这种类型的物体上会经常用到
 
法线纹理重用
 

不可重用法线纹理

原因同上

可以重用法线纹理
比如,一个砖块,我们仅使用一张法线纹理就可以用到所有的6个面上
 
压缩 不可压缩
模型空间下的法线纹理由于每个方向都是可能的,必须存储3个方向的值,不可压缩
 
可压缩
由于切线空间下的法线纹理中法线的Z方向总是正方向,因此我们可以仅存储XY方向,而推导得到Z方向。
 

 切线空间在很多情况下都优于模型空间,因此一般使用切线空间下的法线纹理。


计算时使用哪种空间进行统一计算?
由于法线纹理存储的法线是切线空间下的方向,因此我们通常有两种选择:
    1. 在切线空间下进行光照计算
    需要把光照方向、视角方向变换到切线空间下
    2. 在世界空间下进行光照计算
    需要把采样得到的法线方向变换到世界空间下,再和世界空间下的光照方向和视角方向进行计算

两种方法比较

比较项 切线空间 世界空间
效率 较优
我们可以在顶点着色器中就完成对光照方向和视角方向的变换
较低
由于要先对法线纹理进行采样,所以变换过程必须在片元着色器中实现,这意味着我们需要在片元着色器中进行一次矩阵操作
通用性 较低 较优
有时我们需要在世界空间下进行一些计算
例如,在使用Cubemap进行环境映射时,我们需要使用世界空间下的反射方向对Cubemap进行采样,如果同时需要进行法线映射,我们就需要把法线方向变换到世界空间下。
 

Shader "Shader Learning/07 Basic Texture/02 Normal Map Tangent Space"
{
	Properties
	{
		_Color("Color Tint", Color)=(1, 1, 1, 1)
		_MainTex("Main Tex", 2D) = "white"{}
		_BumpMap("Normal Map", 2D) = "bump"{}   //bump是Unity内置的法线纹理,对应了模型自带的法线信息,当没有定义凹凸纹理的时候就使用模型自带的法线信息
		_BumpScale("Bump Scale", Float) = 1.0   //控制凹凸程度,为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;  //这里直接使用float4,用uv.xy存储基础纹理的坐标,用uv.zw存储法线纹理的坐标
		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;
	//或者使用以下方法
	//o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
	//o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

	//计算从模型空间到切线空间的变换矩阵
	//float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;  //计算副切线,副切线垂直于法线与切线构成的面,叉乘可以得到这个结果,但是垂直法线和切线的方向有两个,用v.tangent.w决定选择其中哪一个方向
	//float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);  //计算变换矩阵,把模型空间下切线方向(x)、副切线方向(y)和法线方向(z)按行排列来得到从模型空间到切线空间的变换矩阵roation
	//或者直接使用内置的宏
	TANGENT_SPACE_ROTATION;   //如果使用这个宏,则顶点着色器的输入数据变量a2v的名称必须为v,因为宏内部就是使用了这个名称

	//将光照方向和视角方向从模型空间转换到切线空间中
	o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;  //先获得模型空间下的光照方向,再使用变换矩阵rotation变换到切线空间
	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;
	//如果法线纹理没有被设置为“Normal map”,则
	//tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;  //先将法线的纹理值反映射回来,根据normal = pixel * 2 - 1,然后乘以_BumpScale控制凹凸程度,只需要控制xy轴,这是因为z轴就是模型自带的法线方向,所以如果xy为0了,就是原法线,如果xy值变化越大,则越偏离原法线,凹凸效果就越强
	//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));    //由于法线都是单位矢量,因此z分量可以由xy计算得到。由于使用的是切线空间下的法线纹理,因此可以保证法线方向的z分量为正
	//如果法线纹理被设置为NormalMap,则需使用内置的方法
	tangentNormal = UnpackNormal(packedNormal);
	tangentNormal.xy *= _BumpScale;  //乘以_BumpScale控制凹凸程度,只需要控制xy轴,这是因为z轴就是模型自带的法线方向,所以如果xy为0了,就是原法线,如果xy值变化越大,则越偏离原法线,凹凸效果就越强
	tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;  //使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo。这里直接用的是uv,但其实使用的是uv.xy,应该是默认会取前两个分量,如果需要用到的是后面的分量,那就必须定义出来,如前面的uv.zw
	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"   //为了避免有错而没发现,暂时先注释
}
Shader "Shader Learning/07 Basic Texture/02 Normal Map World Space"
{
	Properties
	{
		_Color("Color Tint", Color)=(1, 1, 1, 1)
		_MainTex("Main Tex", 2D) = "white"{}
		_BumpMap("Normal Map", 2D) = "bump"{}   //bump是Unity内置的法线纹理,对应了模型自带的法线信息,当没有定义凹凸纹理的时候就使用模型自带的法线信息
		_BumpScale("Bump Scale", Float) = 1.0   //控制凹凸程度,为0时,不会对光照产生任何影响
		_Specular("Specular", Color) = (1, 1, 1, 1)
		_Gloss("Gloss", Range(8.0, 256)) = 20   //Gloss是财智的光泽度,也被称为反光度,用于控制高光区域的“亮点”有多宽,Gloss越大,亮点就越小
	}

	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;  //这里直接使用float4,用uv.xy存储基础纹理的坐标,用uv.zw存储法线纹理的坐标

	//一个插值寄存器最多只能存储float4大小的变量,对于矩阵这样的变量,我们可以把它们按行拆成多个变量再进行存储
	//以下的TtoW0,TtoW1, TtoW2就依次存储了从切线空间到世界空间的变换矩阵的每一行。
	//实际上对方向矢量的变换只需要使用3x3大小的矩阵,即每一行只需使用float3类型的变量即可
	//但为了充分利用插值寄存器的存储空间,我们把世界空间下的顶点位置存储在这些变量的w分量中。
	float4 TtoW0 : TEXCOORD1;
	float4 TtoW1 : TEXCOORD2;
	float4 TtoW2 : 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;
	//或者使用以下方法
	//o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
	//o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);

	//计算世界空间下的顶点坐标,法线、切线、副切线方向
	float3 worldPos = mul(_Object2World, v.vertex).xyz;
	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
	fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

	//我们把顶点切线、副切线和法线方向按列摆放得到从切线空间到世界空间的变换矩阵,我们把该矩阵的每一行分别存储在TtoW0,TtoW1,TtoW2
	//并把世界空间下的顶点位置的xyz分量分别存储在了这些变量的w分量中
	o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
	o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
	o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

	return o;
	};

	fixed4 frag(v2f i) : SV_Target
	{
		//计算世界空间下的顶点坐标,光照方向,视角方向
		float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);  //从w分量中构建世界空间下的坐标
		fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));   //UnityWorldSpaceLightDir的参数是世界空间下的,WorldSpaceLightDir的参数是模型空间下的(内部调用了UnityWorldSpaceLightDir方法),获得的结果一致,所以最好先计算得到WorldPos再使用UnityWorldSpaceLightDir减少计算量
		fixed3 worldViewDir = 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)));
	//最后使用TtoW0,TtoW1,TtoW2存储的变换矩阵把法线变换到世界空间下
	//通过使用点乘操作来实现矩阵的每一行和法线相乘来得到的
	bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

	//计算标准光照模型中的各个部分
	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;  //使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo。这里直接用的是uv,但其实使用的是uv.xy,应该是默认会取前两个分量,如果需要用到的是后面的分量,那就必须定义出来,如前面的uv.zw
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;  //将反射率和环境光照相乘得到环境光部分
	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, worldLightDir)); //计算漫反射光照
	//计算高光部分
	fixed3 halfDir = normalize(worldLightDir + worldViewDir);  
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);

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

	ENDCG
	}
	}

	//FallBack "Specular"   //为了避免有错而没发现,暂时先注释
}


3 渐变纹理

Shader "Shader Learning/07 Basic Texture/03 Ramp Texture Half Lambert"
{
	Properties
	{
		_Color("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
		_RampTex("Ramp Tex", 2D) = "white"{}   //存储渐变纹理
		_Specular("Specular", Color) = (1.0, 1.0, 1.0, 1.0)
		_Gloss("Gloss", Range(8.0, 256)) = 20    //Gloss是材质的光泽度,也被称为反光度,用于控制高光区域的“亮点”有多宽,Gloss越大,亮点就越小
	}

	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 worldPos : TEXCOORD0;
		float3 worldNormal : TEXCOORD1;
		float2 uv : TEXCOOR2;
	};

	v2f vert(a2v v)
	{
		v2f o;
		
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

	o.worldPos = mul(_Object2World, v.vertex).xyz;

	o.worldNormal = UnityObjectToWorldNormal(v.normal);

	o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);  //没有被使用到,可能不需要

	return o;
	}

	fixed4 frag(v2f i) : SV_Target
	{				
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;   //与之前的 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;  不同,但这就是不同的经验模型

	//计算漫反射部分,这里使用半兰伯特经验模型
	fixed3 worldNormalDir = normalize(i.worldNormal);
	fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
	fixed halfLambert = 0.5 * dot(worldNormalDir, worldLightDir) + 0.5;  //半兰伯特经验模型
	fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
	fixed3 diffuse = _LightColor0.rgb * diffuseColor;

	//计算高光部分
	fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
	fixed3 halfDir = normalize(worldLightDir + worldViewDir);
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormalDir, halfDir)), _Gloss);

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

	ENDCG
	}
	}
	//FallBack "Specular"   //为了避免有错而没发现,暂时先注释
}

4 遮罩纹理 
 

Shader "Shader Learning/07 Basic Texture/04 Mask Texture Tangent Space"
{
	Properties
	{
		_Color("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex("Main Tex", 2D) = "white"{}
		_BumpTex("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 "Lighting.cginc"

	fixed4 _Color;
	sampler2D _MainTex;
	float4 _MainTex_ST;  //在这里我们只定义一个纹理的缩放和平移系数变量,三个纹理共同使用这个变量,节省需要存储的纹理坐标数目,不然插值寄存器不够用
	sampler2D _BumpTex;
	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 tangentLightDir : TEXCOORD1;
		float3 tangentViewDir : TEXCOORD2;
	};

	v2f vert(a2v v)
	{
		v2f o;

	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

	o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
	//或者使用
	//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
	 
	TANGENT_SPACE_ROTATION;

	o.tangentLightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
	o.tangentViewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

	return o;
	}

	fixed4 frag(v2f i) : SV_Target
	{
		fixed3 tangentLightDir = normalize(i.tangentLightDir);  //即使在顶点着色器中归一化过,在片元着色器中一般也需要重新归一化,从vert到frag会经过插值,到达frag里面的逐像素方向就不再能保证是归一化的。
		fixed3 tangentViewDir = normalize(i.tangentViewDir);

	fixed3 tangentNormal = UnpackNormal(tex2D(_BumpTex, 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;  //使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo。

	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;  //计算遮罩的值,选择使用r分量来计算掩码值
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask;
	
	return fixed4(ambient + diffuse + specular, 1.0);
	}

	ENDCG
	}
	}

	//FallBack "Specular"  //为了避免有错而没发现,暂时先注释
}

书中并没有写这个shader,是自己根据前面的改的,但是效果似乎是一样的

Shader "Shader Learning/07 Basic Texture/04 Mask Texture World Space"
{
	Properties
	{
		_Color("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex("Main Tex", 2D) = "white"{}
		_BumpTex("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 "Lighting.cginc"

	fixed4 _Color;
	sampler2D _MainTex;
	float4 _MainTex_ST;  //在这里我们只定义一个纹理的缩放和平移系数变量,三个纹理共同使用这个变量,节省需要存储的纹理坐标数目,不然插值寄存器不够用
	sampler2D _BumpTex;
	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;
		float4 TtoW0 : TEXCOORD1;
		float4 TtoW1 : TEXCOORD2;
		float4 TtoW2 : TEXCOORD3;
	};

	v2f vert(a2v v)
	{
		v2f o;

	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

	o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
	//或者使用
	//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
	 
	float4 worldPos = mul(_Object2World, v.vertex);

	fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);

	fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);  //

	fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

	//将从切线空间到世界空间的变换矩阵存储,以及世界空间下的顶点坐标
	o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
	o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
	o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

	return o;
	}

	fixed4 frag(v2f i) : SV_Target
	{
		float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
		fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));  //即使在顶点着色器中归一化过,在片元着色器中一般也需要重新归一化,从vert到frag会经过插值,到达frag里面的逐像素方向就不再能保证是归一化的。
		fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

	fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv));
	bump.xy *= _BumpScale;
	bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
	//最后使用TtoW0,TtoW1,TtoW2存储的变换矩阵把法线变换到世界空间下
	//通过使用点乘操作来实现矩阵的每一行和法线相乘来得到的
	bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

	fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;  //使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo。

	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;  //将反射率和环境光照相乘得到环境光部分

	fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, worldLightDir));  //

	//计算高光部分
	fixed3 halfDir = normalize(worldLightDir + worldViewDir);
	fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;  //计算遮罩的值
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss) * specularMask;
	
	return fixed4(ambient + diffuse + specular, 1.0);
	}

	ENDCG
	}
	}

	//FallBack "Specular"  //为了避免有错而没发现,暂时先注释
}

猜你喜欢

转载自blog.csdn.net/ithot/article/details/125952131