UnityShader入门精要学习 7 :基础纹理(待总结)

纹理映射技术

单张纹理

实践 - 使用单张纹理作为模拟颜色

Shader "Unity Shader Books/Chapter 7/Single Texture"
{
    Properties
    {
		_Color ("Color Tint",Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
	SubShader
	{
		Pass
		{
			Tags {"LightrMode" = "ForwardBase"}

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;//其中存储了纹理的缩放和偏移值,分别可以通过.xy和.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 = 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;//使用tex2D函数对纹理采样,返回纹素值,乘以颜色属性作为材质反射率albedo
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;//将albedo和环境光照相乘得到环境光
				
				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));//使用albedo计算漫反射结果

				//计算高光反射
				fixed3 viewDir = normalize(UnityWorldSpaceLightDir(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"
}

完成上面代码的编写并赋给场景中准备好的胶囊体,并准备一张普通的jpg砖墙图片赋值给 Texture 属性,我们就可以看到其呈现的效果:
在这里插入图片描述

纹理属性(待补充)

对于我们选择的纹理(jpg图片),我们可以在 Inspector 窗口中检视它的属性:
在这里插入图片描述

凹凸映射

凹凸映射是纹理的另一种常见应用。他的目的是使用一张纹理来修改模型表面的法线,丰富模型视觉上的效果但不改变顶点的实际位置
主要有两种方法来进行凹凸映射:一种是高度映射,一种是法线映射

高度纹理

高度映射的方式是使用一张高度纹理(height map)来模拟表面位移,然后得到一个修改后的法线值。 height map 中存储的是强度值 intensity ,用于表示模型表面局部的海拔高度:
在这里插入图片描述
就如上面这张 height map 所示,其颜色越浅表明模型越向外凸起,颜色越深表明模型越向内凹陷
使用这种方法好处是可以直观的知道模型表面的凹凸情况,但这一方式计算复杂,实时计算时不能直接得到表面法线,需要由像素的灰度值计算而得,会消耗更多的性能
高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息

法线纹理

法线映射的方式是使用一张法线纹理(normal map)直接存储表面法线
由于法线方向的分量范围是[-1,1],像素的分量范围是[0,1],因此我们需要做一个映射,通常为:
在这里插入图片描述
因此我们在 shader 中进行纹理采样后要得到法线方向需要做相应的反映射:
在这里插入图片描述
除此以外,我们还需要考虑法线纹理中存储法线方向是在哪个坐标空间中的。这里我们可以采用两种坐标空间:一种是模型自身的模型空间,另一种是模型顶点的切线空间,因此也有了对应的纹理称谓:模型空间的法线纹理和切线空间的法线纹理:
在这里插入图片描述
在这里插入图片描述
(由于选取了不同空间在映射后对应了不同的RGB值,同一张法线纹理呈现不同的颜色)

我们往往会使用切线空间下的法线纹理(因为优点很多)

实践 - 切线空间下的法线纹理

在开始编写前,还有一些我们需要注意的地方:我们需要在计算光照模型时统一各个方向矢量所在的坐标空间。通常我们有两种选择,一种是在切线空间下进行光照关照计算,此时我们需要将光照方向和视角方向转换到切线空间下;一种是在世界空间下进行光照计算,此时需要将纹理采样得到的法线方向转换到世界空间下
下分别实现两种方法

在切线空间下计算光照
Shader "Unity Shaders Book/Chapter7/Normal Map In Tangent Space"
{
    Properties
    {
		_Color("Color Tint",Color) = (1,1,1,1)
		_MainTex("Texture", 2D) = "white" {}
		_BumpMap("Normal map",2D) = "bump" {}			//法线纹理属性,使用内置的法线纹理"bump"
		_BumpScale("Bump Scale",Float) = 1.0			//用于控制凹凸程度,为0时不对光照产生影响
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        Tags { "LightMode"="ForwardBase" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;//其中存储了纹理的缩放和偏移值,分别可以通过.xy和.zw得到
			sampler2D _BumpMap;
			float4 _BumpMap_ST;//和_MainTex_ST类似,用于获得纹理的属性(平铺和偏移系数)
			float _BumpScale;
			fixed4 _Specular;
			float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
				float4 tangent : TANGENT;			//tangent.w决定切线空间中第三个坐标轴 ———— 副切线的方向性
				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);
				//在uv中存储使用的两个纹理坐标
                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 binarmal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
				//建立将向量从模型空间变换到切线空间的变换矩阵
				//float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);

				//通过Unity内置变量直接计算得到rotation变换矩阵
				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;
				//如果法线纹理没有设置成Normal Map类型,则手动进行反映射
				tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
				tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

				//如果法线方向设置为了Normal Map类型,则使用内置函数计算正确的法线方向
				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;//使用tex2D函数对纹理采样,返回纹素值,乘以颜色属性作为材质反射率albedo

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

				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));//使用albedo计算漫反射结果

				//计算高光反射
				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"
}

完成上面的代码后,我们可以在材质面板中为胶囊体添加 normal map ,使得胶囊体呈现视觉上的凹凸感,面板中的 Bump Scale 属性可以调整凹凸程度
在这里插入图片描述

在世界空间下计算
Shader "Unity Shaders Book/Chapter7/Normal Map In World Space"
{
	Properties
	{
		_Color("Color Tint",Color) = (1,1,1,1)
		_MainTex("Texture", 2D) = "white" {}
		_BumpMap("Normal map",2D) = "bump" {}			//法线纹理属性,使用内置的法线纹理"bump"
		_BumpScale("Bump Scale",Float) = 1.0			//用于控制凹凸程度,为0时不对光照产生影响
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
		SubShader
		{
			Tags { "LightMode" = "ForwardBase" }

			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				#include "Lighting.cginc"

				fixed4 _Color;
				sampler2D _MainTex;
				float4 _MainTex_ST;//其中存储了纹理的缩放和偏移值,分别可以通过.xy和.zw得到
				sampler2D _BumpMap;
				float4 _BumpMap_ST;//和_MainTex_ST类似,用于获得纹理的属性(平铺和偏移系数)
				float _BumpScale;
				fixed4 _Specular;
				float _Gloss;

				struct a2v
				{
					float4 vertex : POSITION;
					float3 normal : NORMAL;
					float4 tangent : TANGENT;			//tangent.w决定切线空间中第三个坐标轴 ———— 副切线的方向性
					float4 texcoord : TEXCOORD0;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float4 uv : TEXCOORD0;
					//依次存储从切线空间到世界空间变换矩阵的每一行(世界空间下的顶点位置存储在w分量中)
					float4 TtoW0 : TEXCOORD1;
					float4 TtoW1 : TEXCOORD2;
					float4 TtoW2 : TEXCOORD3;
				};

				v2f vert(a2v v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					//在uv中存储使用的两个纹理坐标
					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;
					float3 worldNormal = UnityObjectToWorldNormal(v.vertex);
					float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
					float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

					//计算切线空间到世界空间的变换矩阵
					//将世界空间下的顶点位置的xyz分量分别存储在TtoW变量的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 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.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));

					//通过纹理采样计算漫反射颜色
					fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;//使用tex2D函数对纹理采样,返回纹素值,乘以颜色属性作为材质反射率albedo

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

					fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, LightDir));//使用albedo计算漫反射结果

					//计算高光反射
					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"
}

在这里插入图片描述

Unity中的法线纹理类型

在 Unity 中使用法线贴图时,尽量将其纹理类型标识为 Normal Map ,这样做 Unity 可以根据不同平台对纹理进行压缩,然后通过 UnpackNormal 函数针对不同的压缩格式进行正确的采样
在这里插入图片描述
值得一提的是,当纹理类型标识为 Normal Map 后,下面会出现一个复选框 “Create from Grayscale”,这一选项用于从高度图中生成法线纹理(勾选后, Unity 会根据高度图生成一张切线空间下的法线纹理)
在这里插入图片描述
其中, Bumpiness 用于控制凹凸程度, Filtering 决定计算凹凸程度的方式

渐变纹理(存在问题)

实践 - 使用渐变纹理控制漫反射光照

Shader "Unity Shaders Book/Chapter7/Ramp Texture"
{
    Properties
    {
		_Color ("Tint Color",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
    {
        Tags { "LightMode"="ForwardBase" }

        Pass
        {
            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 = UnityObjectToClipPos(v.vertex);
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);//使用Unity内置宏计算平铺和偏移后的纹理坐标
                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(UnityWorldSpaceLightDir(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时,可以保护表现不受该属性的影响

实践 - 高光遮罩纹理

Shader "Unity Shaders Book/Chapter7/Mask Texture"
{
    Properties
    {
		_Color ("Tint Color",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
    {
        Tags { "LightMode"="ForwardBase" }

        Pass
        {
            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;//使用tex2D函数对纹理采样,返回纹素值,乘以颜色属性作为材质反射率albedo

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

				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));//使用albedo计算漫反射结果

				//计算使用遮罩纹理的高光反射
				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"
}

(由于缺少素材,暂无配图)

其他遮罩纹理

实际的游戏制作过程中,遮罩纹理已经不限于保护游戏的某些区域不被修改,而是可以存储任何我们希望逐像素控制的表面属性。我们会充分利用一张纹理的RGBA四个通道来存储不同的属性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值