基础纹理
最基本的纹理采样用到的Unity Shader 内置方法:
UNITY_MATRIX_MVP 当前的模型观察投影矩阵:用于将顶点/方向矢量从模型空间变换到裁剪空间。
模型空间下的顶点纹理坐标 float4 vertex:POSITION;
变换到裁剪空间下顶点纹理坐标 float4 pos:SV_POSITION;
pos = mul(UNITY_MATRIX_MVP, vertex);
_Object2World 当前的模型矩阵:用于将顶点/方向矢量从模型空间变换到世界空间
模型空间下的顶点纹理坐标 float4 vertex:POSITION;
世界空间下的顶点纹理坐标 float3 worldPos:TEXCOORD1;
worldPos = mul(_Object2World,vertex).xyz;
UnityObjectToWorldNormal 把法线从模型空间转换到世界空间
模型空间下的法线 float3 normal:NORMAL
世界空间下的法线 float3 worldNormal:TEXCOORD0;
worldNormal = UnityObjectToWorldNormal(normal);
WorldSpaceLightDir 输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化
float3 WorldSpaceLightDir(float4 v);
UnityWorldSpaceLightDir 输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化(Unity内置函数相比WorldSpaceLightDir 多了对方向矢量进行了一次坐标空间变换)
float3 UnityWorldSpaceLightDir(float3 v);
世界空间下的顶点纹理坐标 float3 worldPos:TEXCOORD1;
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
cg的tex2D函数对纹理进行采样。它的第一个参数是需要被采样的纹理,第二个参数是一个float2类型的纹理坐标,它将返回计算得到的纹素值。
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
float4 texcoord:TEXCOORD0;
float2 uv:TEXCOORD2;
uv=texcoord.xy*_MainTex_ST+_MainTex_ST.zw;
或者
uv=TRANSFORM_TEX(texcoord,_MainTex);
fixed3 albedo = tex2D(_MainTex,uv).rgb*_Color.rgb;
(材质的反射率albedo)
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
(Unity的内置变量UNITY_LIGHTMODEL_AMBIENT环境光与材质的反射率albedo相乘得到ambient)
fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(worldNormal,worldLightDir));(计算漫反射光照:_LightColor0光源的颜色和强度)
float3 UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
(Blinn光照模型)
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(worldNormal,halfDir)),_Glass);
(高光反射模型计算)
return fixed4(ambient + diffuse + specular,1.0);
(最终得到漫反射时使用纹理中的纹素值)
纹理的属性
TextureType:Texture、Normal map、Cubemap
Wrap Mode:Repeat(这种模式下,如果纹理坐标超过1,那么它的整数部分将会被舍弃,而直接使用小数部分进行采样,这样的结果是纹理将会不断重复)、Clamp(这种模式下,如果纹理坐标大于1,那么将会被截取到1,如果小于0,那么将会被截取到0)。
Filter Mode:纹理由于变换而产生拉伸时将会采用哪种滤波模式。Filter Mode 支持3种模式:Point,Bilinear 以及Trilinear。(滤波效果依次提升,但需要消耗的性能也依次增大。)
纹理缩小的过程比放大更加复杂一些,此时原纹理中的多个像素将会对应一个目标像素。纹理缩放更加复杂的原因在于我们往往需要处理抗锯齿问题,一个最常使用的方法就是使用多级渐远纹理(mipmapping)技术。
凹凸映射
纹理的另一种常见的应用就是凹凸映射(bump mapping)。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。
有两种主要的方法可以用来进行凹凸映射:一种方法是使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(height mapping);另一种方法则是使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)
高度纹理,即使用一张高度图来实现凹凸映射。高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。因此,颜色越浅表明该位置的表面越向外凸起,而颜色越深表明该位置越向里凹。这种方法的好处是非常直观,我们可以从高度图中明确地知道一个模型表面的凹凸情况,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值计算而得,因此需要消耗更多的性能。高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息。也就是说,我们通常会使用法线映射来修改光照。
而法线纹理 中存储的就是表面的法线方向。由于法线方向的分量范围在[-1,1],而像素的分量范围为[0,1],因此我们需要做一个映射,通常使用的映射就是:
pixel = (normal+1)/ 2
这就要求,我们在shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
normal = pixel * 2 - 1
法线纹理中存储的法线方向所在的坐标空间:
对于模型顶点自带的法线,它们是定义在模型空间中的,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理(object-space-normal map)。
在实际制作中,我们往往会采用另一种坐标空间,即模型顶点的切线空间(tangent space)来存储法线。对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为是副切线或副法线。这种纹理被称为是切线空间的法线纹理(tangent-space-normal-map)。