聊一聊一些荧幕和现场背后的图像故事(21)--贴图
贴图是计算机建模呈像技术的一部分
细分有材质贴图,纹理贴图,法线贴图,环境贴图
那么我们来整理下吧
我们一直会听到大大小小的消息流出所谓“建模”
那到底啥是建模那,
建模最简单通俗的解释就是,用你手边的3维绘图工具,在三维角度绘制出一个角色/物件
这个过程本身

受制于工具的选择 主流的有MAYA C4D blender Zbrush
建模的达成方式不尽相同,但目的非常直观,就是构架3D模型本身,可以是景物的,建筑的,人物的,指示牌的。

3D影视动画以及游戏制作过程中的一个环节,即:用ps等平面软件制作材质平面图,覆于利用Maya、3DMax等3D制作软件建立的立体模型上的过程,称为贴图。
常用格式为pdd\jpg\tif\psd



这么多概念,无非是为了完成一个共同目标:用计算机表现真实可信的 Shading
Shading 是真实世界中的光影效果,它是由物体表面材质、灯光、观察者的视角等多种因素共同决定的。要实现计算机的模拟生成,是一个非常复杂的过程。不过它的原理大概可以简化为一个函数:Intensity = Material (Light, Eye)
也就是说,光影的强度,是由 a.照在材质上的光 b.视线 共同决定的。
如何制造 Shading 效果?
万物看起来不尽相同,它们有各自的材质和纹理。
纹理(Texture)是什么?
纹理就是一段有规律、可重复的图像。利用纹理,我们可以非常取巧地让三维物体看起来更真实。
贴图可以说是最简单的材质方法:
选定物体表面的某些区域
更改这个区域的一些属性(如颜色、反光度、透明度等
那么 UV Mapping 又是什么?跟 Texture Mapping 有什么区别呢?
可以这么理解,Texture Mapping 是目标,把材质用一种规则映射到物体表面。而 UV Mapping 就是映射的规则。在这个规则中,给三维体每一个顶点增加两个值 U 和 V,它们记录了三维表面和二维表面的坐标对应关系:

有了映射关系,我们就可以分门别类地把影响光照的不同参数,都通过图片映射到三维几何体上。
贴上皮肤的方法虽好,但是局限也很明显。如果没有合适的图像,或者要创建真实世界中罕见的材质,皮肤就不好找了。这个时候需要让程序帮忙「生长」出新的皮肤。
这种程序叫做 Shaders。Shading 是始终如一的终极目标,那么应该就能明白为什么实现这个目标的程序叫做 Shaders 了。
它实际上是一个程序片段、一系列的指令,可以将三维 Mesh(网格)以指定方式与颜色、贴图等组合,完成复杂的计算输出(渲染器可读取的点和颜色的对应关系),会对屏幕上的每个像素同时下达命令。也就是说,代码必须根据像素在屏幕上的不同位置执行不同的操作。就像活字印刷,你的程序就像一个 function(函数),输入位置信息,输出颜色信息,当它编译完之后会以相当快的速度运行。
UV贴图
UV贴图是用于轻松包装纹理的3D模型表面的平面表示。创建UV贴图的过程称为UV展开。
U和V指的是2D空间的水平轴和垂直轴,因为X,Y和Z已在3D空间中使用。
一旦创建了多边形网格,下一步就是将其“展开”为UV贴图。现在要赋予网格生命并使它看起来更逼真(或风格化),你想添加纹理。
但是,没有3D纹理之类的东西,因为它们始终基于2D图像。
这就是UV映射的用处,因为它是将3D网格转换为2D信息以便可以在其周围包裹 2D纹理的过程。
起初这似乎是一个令人困惑的想法,但这确实非常简单。如果你以前曾经用纸做过一个立方体,那么你只需完成相反的步骤即可!

但是,立方体是一个基本示例,并且随着网格变得越来越复杂,UV贴图也是如此。这可能会变得很繁琐,但这对3D工作流程至关重要。
即使你不打算对模型进行纹理处理,许多现代的实时引擎(例如,虚幻引擎4或Unity)也需要对你的资产进行UV解包才能执行其一些轻度烘焙。
探索紫外线解缠
既然已经概述了UV贴图的基本概念,我们就可以深入研究UV展开的中间部分,即接缝。
接缝是使任何3D几何形状扁平化的不幸和不可避免的副作用。
接缝是网格的一部分,必须进行拆分才能将3D网格转换为2D UV贴图。
UV展开始终是一种折衷方案,可以使线框变形尽可能小,同时还要使接缝最小。就UV贴图而言,变形是必须更改多边形的形状和大小以适应平坦化过程的程度。太多的失真会影响最终模型上最终效果。

在此图像中,打开多维数据集的方式未引起多边形变形。
通过应用基本的方格纹理可以很容易看出这一点。如果未拉伸棋盘格图案,则可以避免展开时变形。
但是,这种仅将所有多边形分开的方法的缺点是产生的接缝数量。
左侧的多维数据集的UV接缝以绿色突出显示。你可以看到图案在边缘移动时没有对齐。在更复杂的网格中,这可能会成为问题,这需要你需要练习并灵活地进行接缝放置。

这是一个示例,如果你在UV解包中严重扭曲多边形会发生什么。此多维数据集上的纹理与前面的示例没有什么不同,但是你可以看到它已被拉伸并变形。但是,缺少接缝确实意味着图案排成一行并围绕立方体的边缘,但是在这种情况下,付出的代价是不值得的。

显而易见的答案是在两者之间找到平衡。
在这个图像中,你可以看到保留了棋盘格图案,并且我们在立方体的正面周围有一些不错的连续边。
在练习和展开更多内容时,你将了解“隐藏”接缝的最佳位置是使它们不那么明显。
使接缝不那么明显的良好规则是:
使它们沿着通常不太明显的坚硬边缘
将它们隐藏在模型的其他部分后面。例如,如果解开头部,则将接缝放在头发所在的位置。
将它们隐藏在模型焦点下方或后面,这样人们就不太可能看到它们。
展开3D网格时要考虑的另一件事是UV重叠。

当你的UV贴图中有两个或多个多边形彼此重叠时,就是UV重叠。这意味着模型的这两个部分将显示相同的纹理信息,因为它们都占据相同的UV空间。
通常要避免重叠UV,这样可以使纹理保持变化,而不会偶然导致纹理看起来不正确。也就是说,有时你可能会故意使用重叠的UV。如果纹理非常基本,那么你可能会在同一UV空间上具有多个网格部分来重复该纹理。
这项技术非常有用,因为它可以让你减小纹理大小,这意味着,如果你使用的是游戏引擎,通常它将运行得更流畅。
当开发较弱的机器(如手机)时,这一点尤为重要。
关于紫外线贴图需要了解的最后一件事是紫外线通道。UV通道允许同一对象具有多个UV贴图。对于游戏引擎而言,这再次非常重要。如本文前面所述,游戏引擎使用UV贴图烘焙照明信息。
这意味着绝对不会有任何重叠的UV,因为阴影信息将放置在模型的错误区域中,并且你通常会收到某种错误消息。如前所述,现在重叠的UV有时在游戏开发中是个好主意。
因此,折衷方案是具有2个UV通道。一种是带有紫外线信息的纹理,另一种是带有紫外线信息的照明。您做得越多,越容易,但是UV制图过程有很多细节。希望你现在对UV映射有了更好的了解。这是一个足够简单的过程,乍一看似乎令人生畏,但很容易掌握实践。
法线是什么?
1、法线无处不在,这是图形学基础中的基础。
2、法线贴图,凹凸图,位移图等等,在图形学历史上有着比较重要的位置,在很多图形学的架构中都有应用,典型的例如延迟渲染架构。
法线
法线,英文名normal。首先,要理解点法线和面法线。
现在资料烂大街的年代,去寻找这个答案是比较容易的,估计连百度都能找到还不错的答案解释了。记得当初为了搞懂这个东东,我自己亲自写过很多算法。
第一个问题:假设一个顶点被N个三角形公用,这个顶点的法线怎么算?

如图,点A的法线应该怎么算才是合理的?
如果是取的面法线,那么多个面公用一个点,取哪个面的才是合理的?这个问题曾经困扰过我好几天。然后我自己写算法验证。
第一个方法:取任意一个面的法线。但是这种做法效果奇差,可想而知。
第二个方法:计算所有相邻的面的法线,然后取平均值。
这种方案一般情况下,效果还凑合。但是会有其他一些问题。
1、当一个很大的面和一个很小的面邻面,两个面加权求平均,合适吗?
2、并非所有的应用都是需要这种平滑的光照渲染,例如有一些GIS相关的应用,说不定不需要平滑的光照,而是希望得到面与面的清晰边界的光照。
第三个方法:每一个面的法线都要,每个顶点的法线就是面法线。那么,这种情况下,共面的顶点怎么办?当然是多顶点,不适用共享顶点操作模式。例如一个顶点有N个共面,那么这个顶点就不是一个顶点,而是N个顶点,但是坐标是一样的,法线不一样。这种模式,会导致共享顶点减少显存占用的做法失效了。但是,对现代显卡来说,其实顶点数大多数情况下不是很大的开销了,尤其是对于PC来说。
第四个方法:根据相邻面的权重来计算。权重用面积来算。假设有三个面共顶点,三个面的面积分别为A、B、C,法线分别为N1、N2、N3.那么先算A的面积占比p1 = (A / (A + B + C)),法线的占比跟面积的占比一样的。N1 = p1 * (N1 + N2 + N3)。这个算法是我自己测试的,我估计不会有人这么干,当初我这么做只是为了看看这样做的效果怎么样,有没有操作的可行性。事实上,这种做法,效果不怎么样,按道理大概率不会有软件这么做?
我估计,现在图形学上,采用的方案主要是第二跟第三种。我在网上找了两张图,看看第二跟第三种的区别,一眼可以看到。

这么看来,是不是顶点法线才是最合理的?面法线不靠谱?其实不是的,这种完全是看你的需要。看图:

如果是这种box,你用了顶点法线,出来的就是这种不伦不类的效果了。这种情况下,你大概率需要的是各个面的面法线。所以总结开来,当你需要平滑的时候,用求平均这类顶点法线;当你需要区分的时候,不同面用不同的法线。


法线贴图。
为什么需要法线贴图?

看看这图。如果需要在3D里面渲染这样一个画面,首先是很麻烦,其次是顶点太多。做这样一个模型,估计几万面都是可能的。一些大场景里,满屏的悬崖峭壁,都是这样的效果,面数太多,导致了渲染效率的低下。


那么,能不能做一个面,然后直接上图?当然可以。但是,效果较差,并且实时渲染光照的时候,就更加不理想了。这个时候,就需要用法线贴图了。
光与影,法线贴图技术的物理学基础
我们知道,人之所以能够对景物看出立体感的主要原因是因为人有两只眼睛。两只眼睛看的景象是不同的,所以人们才能分辨出立体感来。但是,由于电脑的屏幕是一个平面,分辨3D效果就只能靠光影效果来实现了。
举个简单的例子,这就像我们画素描的时候,为了不让一个球体看起来像是一个圆圈,必须让球体的一些区域是亮的,一些区域是暗的。而且从亮部转向暗部的时候是一个均匀的按照物理模型特点的过渡,这样画出来的球体才像个球体,电脑为我们绘制的过程也是一样。

因为有明暗,我们才能将平面理解成立体
基于这个道理,我们就不难理解可以通过贴图局部的亮暗变化来实现假的3D效果。换一种说法就是说我们可以通过在贴图上的局部做一些亮暗的变化来做到一种假的3D效果。
法线贴图的意思是:这里还是渲染一个QUAD,两个三角形,但是通过贴图来描述像素的法线。渲染每一个像素的时候,都用的不同的法线,这样,实时光照的时候,能完美模拟出来光照的效果,而且大大降低了计算量。下面,我随手用CPU写一点伪代码,来模拟这个过程,能轻易看出来这个效率的不同。
这是用法线贴图的。(注意,以上仅仅是简单的CPU模拟,GPU不是这样的。GPU有大量的渲染线程,并且我没记错的话,还是SIMD指令,复杂得多。但是不管怎么样,法线贴图渲染效率的提升都是实打实的。)
那么以上可以看出,法线贴图技术,仅仅是让三角形渲染的时候,多了一个真实的法线值,用于做光照计算,而不能增加顶点值。因为一般时候,顶点值在计算光照的时候都用不到。
那么,是不是所有的复杂模型都可以用法线贴图来解决呢?当然是不可能的。说穿了,法线贴图仅仅是简单的视觉欺骗,一旦凹凸太明显的模型,使用了法线贴图,太靠近的时候,就穿帮了。所以,适用于法线贴图的场合,主要就是凹凸不太明显,细节很多,需要表现实时光照效果,不会太靠近观察的物体。
法线贴图为什么绝大部分都是偏蓝色的?这是一个好问题。彻底理解了这个问题,那么法线的理解基本上可以说登堂入室,脱离了菜鸟行列。
先来回顾一些简单的概念,我应该会在纹理的章节里面讲过,没讲过那就是漏掉了,讲过了这里就当复习。
重点1:纹理的像素值,都是0-1之间!没有负数,不能大于1!
这么干有什么好处呢?很简单,一般的浮点数,就是32位,精度有限,还有大量的精度用于描述整数部分,必然导致了小数部分精度的缺失。全部用于描述小数,精度更好。我没有仔细查看过GPU这块用的是哪个浮点数标准,我只隐约记得Nvidia的文档里提过一般浮点数是IEEE754标准,而纹理的就不知道了,但是我相信不会跟一般浮点数一样的,毕竟不需要使用大量的资源来描述整数位了。
所以,法线值储存在贴图里,首先就要normalize,转化为-1到1之间。然后再因为不能有负数,需要再转换到0-1之间,一般有大概这样的函数:
Float3 DecodeNormal(float3 n)
{
Return (n * 2 - 1.0f);
}
Float3 EncodeNormal(float3 n)
{
Return (n + 1.0f) * 0.5;
}
据说这个函数有人玩出花来的,例如什么压缩到16位贴图减少显存占用,这个其实比较简单,因为normalize之后的法线值,其实是x ^2 + y^2 + z^2 = 1;那么你保存了x跟y岂不是可以反过来算出z了嘛。但是这种做法虽然降低了显存占用,同时也降低了效率啊,需要开方一次。其他据说还有一大堆乱七八糟的优化,我只是耳闻,反正我没有干过。有兴趣的也可以自己试试看。
重点 2:模型有本地坐标系,世界坐标系。渲染的时候,必须变换到世界坐标系才能正确渲染。这个变换一般都很简单,就是一行代码:
Float4 WorldPos = WorldMatrix * LocalPos;
那么问题来了,法线怎么弄呢?当你没有用到法线贴图的时候,其实也是一样的:
Float4 WorldNormal = WorldMatrix * LocalNormal;
那么,你使用了法线贴图呢?
我们需要这么干:
Float4 Normal = tex2D(NormalTexture, UV);
Float4 RealNormal = DecodeNormal(Normal); // 0,1转换到-1,1
Float4 WorldNormal = WorldMatrix * RealNormal;
Float4 Col = CalcLighting(WorldNormal, Light);// 法线和光照计算颜色。
上面代码有什么问题吗?其实,如果就一般的程序来说,一点问题都没有。甚至更糟糕的垃圾代码,都没有问题。我见过无数比这糟糕得到的代码,照样跑得666.
图形学为什么相对比较难?因为图形学对性能有极致的需求。以上代码,对性能上有一定的损耗。主要表现在哪里?
首先,这里的UV是需要三角形插值得到的,这就导致了这部分代码必须只能运行在PS(像素着色器)上。也就是说,每个像素都需要执行一遍。
其实这也不是什么大问题。但是,有更好的优化方式啊。我可以把Light的坐标,转换到法线贴图的本地坐标系,然后进行光照,结果是一样的啊,只要在同一个坐标系即可。而Light的坐标转换,只需要在VS里面算一次即可,不需要在PS里面反复算。
以上这个坐标系,叫做切线坐标系。首先,任意一个三角形,先计算一个Normal,然后再计算一个切线。根据法线跟切线的两两垂直关系,叉乘(crossProduct),得到副法线,构建坐标系。三角形的法线好计算,已知三个顶点,根据面的方向,两两叉乘可以得到法线,这点代码到处都能找到。那么切线是怎么算的呢?我没记错的话,我记得是用偏微分方程,以U坐标方向为切线方向来算的,那么V方向就是副法线方向(这部分不保证绝对正确,懒得去查资料了,大概理解一下原理即可,想知道的自己去查一下)。
除了效率原因,还有另外一个原因,据说是形变。假设是模型,使用了形变,如果法线贴图储存的是本地坐标系,这个世界变换并不能体现这个形变,而且法线贴图的计算一般都是再MAX,玛雅之类的软件里,引擎一般不提供,修改法线贴图就很麻烦了。而使用了切线坐标系,是可以实现形变的。形变之后,重新计算Normal跟Tangent即可。但是,这其实也是挺麻烦的事,一般来说,使用到法线贴图的模型,都是一些大平面的细节模型,形变这个因素我没碰到过。
回到主题,为什么法线贴图是偏蓝色?很明显了,在切线坐标系里,定义顺序是Tangent、Binormal、Normal,也就是说,Normal处于z这个方向。而对于一个三角形而言,绝大多数时候,法线值都是垂直于这个面的。显而易见,法线贴图的法线值大多数时候是接近于(0,0,1)的,当然是接近于蓝色了。
对法线编码和解码
我们的表面法线是单位向量, 通常位于范围 -1.0
到 1.0
之间. 我们可以通过把法线范围转换为 0.0
到 1.0
之间来把法线向量(x, y, z)
存储到一个 RGB
纹理贴图中. 下面是伪码:
Color.rgb = Normal.xyz / 2.0 + 0.5;
例如, 一个法线 (-1, 0, 1)
会被作为 RGB
编码为 (0, 0.5, 1)
. x
轴(左/右)被保存到红色通道, y
轴(上/下)被保存到绿色通道, z
轴(前/后)被保存到蓝色通道.
最终的法线图(normal map
)看起来就是下面这个样子:

典型地, 我们使用程序来生成法线图, 而不是手动绘制.
理解法线图, 把每个通道独立出来查看会更清楚:

看着,绿色通道,我们看到更亮的部分(值更接近于 1.0
) 定义了法线指向上方的区域,而更暗的区域(值更接近为 0.0
) 定义了法线指向下方的区域. 大多数的法线图会是蓝色,因为Z
轴(蓝色通道)通常指向我们(即值为 1.0
).
在我们游戏的片段着色器中, 我们可以把法线解码, 通过执行跟之前编码时相反的操作, 把颜色值展开为范围 -1.0
到 1.0
之间:
//sample the normal map
NormalMap = texture2D(NormalMapTex, TexCoord);
//convert to range -1.0 to 1.0
Normal.xyz = NormalMap.rgb * 2.0 - 1.0;
注意: 要记住不同的引擎和软件会使用不同的坐标系, 绿色通道可能需要翻转.
什么是HDR环境贴图?

1、打开模型文件,切换至顶视图。

2、在创建面板中选择图形-->弧。

3、在可以看到窗户的一侧的外面绘制一条大弧线,如下图:

4、切换至前视图,在修改面板里添加挤出修改器,并在参数面板中数量设为5000,调整位置,不要遮住目标平行光。

5、按m快捷键,为弧添加发光材质,然后赋予外景图片jpg

6、绑定到弧线上。

7、然后在修改器面板中添加壳命令,这是为了做双面或者厚度时所用,最后选择UVW贴图,在参数中选择长方体,调整长宽高的参数。

8、最后,渲染接果如图,窗外有一片树林。

环境贴图就是通过图片的形式体现3维环境背景视感的做法,至于成立的形式
不同软件也不经相同,所以这只是一个通量