文章目录
纹理单张纹理纹理的属性Wrap Mode 属性Filter Mode属性凹凸映射高度纹理法线纹理渐变纹理遮罩纹理其他遮罩纹理纹理
纹理就是使用一张图片来控制模型的外观。
使用纹理映射(texture mapping)技术,我们可以把一张图“黏”在模型表面,逐纹素(texel)(纹素的名字是为了和像素进行区分)地控制模型的颜色。
在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标(texture-mapping coordinates)存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D坐标。通常,这些坐标使用一个**二维变量(u, v)**来表示,其中u是横向坐标,而v是纵向坐标。因此,纹理映射坐标也被称为UV坐标。
纹理的大小可以是多种多样的,如256×256或者1028×1028,但顶点UV坐标的范围通常都被归一化到[0, 1]范围内。
纹理采样时使用的纹理坐标不一定是在[0, 1]范围内。实际上,这种不在[0, 1]范围内的纹理坐标有时会非常有用。与之关系紧密的是纹理的平铺模式,它将决定渲染引擎在遇到不在[0, 1]范围内的纹理坐标时如何进行纹理采样。
单张纹理
使用一张纹理来代替物体的漫反射颜色:
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'Shader "Unlit/CH7-SingleTexture" {Properties {_Color ("Color Tint", Color) = (1, 1, 1, 1)// 2D是纹理属性的声明方式// 使用一个字符串后跟一个花括号作为它的初始值,"white"是内置纹理的名字,也就是一个全白的纹理。_MainTex ("Main Tex", 2D) = "white" {}_Specular ("Specular", Color) = (1, 1, 1, 1)_Gloss ("Gloss", Range(8.0, 256)) = 20}SubShader {Pass {Tags {// LightMode标签是Pass标签中的一种,它用于定义该Pass在Unity的光照流水线中的角色。"LightMode" = "ForwardBase"}CGPROGRAM#pragma vertex vert#pragma fragment frag#include "Lighting.cginc"fixed4 _Color;sampler2D _MainTex;// 纹理名_ST的方式来声明某个纹理的属性// ST是缩放(scale)和平移(translation)的缩写// _MainTex_ST可以让我们得到该纹理的缩放和平移(偏移)值// _MainTex_ST.xy存储的是缩放值// _MainTex_ST.zw存储的是偏移值float4 _MainTex_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 = 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;fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));fixed3 viewDir = normalize(UnityWorldSpaceViewDir(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"}
纹理的属性
Wrap Mode 属性
Repeat:如果纹理坐标超过了1,那么它的整数部分将会被舍弃,而直接使用小数部分进行采样,这样的结果是纹理将会不断重复
Clamp:如果纹理坐标大于1,那么将会截取到1,如果小于0,那么将会截取到0
上图展示了在纹理的平铺(Tiling)属性为(3, 3)时分别使用两种Wrap Mode的结果。
左图使用了Repeat模式,在这种模式下纹理将会不断重复;
右图使用了Clamp模式,在这种模式下超过范围的部分将会截取到边界值,形成一个条形结构。
上图展示了在纹理的偏移属性为(0.2, 0.6)时分别使用两种Wrap Mode的结果,左图使用了Repeat模式,右图使用了Clamp模式。
Filter Mode属性
Filter Mode属性决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。
Filter Mode支持3种模式:Point,Bilinear以及Trilinear。
它们得到的图片滤波效果依次提升,但需要耗费的性能也依次增大。
纹理滤波会影响放大或缩小纹理时得到的图片质量。
例如,当我们把一张64×64大小的纹理贴在一个512×512大小的平面上时,就需要放大纹理。
纹理缩小的过程比放大更加复杂一些,此时原纹理中的多个像素将会对应一个目标像素。
纹理缩放更加复杂的原因在于我们往往需要处理抗锯齿问题,一个最常使用的方法就是使用多级渐远纹理(mipmapping)技术。
“mip”是拉丁文“multum in parvo”的缩写,它的意思是“在一个小空间中有许多东西”。
多级渐远纹理技术将原纹理提前用滤波处理来得到很多更小的图像,形成了一个图像金字塔,每一层都是对上一层图像降采样的结果。这样在实时运行时,就可以快速得到结果像素,例如当物体远离摄像机时,可以直接使用较小的纹理。但缺点是需要使用一定的空间用于存储这些多级渐远纹理,通常会多占用33%的内存空间。这是一种典型的用空间换取时间的方法。
在Unity中,我们可以在纹理导入面板中,首先将纹理类型(Texture Type)选择成Advanced,再勾选Generate Mip Maps即可开启多级渐远纹理技术。同时,我们还可以选择生成多级渐远纹理时是否使用线性空间(用于伽玛校正,详见18.4.2节)以及采用的滤波器等。
上图给出了从一个倾斜的角度观察一个网格结构的地板时,使用不同FilterMode(同时也使用了多级渐远纹理技术)得到的效果。
在内部实现上,Point模式使用了最近邻(nearest neighbor)滤波,在放大或缩小时,它的采样像素数目通常只有一个,因此图像会看起来有种像素风格的效果。
而Bilinear滤波则使用了线性滤波,对于每个目标像素,它会找到4个邻近像素,然后对它们进行线性插值混合后得到最终像素,因此图像看起来像被模糊了。
而Trilinear滤波几乎是和Bilinear一样的,只是Trilinear还会在多级渐远纹理之间进行混合。如果一张纹理没有使用多级渐远纹理技术,那么Trilinear得到的结果是和Bilinear就一样的。
通常,我们会选择Bilinear滤波模式。需要注意的是,有时我们不希望纹理看起来是模糊的,例如对于一些类似棋盘的纹理,我们希望它就是像素风的,这时我们可能会选择Point模式。
凹凸映射
纹理的另一种常见的应用就是凹凸映射(bump mapping)。
凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。
这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”。
有两种主要的方法可以用来进行凹凸映射:
使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(height mapping)使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)
高度纹理
高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。
颜色越浅表明该位置的表面越向外凸起,而颜色越深表明该位置越向里凹。
这种方法的好处是非常直观,我们可以从高度图中明确地知道一个模型表面的凹凸情况,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值计算而得,因此需要消耗更多的性能。
👆 高度图
高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息。
我们通常会使用法线映射来修改光照。
法线纹理
法线纹理中存储的是表面的法线方向。
由于法线方向的分量范围在[-1,1],而像素的分量范围为[0, 1],因此我们需要做一个映射:
这就要求,我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数。
方向是相对于坐标空间来说的,对于模型顶点自带的法线,它们是定义在模型空间中的,因此将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理(object-space normalmap)。然而,在实际制作中,我们往往会采用另一种坐标空间,即模型顶点的切线空间(tangent space)来存储法线。对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n), x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为是副切线(bitangent, b)或副法线,这种纹理被称为是切线空间的法线纹理(tangent-space normal map)。
👆模型顶点的切线空间。原点对应顶点坐标,x轴是切线方向,y轴是副切线方向,z轴是法线方向。
👆模型空间下的法线纹理
👆切线空间下的法线纹理
模型空间下的法线纹理看起来是“五颜六色”的。这是因为所有法线所在的坐标空间是同一个坐标空间,即模型空间,而每个点存储的法线方向是各异的,有的是(0, 1, 0),经过映射后存储到纹理中就对应了RGB(0.5, 1, 0.5)浅绿色,有的是(0, -1, 0),经过映射后存储到纹理中就对应了(0.5, 0, 0.5)紫色。
切线空间下的法线纹理看起来几乎全部是浅蓝色的。这是因为,每个法线方向所在的坐标空间是不一样的,即是表面每点各自的切线空间。这种法线纹理其实就是存储了每个点在各自的切线空间中的法线扰动方向。也就是说,如果一个点的法线方向不变,那么在它的切线空间中,新的法线方向就是z轴方向,即值为(0, 0, 1),经过映射后存储在纹理中就对应了RGB(0.5, 0.5, 1)浅蓝色。而这个颜色就是法线纹理中大片的蓝色。这些蓝色实际上说明顶点的大部分法线是和模型本身法线一样的,不需要改变。
总体来说,模型空间下的法线纹理更符合人类的直观认识,而且法线纹理本身也很直观,容易调整,因为不同的法线方向就代表了不同的颜色。但美术人员往往更喜欢使用切线空间下的法线纹理:
自由度很高。
模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型,而应用到其他模型上效果就完全错误了。
切线空间下的法线纹理记录的是相对法线信息,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。
可进行UV动画。
比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理会得到完全错误的结果。
可以重用法线纹理。
比如,一个砖块,我们仅使用一张法线纹理就可以用到所有的6个面上。
可压缩。
由于切线空间下的法线纹理中法线的Z方向总是正方向,因此我们可以仅存储XY方向,而推导得到Z方向。
而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。
渐变纹理
一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理其实可以用于存储任何表面属性。
一种常见的用法就是使用渐变纹理来控制漫反射光照的结果。在之前计算漫反射光照时,我们都是使用表面法线和光照方向的点积结果与材质的反射率相乘来得到表面的漫反射光照。但有时,我们需要更加灵活地控制光照结果。
冷到暖色调(cool-to-warmtones),用来得到一种插画风格的渲染效果。使用这种技术,可以保证物体的轮廓线相比于之前使用的传统漫反射光照更加明显,而且能够提供多种色调变化。
使用这种方式可以自由地控制物体的漫反射光照。不同的渐变纹理有不同的特性。例如,在左边的图中,我们使用一张从紫色调到浅黄色调的渐变纹理;而中间的图使用的渐变纹理则和《军团要塞2》中渲染人物使用的渐变纹理是类似的,它们都是从黑色逐渐向浅灰色靠拢,而且中间的分界线部分微微发红,这是因为画家在插画中往往会在阴影处使用这样的色调;右侧的渐变纹理则通常被用于卡通风格的渲染,这种渐变纹理中的色调通常是突变的,即没有平滑过渡,以此来模拟卡通中的阴影色块。
遮罩纹理
遮罩允许我们可以保护某些区域,使它们免于某些修改。
在之前的实现中,我们都是把高光反射应用到模型表面的所有地方,即所有的像素都使用同样大小的高光强度和高光指数。
但有时,我们希望模型表面某些区域的反光强烈一些,而某些区域弱一些。
为了得到更加细腻的效果,我们就可以使用一张遮罩纹理来控制光照。
另一种常见的应用是在制作地形材质时需要混合多张图片,例如表现草地的纹理、表现石子的纹理、表现裸露土地的纹理等,使用遮罩纹理可以控制如何混合这些纹理。
使用遮罩纹理的流程:通过采样得到遮罩纹理的纹素值,然后使用其中某个(或某几个)通道的值(例如texel.r)来与某种表面属性进行相乘,当该通道的值为0时,可以保护表面不受该属性的影响。
其他遮罩纹理
在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。
通常,我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。
例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道。