纹理显示原理
原始纹理(边长是 $2n$ ),纹理不一定是方图,如果原始图的边长不是 $2n$,游戏引擎在运行时,会自动将纹理的变长补偿为$2^n$,所以补偿是有性能损耗的。
贴图与渲染点对应原理
模型工程师,建模时,会将Mesh网格点和贴图的UV坐标对应,程序首先需
要通过算法,解出当前渲染点对应的UV坐标,然后到纹理中取得对应的像
素,最后着色到渲染点上。
编写一个简单的纹理贴图,实现混色
纹理属性:存储纹理图
渲染点:CPU提供的渲染点位置
贴图缩放数据和偏移数据:连续贴图到Mesh上,有缩放和偏移量,才能算出对应纹理中的UV点
纹理解析算法:Cg函数Tex2D(纹理,UV坐标)
效果演示:
代码示例:
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模型光照
效果演示:
代码示例:
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】拿法线纹理算出的光照法线,做光照运算
效果演示:
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"
}
评论区