一个三维场景的画面的好坏,百分之四十取决于模型,百分之六十取决于贴图,可见贴图在画面中所占的重要性。在这里我将列举一系列贴图,并且初步阐述其概念,理解原理的基础上制作贴图,也就顺手多了。
1. 漫反射贴图diffuse map
漫反射贴图在游戏中表现出物体表面的反射和表面颜色。换句话说,它可以表现出物体被光照射到而显出的颜色和强度。我们通过颜色和明暗来绘制一幅漫反射贴图,在这张贴图中,吸收了比较多的光线的部分比较暗,而表面反射比较强的部分,吸收的光线比较少。
刨去那些杂糅的东西,我们只谈明显的,漫反射贴图表现了什么? 列举一下,物体的固有色以及纹理,贴图上的光影。前面的固有色和纹理我们很容易理解,至于后面的光影,我们再绘制漫反射贴图的时候需要区别对待,比如我们做一堵墙,每一块砖都是用模型做出来的,那么我们就没有必要绘制砖缝,因为这个可以通过打灯光来实现。可是我们如果用模型只做了一面墙,上面的砖块是用贴图来实现,那么就得绘制出砖缝了。从美术的角度,砖缝出了事一条单独的材质带外,还有就是砖缝也是承接投影的,所以在漫反射图上,绘制出投影也是很有必要的。
没有什么物体能够反射出跟照到它身上相同强度的光。因此,让你的漫反射贴图暗一些是一个不错的想法。通常,光滑的面只有很少的光会散射,所以你的漫反射贴图可以亮一些。
漫反射贴图应用到材质中去是直接通过DiffuseMap的。再命名规范上它通常是再文件的末尾加上“_d”来标记它是漫反射贴图。
2. 凹凸贴图Bump maps
凸凹贴图可以给贴图增加立体感。它其实并不能改变模型的形状,而是通过影响模型表面的影子来达到凸凹效果的。再游戏中有两种不同类型的凸凹贴图,法线贴图(normalmap)和高度贴图(highmap)。
Normal maps法线贴图
法线贴图定义了一个表面的倾斜度或者法线。换一种说法,他们改变了我们所看到的表面的倾斜度。
法线贴图把空间坐标的参数(X,Y,Z)记录在像素中(R,G,B)。
有两种制作法线贴图的方法:
1.从三维的模型渲染出一张法线贴图 (用高模跟低模重叠在一起,把高模上的细节烘焙到低模的UV上,这里需要低模有一个不能重叠的UV)
2.转换一张高度贴图成为一个法线贴图。(是用NVIDIA的PS插件来转换一张图成为法线贴图)
种类
1.世界空间下的法线纹理
2.模型空间下的法线纹理
3.切线空间下的法线纹理
对比不同的法线贴图
首先按照不同的坐标系得到了不同类型的法线贴图。模型顶点的法线根据所处坐标系的不同,在成图后表现也是不同的。以模型法线贴图和切线法线贴图为例:
不同的颜色是因为法线作为一个Vector3类型的需要转换到2D的颜色,而通常来说我们可以把颜色的RGB看成一个坐标系,这样法线就对应了一个RGB颜色。但是法线的范围是[-1,1],而颜色是没有赋值的,因此有以下的转换过程。
法线纹理存储的是表面的法线方向。由于法线方向的分量范围在[-1, 1],而像素的分量范围为[0, 1],因此我们需要做一个映射,通常使用的映射就是:
pixel=(normal + 1) / 2
这就要求,我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
normal=pixel × 2 - 1
1.world space normal map
一旦从贴图里解压出来后,就可以直接用了,效率很高.但是有个缺点,这个world space normal 是固定了,如果物体没有保持原来的方向和位置,那原来生成的normal map就作废了。
世界坐标下的顶点的法线是现成的,因此好用。不过如果进入模型在U3D里面进行了位置或者方向的转换,那么在没有转换矩阵的情况下,法线信息就是错误的,也就无法使用了。进一步思考如果场景中存在大量的静态模型,可以考虑用这个。
2.object space normal map
对于模型顶点自带的法线,它们是定义在模型空间中的,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为模型空间的法线纹理(object-space normal map)。
对象空间的法线贴图,这个贴图中记录的法线信息是基于模型空间的,因此数值是相对的,这样模型在场景中是可以位移和旋转的,只要在计算的时候乘上对应的矩阵即可。而且从上面的图里可以看到贴图是彩色的,因为模型上的顶点法线在这个空间中是朝各个方向的。
对象空间的法线贴图比起世界空间的在使用了已经有了很大的进步,不过它仍旧有自己的局限性,就是这样的贴图还是依赖于模型本身。从其定义也可以看出来,它也是一种“绝对位置”。如果模型发生了形变,则这个贴图的信息就是错误的。从目前来看,我只能想到模型在场景中存在动画这个问题。
3.tangent space normal map
对于模型的每一个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为副切线(bitangent, b)或副法线。这种纹理被称为是切线空间的法线纹理(tangent-space normal map)。
定义很好理解,从本质上讲切线空间的法线贴图解决了模型变形的问题。因为在另一个模型上的顶点的切线空间坐标系里面,法线贴图中的信息是可以使用的,它不依赖于模型本身、也不依赖于模型所处的坐标系。
4.切线空间下的法线贴图如何生成
以3DMAX为例,法线贴图需要高模和低模配合,具体过程不说了,网上大把的视频。
重点是法线贴图的生成需要高模和低模,因为没有高模就不知道法线方向,没有低模,就不知道高模上某点的法线对应于低模上哪个点。
下面才是重点,因为多个高模的面使用了同一个低模的面,因此在生成法线贴图时,高模不能使用自己的tangent space,而是使用低模的tangent space。这样一些高模上的点的法线与低模面上的法线出现了不一致,你可以想象低模上的某个面上的法线指向一个方向,但是对应了几个高模的点,可能一些点的法线与低模面的法线方向一样,那么很好,完美融入低模的切线空间,颜色呈现出淡蓝色。但是其它那些和低模面法线不一致的点的法线就产生了夹角,就造成了在切线法线贴图上那些不是[0.5,0.5,1]色值的点。
低模上的这个tangent space,也必须与高模上的坐标系tangent space。因为低模上的一个面,可能对应了高模上的几个面(精度高),按照新方法每个面都有一个局部坐标系,那对于低模上的每个面,高模因为存在好几个面,就会出现好几个局部坐标系,这肯定是不行的。所以高模所用的tangent space,就是低模上的。生成法线贴图,必定会确认高模上哪些面都对应低模上的哪个面,然后高模上的这几个面的法线,都会转换为低模这个面上所构建的tangent space的坐标。这样,当低模变形时,即三角面变化时,它的tangent space也会跟着变化,保存在贴图里的法线乘以低模这个面的tangent space到外部坐标系的转换矩阵即可得到外部坐标。顺便再提一点,高模保存的这个法线,是高模上object space里的法线。
对于上述的内容,这张图做了很好的诠释。
如何将高模的法线贴图用在低模上
对于object space normal map,低模的object space坐标系与高模中的object space坐标系是重合的。所以不需要构建,所以低模上某点才能直接用高模的法线替换自己的法线。
Height maps高度贴图
什么是HeightMap呢?所谓高度图实际上就是一个2D数组。创建地形为什么需要高度图呢?可以这样考虑,地形实际上就是一系列高度不同的网格而已,这样数组中每个元素的索引值刚好可以用来定位不同的网格(x,y),而所储存的值就是网格的高度(z)。
我们在这里叙述高度图,其实也是为了更好的绘制法线贴图,很多情况下我们的法线贴图只能在已有的漫反射贴图作为素材进行绘制,这样就是需要由一个HeightMap转换成法线贴图的一个过程,明白了这个原理,做起来也就可以更好的驾驭其效果。
高度贴图是一种黑白的图像,它通过像素来定义模型表面的高度。越亮的地方它的高度就越高,画面越白的地方越高,越黑的地方越低,灰色的在中间,从而表现不同的地形。
高度贴图通常是在图形处理软件中绘制的。他们通常没有必要渲染这些,再DOOM3游戏中高度贴图是被转换成法线贴图来使用的。使用高度贴图仅仅是为了适应简单的工作流程。高度贴图通常通过“Heightmap”函数来调用到3D软件中去的,我们通常再文件名后面加一个"_h"来标示它。
Normal maps vs. height maps
法线贴图和高度贴图
一般来说,Normal Map来自于Height Map。具体生成的方法如下:
把Height Map的每个像素和它上面的一个像素相减,得到一个高度差,作为该点法线的x值;
把Height Map的每个像素和它右边的一个像素相减,得到一个高度差,作为该点法线的y值;
取1作为该点法线的z值。
另种推导过程如下:
x方向,每个像素和它下面的一个像素相减,得到向量<1, 0, hb - ha>,其中ha是该像素的高度值,hb是下一行的高度值;
y方向,每个像素和它左边的一个像素相减,得到向量<0, 1, hc - ha>,其中ha是该像素的高度值,hc是左一列的高度值;
两个向量Cross,得到
简单来说,就是取两个方向的切线向量,对它们做Cross得到该点的法线向量。
还有第三种做法,是根据每个象素四边的点计算,而该点象素本身不参与计算。计算结果大同小异。
而且我觉得这种计算只适合于单块的HeightMap、NormalMap,像是DOOM3中的NormalMap就无法由HeightMap计算出来了。所以最好还是在美工建模的时候同时生成NormalMap和HeightMap而不是利用HeightMap生成NormalMap。
DOOM3游戏引擎可以把法线贴图和高度贴图合成在一张凸凹贴图上。
通常我们绘制一张具有足够细节的高度贴图要比建立一个足够细节的模型然后渲染成相应的法线贴图要实际的多。
法线和高度的凸凹贴图可以通过Addnormals函数来合并到一种材质中。
毫无疑问,高度贴图大多数游戏引擎中出现的不多。他们只是给电脑一种方法来计算曲面法线当使用动态灯光的时候。
这说明实际上,一张高度贴图被转换成一张法线贴图,以此可以计算出相邻两块不同高度的位置之间的倾斜面。高度贴图永远不能像法线贴图这样具有足够的细节,这是被肯定的。
很明显只有灰度的高度贴图并不能很好的表现应该有的细节,因为它是黑白的,RGB颜色就会遭到浪费,并且因此你只能只用256层级的强度。
相比较来言法线贴图的每一个图像通道都可以利用到,显而易见,法线贴图能够更好的来表现凸凹。
3. Specular maps高光贴图
什么是高光贴图?
高光贴图是用来表现当光线照射到模型表面时,其表面属性的.(如金属和皮肤、布、塑料反射不同量的光)从而区分不同材质.
高光贴图再引擎中表现镜面反射和物体表面的高光颜色。
材质的反光程度就越强。(强弱度度是指,如果将这张Specularmap去色成为黑白图,图上越偏向RGB0,0,0,的部分高光越弱,越偏向RGB255,255,255的部分高光越强.)
我们建立高光贴图的时候,我们使用solid value来表现普通表面的反射,而暗的地方则会给人一种侵蚀风化的反射效果。(你头脑中要有很清晰的物件不同材质之间高光强弱的关系:高光最强的是那个部分,最弱的是那个部分,处在中间级别的是哪些部分.一般来说:金属的高光>塑料>木头>皮肤>不料,但是这个只是一个大致的分类,不要把它作为高光的指导.有时,你处理的物件绝大部分都是同一类型材质的,比如布料,这时你也要小心的去分辨不同材料之间的高光强度的区别.切记,在这个阶段一定要保持清晰的头脑,不要急着去添加那些细节.在大的强弱关系还没有决定之前,就去添加那些细节会影响你的判断,而最后得到一张层次不清晰很“花”的高光.很多时候,我们容易犯这样的毛病,就是将物件的高光处理的太过单一.)
拿一面墙的贴图举例,砖的表面与砖缝相比将会有比较少的反光,但是砖缝的位置其实应该几乎是没有反光的。(确定好整体高光的强弱之后,就开始在高光上叠加细节:比如金属划痕,金属倒角高光,锈渍周围的裸金属亮点,油渍,灰尘等.这时,你会发现,如果你在Diffusemap的绘制过程中,保留了纹理,划痕或以上提到过的细节的图层,你只需要将Diffusemap中的相应图层拖曳到Specularmap中,然后根据这些细节应该反映出来的高光强度调节就可以了.So,良好的图层管理习惯是非常必要的.)
颜色再高光贴图中将会用来定义高光的颜色,组成砖的材料应该是一些沙子,他们将会反射出一些微笑的具有质感的光,这些在上面的例子中已经展示了出来。(为了丰富高光贴图,我们有很多方法:做局部高光的细微变化,添加纹理(这个纹理要和材质本身的纹理区分开),叠加彩色图层(谨慎用))
高光贴图是通过Specularmap函数调用到引擎中的,通常我们再贴图的后面加一个"_s"来区别它。
凸凹贴图可以通过高光贴图来改进成相当漂亮的贴图。(要记住的是,单单凭借高光贴图是无法充分的表现材质特性的,只有Didffuse,Normal,和Specular三张配合才能充分的表现材质特性.)
﹫﹫在UNITY中,高光贴图通常放在漫反射贴图的透明通道里,我们是用相关的SHANDER就可以达到高光的效果。
4. AO贴图
Ambient Occlusiont简称AO贴图,中文一般叫做环境阻塞贴图。是一种目前次时代游戏中常用的贴图技术,很多朋友将其与全局光烘焙贴图混淆,其实二者本质是完全不同的。
首先,我们从简单的AO贴图的算法来讲:
AO贴图的计算是不受任何光线影响的,仅仅计算物体间的距离,并根据距离产生一个8位的通道。计算物体的AO贴图的时候,程序使每个像素,根据物体的法线,发射出一条光,这个光碰触到物体的时候,就会产生反馈,标记这里附近有物体,就呈现黑色。而球上方的像素所发射的光,没有碰触到任何物体,因此标记为白色。
简单了解算法后,大家就明白,全局光的烘焙师模拟GI(全局光)所呈现的阴影效果,而AO贴图时模拟模型的各个面之间的距离。二者性质是完全不一样的。
我举例简单对比AO贴图和GI阴影贴图的区别。
根据这个低模,右边计算出的AO贴图的黑白关系,是根据物体模型距离产生的,不存在任何光源效果的影响,边缘部分等比较密集的结构,正确的产生了深色,强化了模型结构,在游戏引擎中,与其他通道贴图混合,可以提升游戏的效果。
右边的是全局光烘焙贴图的效果,是用MAX的天光计算结果进行烘焙,其阴影效果是模拟自然光线下的模型光影关系,在有结构接近的区域(比如裤袋、袖口)由于GI得光线跟踪计算会使其弱化,符合自然界光线效果,但是不是游戏所需要的效果。
在unity中,我们有两个地方可以调整AO,一个是在光照贴图渲染器中,有一个调整AO的参数,这个是确实渲染了一层AO。还有一个就是通过摄影机特效,有一个屏幕空间环境阻塞的特效screen speace ambient occlusion(SSAO).这两个都可以实现部分的AO效果,有空可以自己尝试一下。
5. 环境贴图CUBEMAP
Cube map技术说到底就是用一个虚拟的立方体(cube)包围住物体,眼睛到物体某处的向量eyevec经过反射(以该处的法线为对称轴),反射向量reflectvec射到立方体上,就在该立方体上获得一个纹素了(见下图)。明显,我们需要一个类似天空盒般的6张纹理贴在这个虚拟的立方体上。按CUBE MAPPING原意,就是一种enviroment map,因此把周围场景渲染到这6张纹理里是“正统”的。也就是每次渲染时,都作一次离线渲染,分别在每个矩形中心放置相机“拍下”场景,用FBO渲染到纹理,然后把这张纹理作为一个cube map对象的六纹理之一。这样即使是动态之物也能被映射到物体表面了(虽然缺点是不能映射物体自身的任何部分)。
CUBEMAP的制作方法:
http://www.cgtextures.com/content.php?action=tutorial&name=cubemaps
unity3d的官网上有一段代码,叫做Camera.RenderToCubemap
讲的是怎样把我们的场景烘焙成cubemap,里面附有代码,有兴趣的可以在SCRIPT帮助文件中搜索我上一行提到的关键词。
6. 光照纹理LIGHTMAP
什么是烘焙? 简单地说, 就是把物体光照的明暗信息保存到纹理上, 实时绘制时不再进行光照计算, 而是采用预先生成的光照纹理(lightmap)来表示明暗效果. 那么, 这样有什么意义呢?
好处:由于省去了光照计算, 可以提高绘制速度
对于一些过度复杂的光照(如光线追踪, 辐射度, AO等算法), 实时计算不太现实. 如果预先计算好保存到纹理上, 这样无疑可以大大提高模型的光影效果,保存下来的lightmap还可以进行二次处理, 如做一下模糊, 让阴影边缘更加柔和
当然, 缺点也是有的:
模型额外多了一层纹理, 这样相当于增加了资源的管理成本(异步装载, 版本控制, 文件体积等). 当然, 也可以选择把明暗信息写回原纹理, 但这样限制比较多, 如纹理坐标范围, 物体实例个数...
模型需要隔外一层可以展开到一张纹理平面的UV(范围只能是[0,1], 不能重合). 如果原模型本身就是这样, 可以结省掉. 但对于大多数模型来说, 可能会采用WRAP/MIRROR寻址, 这只能再做一层, 再说不能强制每个模型只用一张纹理吧? 所以, lightmap的UV需要美术多做一层, 程序展开算法这里不提及....
静态的光影效果与对动态的光影没法很好的结合. 如果光照方向改变了的话, 静态光影效果是无法进行变换的. 而且对于静态的阴影, 没法直接影响到动态的模型. 这一点, 反而影响了真实度。
那么怎么生成lightmap呢?
最直接的办法: 光线追踪....(原理想想很简单, 按照物体定律来就可以了)速度较慢。
下面说的这个是利用GPU进行计算的, 跟实时光照没什么两样:
原理:
想想实时渲染的顶点变换流程: pos * WVP之后, 顶点坐标就变换到屏幕空间了[-1, 1]
如果VertexShader里直接把纹理坐标做为变换结果输出(注意从[0,1]变换到[-1,1]), 那么相当于直接变换到了纹理坐标系, 这时在PixelShader里还是像原来那样计算光照, 输出的结果就可以拿来做lightmap了
静态模型的Lightmap(光照贴图)与Vertex-Lighting(顶点光照)之比较
通常有个误解就是,Vertex-Lighting是一种不费的静态模型打光手段,因此应该被作为提升地图运行效率和减少文件尺寸的手段。这种观点,在这两方面其实都有问题Lightmap使用平展开的一套UV,如同普通皮肤贴图所需的。Lightmap的贴图大小可以灵活设置,比如64x64。这种方式提供了每像素的光照数据Vertex-Lighting使用的数据结构,包含每个顶点所受光照的亮度和色彩信息。
该数据结构消耗特定量的内存,这个量是由模型的顶点数量决定的,不能随意改变在多数情况下,静态模型应该设成使用Lightmap,因为这可以产生最好的视觉效果,最好的运行效率,而且比Vertex-Lighting消耗更少的内存Lightmap和Vertex-Lighting相比较,具有如下优点:- Lightmap可以减少CPU和GPU的占用- Lightmap让CPU需要计算的光照和物体间的互动更少- Lightmap不需要在GPU的多重pass中被渲染- Lightmap pass被整合进Emissive(自发光)pass中,因此可以缩短渲染时间- Lightmap可以表现交错覆盖于静态模型三角面上的复杂的每像素光照,然而Vertex-Lighting只能表现顶点到顶点之间线形的渐变- 使用Lightmap的静态模型,可以通过优化使用更少的三角形,获得额外的效率提升。
为使用Vertex-Lighting而制作的模型,通常需要较高的细分度,获得更多的顶点来改善顶点之间的光照过渡,然而这种做法的副作用是提升了模型的三角形数量并影响运行效率- 静态模型上的Lightmap可以设置为使用很小的分辨率,比如16x16或32x32,来减少内存开支。这对于远离游戏中心区域的静态模型来说,非常有用,这同样也适合受光很均匀的模型。
Vertex-Lighting就不具有这种优化的便利,它总是消耗同样数量的内存来存放模型全部顶点的数据结构- Lightmap可以通过调整UV的布局,来进行优化以提供尽可能好的光照质量。比如,有一个球形岩石,可以将它的底部的三角形的UV尺寸做得很小,从而让这部分在整个Lightmap的UV上面只占据很小一块,这样,对于顶部和侧面来说,就获得了更大的贴图面积于是有更精细的光照效果。Vertex-Lighting的精度总是对应于顶点数,而效果又受模型实际大小的影响(就是说缩小了看还可以的模型,放大比如一百倍,由于顶点不能改变,所以效果也变糙一百倍,而Lightmap因为可以灵活设置精度不存在这个问题),并且不能被优化如果静态模型的三角形和顶点数量很少的话,那使用Vertex-Lighting可能会比使用Lightmap占用更少的内存,然而,使用Lightmap绝对是看起来更好的,效率也更高的。
使用Lightmap让LD可以优化光照的质量和内存的占用所以Lightmap显然是比Vertex-Lighting更好的选择举个例子:比如使用UT3这游戏的静态模型HU_Deco_Pipes.SM.Mesh.S_HU_Deco_Pipes_SM_Pipe01该模型有2555个三角形和2393个顶点如果在场景中放置此模型的420个实例,并且都使用Vertex-Lighting,那么总共消耗11MB内存如果在场景中放置此模型的420个实例,并且都使用32x32的Lightmap,那么总共消耗850kb内存如果在场景中放置此模型的420个实例,并且都使用64x64的Lightmap,那么总共消耗3.3MB内存占用内存的量,也会在地图文件的尺寸上有所表现这个例子中的一部分实例,其所用的Lightmap的精度,可以设到128x128或者更高以便获得最佳的光照效果,而仍然使用相比Vertex-Lighting来说更少的内存。并且使用Lightmap的版本,要比Vertex-Lighting版本在渲染上快8-10个百分点。
7. Mipmap和detailmap
首先从MIPMAP的原理说起,它是把一张贴图按照2的倍数进行缩小。直到1X1。把缩小的图都存储起来。在渲染时,根据一个像素离眼睛为之的距离,来判断从一个合适的图层中取出texel颜色赋值给像素。在D3D和OGL都有相对应的API控制接口。
透过它的工作原理我们可以发现,硬件总是根据眼睛到目标的距离,来玄奇最适合当前屏幕像素分辨率的图层。假设一张32768x32768的mipmap贴图,当前屏幕分辨率为1024*1024。眼睛距离物体比较近时,mipmap最大也只可能从1024*1024的Mipmap图层选取texel。再次,当使用三线性过滤(trilinear)时,最大也只能访问2048*2048的图层选取texel,来和1024*1024图层中的像素进行线性插值。
DetailMAP
顾名思义,就是细节的贴图,原理上不用赘述,其实就是图层的叠加与混合。在这里有几个关键词,一个是Detail的Tiling值,一个是这个Detailmap需要在导入的时候设置为Mipmap,里面的参数大家可以试着调一下,至于Mipmap的原理,已经在上面介绍了。