观前提示:本文章是个人笔记,只是给自己看的,我自己看得懂就行。
一、普通纹理显示
1、纹理显示原理
原始纹理(边长是2n),纹理不一定是方图,如果原始图的边长不是2n,游戏引擎在运行时,会自动将纹理的变长补偿为2n,所以补偿是有性能损耗的。
2、贴图与渲染点对应原理
模型工程师,建模时,会将Mesh网格点和贴图的UV坐标对应,程序首先需要通过算法,解出当前渲染点对应的UV坐标,然后到纹理中取得对应的像素,最后着色到渲染点上。
3、代码实现
(1)纹理采样Shader实现
Shader "test/VFTexture"
{
Properties
{
//需要贴在模型上的纹理贴图,white是unity内置的一张纯白色的纹理贴图
_MainTex ("Texture", 2D) = "white" {}
//给主纹理进行混色的颜色
_Color("Color",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
//主纹理导入Cg变量
sampler2D _MainTex;
//存储了,在材质球上,纹理属性上配置的缩放值和偏移值
float4 _MainTex_ST;
//混色颜色导入Cg变量
fixed4 _Color;
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 r;
r.pos = UnityObjectToClipPos(data.vertex);
//如何计算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代表了平移值
//所以公式如下
r.uv = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
return r;
}
fixed4 frag(v2f data):SV_Target
{
//通过Tex2D解出主纹理对应的颜色值
fixed4 texColor = tex2D(_MainTex,data.uv);
//混色采用向量乘法实现
return texColor * _Color;
}
ENDCG
}
}
FallBack "DIFFUSE"
}
(2)Phong光照纹理采样Shader实现
Shader "test/PhongTexture"
{
Properties
{
//需要贴在模型上的纹理贴图,white是unity内置的一张纯白色的纹理贴图
_MainTex ("Texture", 2D) = "white" {}
//给主纹理进行混色的颜色
_Color("Color",Color) = (1,1,1,1)
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//主纹理导入Cg变量
sampler2D _MainTex;
//存储了,在材质球上,纹理属性上配置的缩放值和偏移值
float4 _MainTex_ST;
//混色颜色导入Cg变量
fixed4 _Color;
//导入高光反射材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
struct c2v
{
//挡墙需要显示点的位置(模型空间下)
float4 vertex:POSITION;
//当前模型渲染点,对应的纹理位置(需要进行转换,才能对应纹理的UV坐标)
float4 texcoord:TEXCOORD0;
//从CPU接收到的当前点的模型空间下的法线向量
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
//通过顶点着色器计算好的UV坐标,传递给片元着色器,因为是纹理点,所以借用TEXCOORD语义,因为已经有TEXCOORD0了,所以写TEXCOORD1
float2 uv:TEXCOORD1;
//借用纹理的语义实现世界空间下点的位置传递
float4 worldPos:TEXCOORD2;
//世界空间下,法线的方向
float3 normal:NORMAL;
};
v2f vert(c2v data)
{
v2f r;
r.pos = UnityObjectToClipPos(data.vertex);
//如何计算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代表了平移值
//所以公式如下
r.uv = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//被渲染点的位置,是在世界空间下的坐标
r.worldPos = mul(unity_ObjectToWorld, data.vertex);
//世界坐标下,法线的方向向量
r.normal = mul((float3x3)unity_ObjectToWorld,data.normal);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//通过Tex2D解出主纹理对应的颜色值
fixed4 texColor = tex2D(_MainTex,data.uv) * _Color;
//将顶点着色器传过来的法线向量标准化
fixed3 worldNormal = normalize(data.normal);
//使用相机位置减去顶点着色器计算的世界坐标系下的点的位置得到观察方向,并标准化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - data.worldPos.xyz);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),worldNormal));
//高光公式运算
fixed3 Specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(viewDir,refDir)),_Gloss);
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//兰伯特定律公式运算
//主纹理对应的颜色值就是漫反射颜色
fixed3 diffuse = _LightColor0.rgb * texColor.rgb * (max(0,dot(worldNormal,worldLightDir)) );
//Phong光照模型
//还需要环境光与材质颜色混合一下
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * texColor +diffuse + Specular;
//混色采用向量乘法实现
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}
二、法线贴图控制凹凸
法线贴图,是对主纹理凹凸显示,法线贴图在切线空间下存储xy切线,映射法线,法线信息存储在切线空间中。模型是否凹凸,是由模型顶点决定的,现在实现的法线贴图,控制凹凸,实际上是配合光照实现的,凹进去的部分,颜色偏暗,突出来来的部分,颜色偏亮。
法线贴图控制主纹理凹凸显示Shader实现:
Shader "test/PhongNormalTexture"
{
Properties
{
//需要贴在模型上的纹理贴图,white是unity内置的一张纯白色的纹理贴图
_MainTex ("Texture", 2D) = "white" {}
//给主纹理进行混色的颜色
_Color("Color",Color) = (1,1,1,1)
//法线纹理
_BumpTex("BumpTex",2D) = "bump" {}
//法线深度系数,可以控制法线高度
_BumpScale("BumpScale",Float) = 1
//高光反射材质颜色
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
//光晕系数
_Gloss("Gloss",Range(4,256)) = 10
}
SubShader
{
Pass
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//主纹理导入Cg变量
sampler2D _MainTex;
//存储了,在材质球上,纹理属性上配置的缩放值和偏移值
float4 _MainTex_ST;
//主纹理混色颜色导入Cg变量
fixed4 _Color;
//导入法线信息
sampler2D _BumpTex;
float4 _BumpTex_ST;
float _BumpScale;
//导入高光反射材质颜色
fixed4 _SpecularColor;
//导入高光光晕大小系数
fixed _Gloss;
//从CPU过来的信息
struct c2v
{
//当前需要显示点的位置(模型空间下)
float4 vertex:POSITION;
//当前模型渲染点,对应的纹理位置(需要进行转换,才能对应纹理的UV坐标)
float4 texcoord:TEXCOORD0;//因为两张贴图的像素,除颜色外完全相等,所以纹理坐标点可以通用
//光照法线纹理,因为要计算切线空间下的信息,所以需要当前渲染点切线信息
float4 tangent:TANGENT;
//从CPU接收到的当前点的模型空间下的法线向量
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;//经过顶点着色器计算后的,当前点的裁剪空间下的位置
//通过顶点着色器计算好的UV坐标,传递给片元着色器,因为是纹理点,所以借用TEXCOORD语义,因为已经有TEXCOORD0了,所以写TEXCOORD1
float4 uv:TEXCOORD1;//因为要计算两张纹理的UV坐标,所以做一个float4,xy存储主纹理UV,zw存储法线纹理UV
//用于传递从顶点主色器计算好的矩阵
float4 MatrixRowOne : TEXCOORD2;
float4 MatrixRowTwo : TEXCOORD3;
float4 MatrixRowThree : TEXCOORD4;
};
v2f vert(c2v data)
{
v2f r;
r.pos = UnityObjectToClipPos(data.vertex);
//如何计算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代表了平移值
//两张纹理的缩放和偏移不一致,所以分别计算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;
//被渲染点的位置,在世界空间下的坐标
float4 worldPos = mul(unity_ObjectToWorld, data.vertex);
//CPU传递过来的切线是存储在模型空间下
//法线纹理中的,光照法线推算信息,是存储在切线空间下的
//CPU传递过来的切线信息与法线纹理中的切线信息,有转换关系,所以可以推算出一个转换矩阵用于转换法线
//计算出来的转换矩阵是从切线空间到模型空间的转换,而需要的是从切线空间到世界空间的转换矩阵
//所以要先把CPU传过来的切线信息转化到世界空间下,再计算切线的转换矩阵,就能得到从切线空间到世界空间的转换矩阵
//拥有了转换矩阵,就可以将法线纹理中的法线信息,从切线空间转换到世界空间下,进而可以计算光照
//世界坐标下,法线的方向向量
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.MatrixRowOne = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
r.MatrixRowTwo = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
r.MatrixRowThree = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);
return r;
}
fixed4 frag(v2f data):SV_Target
{
//世界空间下的点
float3 worldPos = float3(data.MatrixRowOne.w,data.MatrixRowTwo.w,data.MatrixRowThree.w);
//计算法线纹理中的法线信息,解出的法线在切线空间
//解法线前,需要先对法线纹理贴图进行采样
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.MatrixRowOne.xyz,bump),dot(data.MatrixRowTwo.xyz,bump),dot(data.MatrixRowThree.xyz,bump));
//标准化法线
bump = normalize(bump);
//通过Tex2D解出主纹理对应的颜色值
fixed4 texColor = tex2D(_MainTex,data.uv.xy) * _Color;
//使用相机位置减去顶点着色器计算的世界坐标系下的点的位置得到观察方向,并标准化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos);
//光线反射的方向:reflect(入射光方向,当前点的法线向量)
fixed3 refDir = normalize(reflect(normalize(-_WorldSpaceLightPos0.xyz),bump));
//计算高光反射光照
fixed3 Specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(viewDir,refDir)),_Gloss);
//获得直射光的光方向
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//计算漫反射光照
fixed3 diffuse = _LightColor0.rgb * texColor.rgb * (max(0,dot(bump,worldLightDir)) );
//Phong光照运算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * texColor.rgb +diffuse + Specular;
//混色采用向量乘法实现
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "DIFFUSE"
}