纹理显示原理

原始纹理(边长是 $2n$ ),纹理不一定是方图,如果原始图的边长不是 $2n$,游戏引擎在运行时,会自动将纹理的变长补偿为$2^n$,所以补偿是有性能损耗的。

贴图与渲染点对应原理

模型工程师,建模时,会将Mesh网格点和贴图的UV坐标对应,程序首先需
要通过算法,解出当前渲染点对应的UV坐标,然后到纹理中取得对应的像
素,最后着色到渲染点上。
image.png

编写一个简单的纹理贴图,实现混色

纹理属性:存储纹理图
渲染点:CPU提供的渲染点位置
贴图缩放数据和偏移数据:连续贴图到Mesh上,有缩放和偏移量,才能算出对应纹理中的UV点
纹理解析算法:Cg函数Tex2D(纹理,UV坐标)

效果演示:
image.png
代码示例:

Shader "DY/TexturePhongPixel"
{
    Properties
    {
		//需要贴在模型上的纹理贴图,white是Unity内置的一张纯白色的纹理贴图
		_MainTex("主纹理", 2D) = "white" {}
		// 给主纹理进行混色的颜色
		_Color("混色", Color) = (1, 1, 1, 1)
	}

	SubShader
	{

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			// 主纹理导入Cg变量
			sampler _MainTex;
			
			fixed4 _Color;

			//存储了,在材质球上,纹理属性上配置的缩放值和偏移值
			float4 _MainTex_ST;


			struct c2v
			{
				float4 vertex:POSITION;
				// 当前模型渲染点,对应的纹理位置(需要进行转换,才能对应纹理的UV坐标)
				float4 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				//转换到裁剪空间下的渲染点位置
				float4 pos : SV_POSITION;
				//通过顶点着色器计算好的UV坐标,传递给片元着色器,因为是纹理点,所以借用TEXCOORD语义,因为已经有TEXCOORD0,所以写TEXCOORD1
				float2 uv : TEXCOORD1;
			};

			v2f vert(c2v data)
			{
				v2f v;
				//【1】
				v.pos = UnityObjectToClipPos(data.vertex);

				// 【2】
				//如何计算UV坐标
				//Cg语言,点(1,1),缩放(2,3),运算方法应该是(1,1) x (2,3)
				//Cg语言,点(1, 1),平移(5, 5),运算方法应该是(1, 1) + (5, 5)
				//data.texcoord是float4,需要计算的UV是float2,所以只取xy属性
				//_MainTex_ST内部存储了缩放和平移,xy代表了缩放值,zw代表了平移值
				//所以公式如下
				v.uv = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

				return v;
			}

			fixed4 frag(v2f data) :SV_Target
			{
				//通过Tex2D,解出主纹理对应的颜色值
				fixed4 texColor = tex2D(_MainTex, data.uv);
				return texColor * _Color;
			}
			
           
        ENDCG
    }
    }
}

给纹理添加Phong模型光照

效果演示:
image.png

代码示例:

Shader "DY/PhongTexture"
{
	Properties
	{
		_MainTex("主纹理", 2D) = "white" {}
		_Color("混色", Color) = (1, 1, 1, 1)
		_SpecularColor("高光反射材质颜色", Color) = (1, 1, 1, 1)
		_Gloss("光晕系数", Range(1, 256)) = 1
	}

		SubShader
		{
			//设定光照模式为前向模式(才能正常获取光的颜色和光的方向)
			Tags{ "LightMode" = "ForwardBase" }

			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				#include "Lighting.cginc"

				//导入数据
				sampler2D _MainTex;
				float4 _MainTex_ST;

				fixed4 _Color;
				fixed4 _SpecularColor;
				float _Gloss;

				struct c2v
				{
					float4 vertex : POSITION;
					float3 normal : NORMAL;
					float4 texcoord : TEXCOORD0;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float4 worldPos : TEXCOORD1;
					float3 worldNormal : NORMAL;
					float2 uv : TEXCOORD2;
				};

				v2f vert(c2v data)
				{
					v2f r;
					r.pos = UnityObjectToClipPos(data.vertex);
					r.worldPos = mul(unity_ObjectToWorld, data.vertex);
					r.worldNormal = mul((float3x3)unity_ObjectToWorld, data.normal);
					r.uv = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

					return r;
				}

				fixed4 frag(v2f data) : SV_Target
				{
					fixed4 texColor = tex2D(_MainTex, data.uv) * _Color;
					fixed3 worldNormal = normalize(data.worldNormal);

					//材质的颜色就是漫反射的材质颜色
					fixed3 diffuse = _LightColor0.rgb * texColor.rgb * max(0, dot(worldNormal, normalize(_WorldSpaceLightPos0.xyz)));

					fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos.xyz);
					fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), worldNormal));
					fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

					//还需要环境光与材质颜色也混合一下
					fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * texColor.rgb + diffuse + specular;

					return fixed4(color, 1);
				}

				ENDCG
			}
		}
}

法线贴图

法线贴图:存储有与法线垂直的切线信息,切线信息存储在切线空间中,使
用内部值(x切线,y切线)时,需要将法线转换到世界空间中,再进行光照
运算才能得到正确的结果。
调整凹凸深度的参数:用户可配置,可以控制法线长短

Shader实现
加载两张纹理:主纹理,光照法线纹理(切线空间存储数据)
顶点着色器:
【1】主纹理的UV偏移计算
【2】法线纹理的UV偏移计算
【3】计算切线空间到世界空间的转换矩阵(可变),用于变换光照法线
片元着色器
【1】解压主纹理
【2】解压法线纹理,根据切线信息,转换光照法线信息,将光照法线从切线空间,转到世界空间
【3】拿法线纹理算出的光照法线,做光照运算

