从各种文章总结了纹理以及纹理的使用。

为什么使用纹理

在介绍一个概念之前,先知道它是解决什么问题会对此概念更加印象深刻。
现在有一个模型,我们需要给它进行上色,按照最直接的想法,我们可以在这个模型上的每一个顶点存储颜色,这显然是可行的,但是这也会产生问题,比如建模工作量加大,存储的空间很大,复用性很低等等,于是这里就生成了纹理这个想法,将上色的这些信息保存在一张图片上,“贴”在模型上,可以比较完美地解决了上述的问题。

纹理是什么

纹理实际上就是一张图片,是一种可供着色器读写的结构化存储形式,程序上来说就是一个二维数组。

怎么使用纹理

当我们决定使用纹理作为模型上色的依据时,我们就要考虑怎么将纹理贴到模型上去,即我们要知道在模型上某一位置它的信息存储在纹理的哪一块。
这里就需要纹理管线来对单个纹理进行处理,整个流程就是:

模型空间位置=>通过投影函数=>纹理映射=>纹理坐标=>通讯函数=>新纹理坐标=>纹理采样(避免纹理读取)=>纹理值

这里举一个例子来更加详细地介绍纹理管线,如下图:
纹理管线.png
首先摄像机需要渲染世界上某一物体的某一点,该点在模型空间中的坐标通过投影函数转换为纹理映射(即纹理的uv值),再与纹理的大小(即图像的长宽)相乘获得纹理坐标,之后则需要根据纹理采样设置1来进行采样,其中上述的通讯函数是可以对纹理坐标进行拓展,例如旋转,缩放等。

Filter Mode(纹理过滤)

这个可以说是纹理中一个难点,当纹理由于变化而产生拉伸的时候,通过哪一种滤波模式来调整使得画面更加合理,这种拉伸的形成有很多中情况,例如摄像机通过不同角度观察,纹理与模型相比较小或较大等等,情况很多,但是大致的情况可以分成两种:纹理分辨率较小时纹理分辨率较大时(相较于屏幕分辨率)。

  1. 当纹理分辨率较小时,会导致模糊,锯齿状等情况,可采取的方法:

    • Neartest(最邻近),查找纹理元素中位置中最邻近的值,当纹理分辨率较小时,纹理坐标很容易为小数,则取相邻的整数,这就会导致画面一格一格的样子,简单但是效果并不好。
    • Bilinear(双线性插值),直接上图,下面还有线性插值的公式2
      双线性插值
    • Qu'ilez的光滑曲线插值3,可以看做是双线性插值的升级版,在进行双线性插值之前对UV坐标进行了一定的转变,下图即是转变的例子:
      插值前的坐标变化
    • Bicubic(立方卷积插值或双立方插值),取周围邻近16个,进行三次插值(非线性插值),计算量更大,效果更好4

      实际上上述的方法是按照效果排序,越下面效果越好,计算量自然越大
  2. 当纹理分辨率较大时,相对于纹理较小,这个问题更需要处理,因为在游戏中,经常会出现从远处观察某物体,如果直接去采样纹理,则会导致颜色丢失与闪烁5,可采取的方法:

    • Mipmap,对纹理进行预处理,获取分辨率更低的纹理图像,下图为生成Mipmap具体情况
      MipMap.png
      可以发现生成的Mipmap都是长宽按照1/2的方式等比缩放,通过简单的数学计算可以得出纹理的图像大小在经过Mipmap操作后增加了原纹理大小的1/3倍。
      下图表示了如何查找对应的Mipmap,例如求A像素的对应的纹理元素,则比较相邻的B、C像素点的在纹理中距离大小,取较大值L1,比较L1和Mipmap中边长值,从而选取Mipmap纹理。
      选取Mimap.png
    • Ripmap(各向异性过滤),同样是对纹理进行预处理,但是比Mipmap会生成更多图像,下图就是缩放的方式,经过各向异性过滤操作后的纹理图不仅包含了Mipmap的缩放图像,也就是红线划过的方块(即长宽等比缩放),而且包含了长宽不等比缩放的图像,所以开销是原来的三倍,可以一定程度上解决Overblur(过分模糊),但不能解决斜面问题。
      各向异性过滤.png
      上面使用Ripmap的情况,但实际上Unity/UE4使用各向异性过滤之后,纹理内存仅仅多了1/3,是因为在实际的项目中是用不起Ripmap这多3倍的内存的,所以实际使用的还是MipMap,将屏幕像素反向投影到纹理空间,根据方块的最短边确定level,较长的边则创建一条各向异性的线穿过方块中心,按照过滤等级的高低,沿着这条线进行多次的采样并合成,得到最终采样的结果,效果和使用Ripmap是差不多的。但理论上还是直接使用Ripmap效果更好。
    • Summed-Area Table(积分图),详细内容查看这篇文章(全英文)
    • EWA滤波,被广泛认为是纹理过滤算法中最好的算法,可以解决斜向观察纹理产生的问题,详细内容可以查看这篇文章

    纹理中的难点主要是解决纹理过滤问题

