目录
1 纹理三大问
1.1 What——针对物体表面属性进行“建模”
纹理 Texture
纹理值 Texture Value
纹素 Texels
1.2 Why——降低建模工作量
1.3 How——屏幕空间 <- 世界空间 -> 纹理空间
通过实例解释纹理管线的应用
纹理怎么参与到渲染管线中
2 纹理管线 The Texturing Pipeline
贴图/映射 Mapping
纹理贴图/纹理映射 Texture Mapping / 材质贴图
2.1 空间中的位置 -> 参数空间坐标
投影函数 The Projector Function
纹理空间 Texture space
UV 纹理坐标
UV贴图 UV Mapping
UV的取值及意义
展UV / UV展开
UV映射
2.2 纹理坐标 -> 纹理空间位置
为什么采样会超范围
映射函数 The Corresponder Function
2.3 纹理采样:纹理空间位置 -> 纹理值
纹理采样设置1:封装/寻址模式 Wrap Mode
纹理采样设置2:过滤模式 Filter Mode
2.4 变换纹理值
学习的教程
【技术美术百人计划】图形 1.3 纹理的秘密_哔哩哔哩_bilibili
辅助书籍参考
《Real-Time Rendering 3rd》提炼总结 -毛星云
《Unity Shader入门精要》-冯乐乐
学前思考
May佬给出了一些思路:纹理贴图的原理,附带的一些优化手段、优化效果等。
这之前已经把101学完了,202看了一部分,至于为什么想要从头起一篇博客,因为之前学习101时笔记都是写在了本子上,比较零散,同时最近了解到面试还是会对理论知识有要求的,希望以新手的角度重新全面完整的总结理论部分,巩固知识的同时,以后自己需要复习的时候可以回来再翻翻看。
这篇博客是总结理论知识向,会尽可能地把涉及到的名词介绍清楚,参考了RTR4书中的内容和配图。 之后会更新——纹理基础(结合Unity),实践向,跟着冯乐乐书里的例子做一些简单的尝试。
教程布置的任务
1.Fliter Mode有哪几种?(看完这一篇博客就能够回答了!)
2.纹理贴图的优化方式及原理?
纹理是一种针对物体表面属性进行“建模”的高效技术。我们通俗都说纹理是一张图片,生活中的任何一张图片都可以当作一张纹理来使用,纹理的最初目的也是为了达到在渲染中使用一张图片来控制模型外观,定义其颜色的效果。
微观角度来看,纹理就是一个容器,是一种供着色器读写的结构化储存形式。例如之前做101时,纹理会被以[i][j]的坐标访问并获得k值(比如说作业3给出的代码框架)。[i][j]代表纹理上的一个Texel,k则代表这个Texel储存的RGBA(假设当前纹理储存的是RGBA颜色值)的信息。
但是!严格意义上讲,纹理又≠图片,既然它是一种结构化储存形式,那么纹理就可以用来储存任何的物体表面属性。不仅可以实现上面提到储存颜色信息——“通过一张纹理来控制物体漫反射颜色”,还可以实现高度、法线等表面信息,同时它还会包含一些纹理采样的设置。同样,纹理也不局限于二维的,它同样可以是三维的,例如cubemap、程序纹理等。
既然说了纹理,那先提前介绍一下什么是纹理值吧。纹理值就是储存在纹理中的表面属性数据,一般常见的是RGBA/RGB颜色值。
“纹理上的像素”。
区别于屏幕上的像素,图像纹理中的像素有它自己的名字,通常被称为纹素。
我们如果按照物体的真实模样建模,工作量细节超大,储存空间也超大。如果只用一个贴图来假装体现表面特性,虽然牺牲了一些几何细节,但是工作量、储存空间和读取速度都大幅度提升了。
下图就很简单的表示了纹理实现的方式,想要在3D的地球仪上直接绘制纹理实在是麻烦,要不就在绘制之前先给空白的模型“剥开”得到一个2D图像,在这个2D图像上绘制就十分容易了:
从3D的World space映射到2D的Texture space,点与点对应,找到在World space中绘制点在Texture space中对应的点,把颜色值储存给Texture space中对应的点就行。这样在每次进行渲染的时候,根据映射关系查找模型上点在Texture中的颜色值,所有的点都计算完毕后,就能在Screen space中得到想要的贴上了Texutre的模型样子。
上面所描述的,就是纹理映射要干的事了,就像渲染有渲染管线,纹理的实现也有对应的纹理管线。
我们通过纹理管线实现纹理的应用,管线每一步的细节将在下一章节——第2章详细讲述。我在学习的时候一开始真得分不清每一步到底是针对物体模型还是针对纹理图像,这里就想通过RTR中砖墙的实例(下图),用大白话先解释一下纹理管线到底是怎么一回事。
第一步:将物体模型(实例这里是一个想要贴上“砖墙”纹理的模型)上的一点位置(-2.3, 7.1, 88.2)运用投影函数转化成参数空间中的uv坐标(0.32, 0.29)。即,将砖墙模型上的点映射到0到1之间的一对(u,v)值。因此,这个投影其实是从模型 --> 参数空间的过程,可以理解成纹理与模型间的桥梁。
第二步:得到这个uv对应坐标值了,根据我们现有的纹理图像的大小(分辨率),将uv坐标(0.32, 0.29)乘以宽、高得到对应的纹理空间位置(81, 74),接下来就可通过这个纹理位置在纹理图像上查找对应纹素储存的表面属性值了(实例中就是颜色值RGB),取得颜色值(0.9, 0.8, 0.7)。这一步是在纹理图像上进行的操作过程。
第三步:得到的颜色可以看成一个基础值,如果想要调整颜色的明暗等实现特有的效果,需要用到变换函数,对这个属性值做一个处理,得到最终的结果,这一步也是基于纹理图像操作的。
这部分参考自纹理那些事儿 - 知乎 (zhihu.com)
渲染管线(后续会再开一个博客总结)中,一般会在渲染管线——片元着色阶段使用纹理。建模时会给每个模型顶点分配一个纹理坐标,在进入渲染管线——光栅化阶段时,生成的每个片元会根据每个顶点对应的纹理坐标差值得到新的纹理坐标,根据这个新的纹理坐标对传入的纹理图像数据进行采样获取纹理值。
基于上述对纹理管线应用的叙述,下图展示了单个纹理应用纹理贴图的更加详细过程,“如何将纹理贴在模型上”。这个管线虽然看起来复杂,但每一步都为用户提供了有效的控制手段。
通过将投影函数(projector function)运用于空间中的点,从而得到一组叫做参数空间值(parameter spacevalues)的关于纹理的值,这个过程就成为贴图/映射。
纹理映射、纹理贴图、材质贴图描述的是同一个过程,上述展示的通用管线就是用来描述纹理贴图这一过程的,图形学中是指把储存在内存里的位图(Bitmap)包裹到3D模型表面上的过程,通俗一点,是指将图形绘制到物体表面的技术,为模型增加细节和真实感。
这一步利用投影函数实现:将空间中点位置(x,y,z)转换成一个值域为0到1之间的二元坐标(u,v)。
注意:这里的投影并不是渲染中的摄像机投影,而是指纹理投影。
功能:将空间中的三维点(获取表面位置)转化成纹理坐标(投影到参数空间中)。一般投影函数在美术阶段使用,将投影结果储存在顶点数据中,开发也是直接拿到美术储存在顶点中的投影结果数据直接进行使用。特殊情况下可以在顶点或者像素着色器中使用,实现各种效果,比如环境贴图(environment mapping)有特定的投影函数,针对每个顶点或者每个像素进行计算。
常见的投影函数有球形、圆柱和平面投影。
同一个模型的不同部位,比如某座雕像的帽子、头、手、身体等可以使用不同的投影函数将对应的纹理图像以不同的方式投射到同一个模型上。
纹理空间是针对纹理贴图的二维坐标空间,规范在了0 - 1的单位坐标系内,与屏幕的分辨率、宽高比无关。我理解的纹理空间既属于纹理坐标,又属于由纹理坐标得到的纹理空间位置,即纹素所在(?不太清楚对不对,感觉也没必要钻牛角尖,知道有这么一个概念就行了吧)。
每个纹素在纹理中都有一个唯一的地址,可以被看作是一个列和行的值,用U和V来表示,二者组合在一起的(u, v)就是一个纹理坐标。美术建模时会为每个顶点分配一个纹理坐标,所有的纹理坐标都在纹理空间中,与纹理空间(0, 0)位置相对应。
UV贴图就是通过投影函数得到的纹理坐标点组成的二维图像,是3D模型表面的平面表示。贴图上的每个点精确对应到物体模型表面,点与点之间的间隙再做插值处理。上述提到的(u,v)坐标其实就是这张图像上的某点的坐标值。
UV中:U代表水平方向(屏幕水平),V代表竖直方向(屏幕竖直),UV就代表了2D纹理的轴。但其实,完整一点来说应该叫UVW,W是垂直于屏幕的方向,UVW用于程序贴图或其他的3D贴图中,当前提到的UV一直都是指的二维贴图。
一张纹理图像的大小可以是256X256也可以是1024X1024,为了达到不改变纹理坐标就能处理不同尺寸的纹理图像的目的,(u,v)坐标轴大小值域被归一化到了[0,1)之间:
u -> 水平方向上的第u个像素/图像宽度
v -> 竖直方向上的第v个像素/图像高度
这样,纹理坐标乘以纹理图像的宽度和高度就能得到纹理图像中对应纹素坐标了(纹理空间位置)。理解这个0和1到底是啥含义,对理解后续的映射有很大的帮助!
有一点值得注意:虽然UV值域在[0, 1)内,但纹理采样使用的纹理坐标不一定在[0, 1)内,这与后面会提到的wrap mode有很大的关联。
创建UV贴图的过程就称为UV展开/展UV,也就是模型师们在建模软件中把模型外壳“剥下来”的过程。模型做好之后想要给它做材质等必须要经过展UV这一步骤,实际操作中展UV通常会用到UVLayout、TexTools等软件。
大概理解一下整个过程:模型建好后同时会创建多边形网格,然后模型师们会规划“缝隙”,接着沿缝隙把3D的多边形网格展开成一张张平铺的二维UV贴图,就像博客Maya角色UV展平技巧笔记_dd23333333的博客-CSDN博客_maya展uv中展示的这样:
有的博客是这样描述的:UV映射是将2D图像投影到3D模型的一个面(或多个面)上以进行纹理映射的3D建模过程,更专业的讲,是把位图贴在3D模型上的这一过程或者方法。
我问了美术的小伙伴,她的描述也没有说把UV映射和纹理映射二者区分的特别开。除了上面的说法,还有博客这样描述:“UV映射是一个用来2D图片纹理转换3D网格的标准技术。”
我的理解可能是UV映射是纹理映射的一种表述方式(?不知道正确与否,就不深究了,暂且先这样认为,如有错请指正~)
上述展开的UV贴图是空白的、没有任何风格的。
为了实现纹理贴在模型上,下一步需要做的是:将得到的(u, v)纹理坐标转换为纹理图像中纹素的坐标(纹理空间位置),这一步是由程序去实现的。
那么问题来了:有时采样的顶点纹理坐标可能比1.0大(比0.0小),当分配给顶点的纹理坐标不在0.0到1.0,这个时候就需要映射函数了。
在继续介绍映射函数时,先举个例子解释一下为什么会出现采样超出[0, 1)的情况:
还是拿一面墙的例子,美术可能只制作了一块砖的纹理图像,但是模型是由很多块砖堆起来的一整面墙,这个时候我们就会根据模型大小放大顶点纹理坐标,比如边缘的uv坐标(0,1)经过放大后成了(0, 3)。后续纹理采样时就可以重复采样同一个砖的纹理图像了。
把纹理坐标放大后再进行采样时,就会出现采样超范围的情况了。
RTR4书的原话:“one or more corresponder functions can be used to transform the texture coordinates to texture space”
OpenGL中,这类映射函数被称为——Wrap Mode 封装模式
Direct3D中,这类映射函数被称为——Texture Addressing Mode 纹理寻址模式
常见的映射函数有:
下图是RTR中举出的这四个寻址模式的例子
下图是DX12纹理篇:纹理采样 - 知乎 (zhihu.com)中展示的更加清楚的四种寻址模式:
这一步就根据2.2中得到的纹素坐标,直接去获取纹理图像对应位置储存的颜色值,这个过程就是纹理采样了。纹理采样需要进行两块内容的设置:
这块儿就是上面的映射函数部分。
其实映射函数这一部分就是在为采样做准备的,只是在纹理管线中被划分在了uv向纹理位置转换这一过程中,因此这里就以Wrap Mode加入2.3中作为一个重点部分体现出来。
看到Filtering是不是非常熟悉了!没错!Games101采样部分闫神讲得超级详细:Lecture 06 Rasterization 2 (Antialiasing and Z-Buffering)_哔哩哔哩_bilibili
各种处理纹理过大、纹理过小方法也已经有很多人总结的非常详细的笔记:
图形学底层探秘 - 纹理采样、环绕、过滤与Mipmap的那些事 - 知乎 (zhihu.com)
RTR4-纹理处理(Texturing) - 知乎 (zhihu.com)
我就打算简单介绍一下过滤都有哪些方法。
过滤的目的
将浮点型纹理坐标转换为整数的坐标,并对采样结果进行处理。如果不进行滤波处理,显示的纹理图形会有重叠、错位等问题。
拿到一块纹理图像后,面对的问题无非两种:纹理过大,纹理过小。
纹理过小——放大纹理 Magnification
常用的基础过滤技术有这三个
百人计划的纹理专题也是这么说的,可以很直观的感受三者的效果差异。
纹理过大——缩小纹理 Mnification
缩小纹理可能会导致多个纹理覆盖同一个像素,纹理缩小的过程比放大更复杂。用最邻近和双线性插值的基础过滤方法会因为采样率过低出现颜色丢失和闪烁的问题,百人计划的纹理学习那节课说了这个问题:
常用的处理技术是Mipmap——多级渐远纹理。(101中老师说它是个image pyramid 图像金字塔)
Mipmap原理
我们知道,在采样纹理时,如果手里的纹理图像跟图形大小越接近效果越好。Mipmap是预先生成很多更小的图像,形成一个图像金字塔,每一层更小的都是上一层降采样的结果。
当我们得到了每个level的图之后,采样时直接通过level访问每一级的图像就行!
Mipmap优点
Mipmap只占用多了33%(1/3)的空间,它节约显存宽带,每次访问也不需要传入所有的图。一般Mipmap+双线性过滤,就可以有效消除远处物体出现纹理重叠的现象。
Mipmap不足
101中也提到了,Mipmap会有远处物体过度模糊(overblur)的缺陷。
那么为什么呢?即使是Mipmap叠加三线性过滤后,由于Mipmap是个正方形的预处理,屏幕上近似正方形的图形才能获得比较好的效果。但对于倾斜的、长条状的图形,效果就不理想了,也就是出现上面的过度模糊的问题。
解决这一不足的方法是——各向异性过滤(是一类方法的统称不单单指Ripmap),不再是正方形的预处理,而是做了各种比例的矩形预处理(感觉是类似矩形不完全是矩形)。
比较用的多的各向异性过滤方法是Ripmap。
这一步相当于一个进一步的处理,比如想要纹理比较暗、亮之类的,给他一个变换函数。
后续会有2.0,介绍体纹理、立方贴图、程序贴图、贴图优化方法等纹理基础内容。