Unity_Shader学习(五)基础纹理Shader

在之前的学习中,我们学会了怎么样来实现基础光照Shader,但是在我们的应用中,很少只使用一个颜色,我们更多的使用纹理,使材质表现的信息更加的丰富。所以我们这一节来看一下使用贴图的基础纹理Shader是怎么实现的。

 

1.使用Blinn-Phong光照模型的单张纹理Shader:

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

			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Gloss;
			fixed4 _Specular;

			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 = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
				o.worldPos = worldPos.xyz;
				o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				return o;
			}

			fixed4 frag(v2f i):SV_TARGET{
				float3 worldNormal = i.worldNormal;
				float3 worldPos = i.worldPos;
				float3 worldLight = normalize(UnityWorldSpaceLightDir(worldPos));

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

				fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldNormal, worldLight));

				float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
				float3 h = normalize(viewDir + worldLight);

				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(h,worldNormal)),_Gloss);

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo;

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

			ENDCG
		}
	}
	FallBack "Diffuse"
}

在属性声明中,我们添加了一个_MainTexture变量,它是2D类型的,在Inspecutor面板中,它可以选择一张贴图。

在a2v中我们声明了texcoord,通过它与_MainTexture中的值来计算v2f中的uv纹理坐标值,通过这个uv坐标值获取纹理信息。

我们不仅声明了sampler2D类型的_MainTexture,还声明了一个float4类型的_MainTexture_ST,ST是scale缩放与translation平移的缩写,可以通过它获得纹理的缩放和平移值,其中_MainTexture_ST.xy存储的是缩放值,_MainTexture_ST.zw存储的是偏移值。

在vert函数中,我们计算uv纹理坐标值并通过v2f传递给frag函数,在frag函数中,通过tex2D(_MainTexture, uv)获得纹理信息。计算得albedo=Color * texInfo,然后计算diffuse的等式中的材质属性用albedo来计算,环境光计算也要用ambient=UNITY_AMBIENT * albedo,高光计算不使用albedo。

这样我们就完成了最简单的纹理Shader:

Unity_Shader学习(五)基础纹理Shader_第1张图片

 

2.应用法线纹理的Shader:

法线纹理用于存储表面法线信息,一般一张法线纹理对应着一张普通纹理。我们也可以这样理解,法线纹理中保存的法线 就是用于替换原来  光照模型计算过程中  使用的worldNormal(及其他空间的normal)。由于法线方向分量范围为[-1,1],而像素分量范围为[0,1],所以我们要做一个映射来在纹理中存储法线分量:

pixel = (normal+1)/ 2

normal = pixel X 2 - 1

法线纹理中存储的法线方向,是保存在切线空间中的。这里来解释一下切线空间:切线空间中x轴为该点的切线方向,z轴为该点的法线方向,y轴为x,z轴叉乘得到的方向轴。如果法线纹理中的法线与该点原来的法线一致,则为(0,0,1),在纹理中表现为蓝色,如果不一致则会表现为其他颜色,这样就在法线纹理中表示出了法线的特征。

所以我们的计算可以分为两种:

(1)在切线空间计算,需要到切线空间的变换矩阵

(2)在世界空间计算,需要切线空间到世界空间的变换矩阵

 

切线空间计算的Shader如下:

Shader "Custom/NormalMap_1" {
	Properties {
		_Color("Color",Color) = (1,1,1,1)
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256))=20
		_MainTex("Texture",2D)="white"{}
		_NormalMap("NormalMap",2D)="white"{}
		_NormalScale("NormalScale",Float) = 1.0
	}
	SubShader {
		Pass{
			Tags{"RenderType"="ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"

			fixed4 _Color;
			fixed4 _Specular;
			float _Gloss;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _NormalMap;
			float4 _NormalMap_ST;
			float _NormalScale;

			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 = _MainTex_ST.xy * v.texcoord.xy + _MainTex_ST.zw;
				o.uv.zw = _NormalMap_ST.xy * v.texcoord.xy + _NormalMap_ST.zw;

				float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
				float3x3 rotation = float3x3(normalize(v.tangent.xyz), normalize(binormal), normalize(v.normal));
				o.lightDir = normalize(mul(rotation, ObjSpaceLightDir(v.vertex)).xyz);
				o.viewDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)).xyz);

				return o;
			}

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

				fixed4 packedNormal = tex2D(_NormalMap, i.uv.zw);
				fixed3 tangentNormal;
				tangentNormal.xy = (packedNormal.xy * 2 - 1) * _NormalScale;
				tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));

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

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

				fixed3 h = normalize(tangentLightDir + tangentViewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(h, tangentNormal)),_Gloss);

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

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

			ENDCG
		}
	}
	FallBack "Diffuse"
}

在vert函数中的rotation就是变换到切线空间的矩阵,它是切线空间x轴,y轴,z轴按顺序排列得到的矩阵。frag中的packedNormal是法线纹理中的像素信息(在[0,1]中),tangentNormal是变换后的法线信息(在变换到[-1,1]中后乘以Scale)

 

在世界空间中的计算:

Shader "Custom/NormalMap_2" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256))=20
		_MainTex ("MainTex", 2D) = "white" {}
		_NormalMap("NormalMap",2D)= "white"{}
		_NormalScale("NormalScale",Float) = 1.0

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

			fixed4 _Color;
			fixed4 _Specular;
			float _Gloss;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _NormalMap;
			float4 _NormalMap_ST;
			float _NormalScale;

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

			struct v2f{
				float4 pos:SV_POSITION;
				float3 worldPos:TEXCOORD0;
				float3 T2W_0:TEXCOORD2;
				float3 T2W_1:TEXCOORD3;
				float3 T2W_2:TEXCOORD4;
				float4 uv:TEXCOORD1;
			};

			v2f vert(a2v v){
				v2f o;
				fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				fixed3 worldTangent = normalize(mul(unity_ObjectToWorld, v.tangent).xyz);
				fixed3 binormal = normalize(cross(worldNormal, worldTangent) * v.tangent.w);

				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldPos = worldPos;
				o.T2W_0 = float3(worldTangent.x, binormal.x, worldNormal.x);
				o.T2W_1 = float3(worldTangent.y, binormal.y, worldNormal.y);
				o.T2W_2 = float3(worldTangent.z, binormal.z, worldNormal.z);
				o.uv.xy = _MainTex_ST.xy * v.texcoord.xy + _MainTex_ST.zw;
				o.uv.zw = _NormalMap_ST.xy * v.texcoord.xy + _NormalMap_ST.zw;

				return o;
			}

			fixed4 frag(v2f i):SV_TARGET{
				float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				float3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

				float3 packedNormal = tex2D(_NormalMap, i.uv.zw).xyz;
				float3 bump;
				bump.xy = (packedNormal.xy * 2 - 1) * _NormalScale;
				bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));

				bump = normalize(half3(dot(i.T2W_0, bump), dot(i.T2W_1,bump), dot(i.T2W_2,bump)));

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

				fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldLightDir, bump));
				fixed3 ambient = albedo * UNITY_LIGHTMODEL_AMBIENT.rgb;

				fixed3 h = normalize(worldLightDir + worldViewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(h,bump)),_Gloss);

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

			ENDCG
		}
	}
	FallBack "Diffuse"
}

效果如下:

从左至右依次为:无法线纹理,切线空间计算,世界空间计算

Unity_Shader学习(五)基础纹理Shader_第2张图片

 

你可能感兴趣的:(Unity_Shader学习(五)基础纹理Shader)