凹凸贴图技术

纹理不仅仅可以上色,还有很多用处,这里主要讲一讲凹凸贴图技术。

  1. Bump Mapping(凹凸贴图),实际上是一张灰度图,它仅保存了一个高度值,在光照照射时,通过该高度值对亮度和阴影进行一定的微偏移,来形成视觉错误,让人认为它是凹凸的,但是它存在局限性,几乎无法完成镜面的高光
  2. Normal Mapping(法线贴图),法线贴图也是游戏中经常使用的凹凸贴图技术,它保存的是法线值,在游戏中主要保存的是切线空间的法线值,切线空间如下图,不做过多解释,使用切线空间的原因就是制作出来的法线贴图不管是什么表面都可以使用,具有比较高的适用性。
    切线空间.png
    通过法线贴图存储的法线,可以与光线相计算,计算出来的明暗值则有更多的变化,形成视觉上的凹凸感,如下图,左边没有法线贴图,右边有法线贴图
    法线贴图的区别.png
    当然法线贴图也是有缺点的,当它视角接近水平或者放大时观察,很容易穿帮,如下图放大后观察
    法线贴图的穿帮.png
    这是因为法线贴图生成的凸起并不能遮挡我们的视线,后面的视差贴图则根据此进行了优化
  3. Parallax mapping(视差贴图),实际上就是在法线贴图的基础上,在采样纹理元素的时候,给纹理坐标一定的偏移值,将本应该遮挡的纹理元素剔除,在更近的距离或者更平的角度实现更加真实的凹凸感,详情可以查看这篇文章
  4. Displacement mapping(位移贴图),上述都是通过模拟凹凸感,模型表面并没有实际上的凹凸,而位移贴图则是真正改变了模型的表面,通过micropolygons(微多边形)tessellate(镶嵌)的技巧来实现真正的改变物体表面的细节,实现位移贴图的工作是在片元着色器上(因为顶点着色器不能生成新的顶点),对于原来的表面进行细分,位移等,详细内容可以查看《GPU Gems 2》里的part1.Chapter 7以及Chapter 8的内容

参考


  1. 包含Wrap Mode和Filter Mode(纹理过滤),Wrap Mode决定采样到uv值在[0,1]之外的数值时的表现,比如Repeat(重复),Mirror(镜像)等
  2. Lerp(x,v0,v1)=v0+x(v1-v0)
  3. 名字很奇怪,不知道是什么语言,我仅在《百人计划》介绍纹理的视频中看到过
  4. 有位大佬的文章将立方卷积插值介绍得很详细,并有JS的代码例子,很适合学习
  5. 两个相邻的像素,采样的纹理元素却跨越了半张图,相差过大,则会导致闪烁