效果演示:
image.png

Shader "hxsd/PhongNormalTexture"
{
	Properties
	{
		//用于显示材质纹理
		_MainTex("主纹理", 2D) = "white" {}
	//用于和主纹理混色
	_Color("混色", Color) = (1, 1, 1, 1)

		//法线纹理
		_BumpTex("法线纹理", 2D) = "bump" {}
	//法线深度系数,可以控制法线高度
	_BumpScale("法线深度系数", Range(0, 1)) = 0.5

	_SpecularColor("高光反射材质颜色", Color) = (1, 1, 1, 1)
	_Gloss("光晕系数", Range(1, 256)) = 1
	}

		SubShader
	{
		Tags{ "LightMode" = "ForwardBase" }

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			//导入主纹理数据
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;

			//导入法线信息
			sampler2D _BumpTex;
			float4 _BumpTex_ST;
			float _BumpScale;

			//导入高光信息
			fixed4 _SpecularColor;
			float _Gloss;

			//从CPU过来的数据
			struct c2v
			{
				float4 vertex : POSITION;	//从CPU传递过来的模型空间下,需要渲染的点
				float4 texcoord : TEXCOORD0;	//因为两张贴图的像素,除颜色外完全重叠,所以纹理坐标点可以通用
				float4 tangent : TANGENT;	//光照法线纹理,因为要计算切线空间下的信息,所以需要当前渲染点的切线信息
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;	//模型空间到裁剪空间转换后的点
				float4 uv : TEXCOORD1;		//因为要算出两张纹理的UV坐标,所以做一个float4,xy存储主纹理UV,zw存储法线纹理UV
				float4 worldPos : TEXCOORD2;//世界空间下的点,用于计算高光反射

				float3 t2wOne : TEXCOORD3;	//用于传递从顶点着色器计算好的转换矩阵
				float3 t2wTwo : TEXCOORD4;
				float3 t2wThree : TEXCOORD5;
			};

			v2f vert(c2v data)
			{
				v2f r;

				//将模型空间下的渲染点,转换到裁剪空间下
				r.pos = UnityObjectToClipPos(data.vertex);

				//两张纹理的缩放和偏移可能不同,所以分别计算UV偏移信息,存储在v2f.uv
				//两张纹理一样,所以可以共用原始纹理坐标
				r.uv.xy = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				r.uv.zw = data.texcoord.xy * _BumpTex_ST.xy + _BumpTex_ST.zw;

				//CPU传递过来的切线存储在模型空间下
				//法线纹理中的,光照法线推算信息,是存储在切线空间下的
				//CPU传递过来的切线信息与法线纹理中切线信息,有转换关系,所以可以推算出一个转换矩阵,用于转换法线
				//计算出来的转换矩阵应该是从(切线空间到模型空间)的转换
				//但是我们需要的是计算从(切线空间到世界空间)的转换矩阵(因为最终的光照运算,需要在世界空间中完成)
				//所以应该先把CPU传递过来的切线信息,转换到世界空间下
				//再计算切线的转换矩阵,这时就能得到从(切线空间,到世界空间)的转换矩阵
				//拥有了转换矩阵,就可以将法线纹理中,求解的法线信息,从切线空间,转换到世界空间下,进而可以计算光照

				//世界坐标系下的点的位置(片元着色器计算光照需求)
				r.worldPos = mul(unity_ObjectToWorld, data.vertex);
				//世界空间下渲染点的法线信息
				float3 worldNormal = mul((float3x3)unity_ObjectToWorld, data.normal);
				//世界空间下切线的方向向量
				float3 worldTangent = mul((float3x3)unity_ObjectToWorld, data.tangent.xyz);
				//世界空间下计算与切线和法线垂直的线的方向向量(用于计算转换矩阵)
				float3 worldBinormal = cross(worldNormal, worldTangent) * data.tangent.w;

				//需要将转换矩阵,传递给片元着色器,用于转换切线空间下的法线到世界空间中
				r.t2wOne = float3(worldTangent.x, worldBinormal.x, worldNormal.x);
				r.t2wTwo = float3(worldTangent.y, worldBinormal.y, worldNormal.y);
				r.t2wThree = float3(worldTangent.z, worldBinormal.z, worldNormal.z);

				return r;
			}

			fixed4 frag(v2f data) : SV_Target
			{
				//计算法线纹理中法线信息(重点),解出的法线在切线空间
				//解法线前,需要先对法线纹理贴图进行采样
				fixed3 bump = UnpackNormal(tex2D(_BumpTex, data.uv.zw));
			//通过缩放值,影响凹凸感
			bump.xy *= _BumpScale;
			//计算法线高度(数学公式)
			//法线还没有转换空间,所以计算出的法线,还在切线空间下
			bump.z = sqrt(1 - max(0, dot(bump.xy, bump.xy)));

			//通过顶点着色器传递过来的转换矩阵,转换法线,从切线空间到世界空间
			//向量点乘和矩阵一行乘以一列的规则一样,所以这里不用再重新构造矩阵
			bump = float3(dot(data.t2wOne, bump), dot(data.t2wTwo, bump), dot(data.t2wThree, bump));

			//解主纹理
			fixed4 texColor = tex2D(_MainTex, data.uv.xy) * _Color;

			//计算漫反射光照
			fixed3 diffuse = _LightColor0.rgb * texColor.rgb * max(0, dot(normalize(bump), normalize(_WorldSpaceLightPos0.xyz)));

			//计算高光反射光照
			fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos);
			fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz), normalize(bump)));
			fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);

			//Phong光照运算
			fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * texColor.rgb + diffuse + specular;

			return fixed4(color, 1);
		}

		ENDCG
	}
	}

		Fallback "Diffuse"
}