法线与法线贴图
本文目录 15 个章节
法线与法线贴图
创建时间:2021/2/22 10:47
- 法线与法线贴图
- 法线(Normal)
- 原理——凹凸感的实现
- 点法线的计算
- 直接求平均
- 多顶点
- 光照的计算与插值
- 法线坐标的变换
- 法线贴图(Normal Map)
- tangent space
- 局部空间存储法线
- 切线空间
- 法线贴图的存储
- 其他bump mapping技术
- 凹凸贴图(bump map)
- tangent space
- ref
- 法线(Normal)
法线(Normal)
原理——凹凸感的实现
人眼对物体的凹凸感觉,很大程度上取决于表面的光照明暗变化,如果能通过一张贴图在一个平面上表现出由凹凸造成的明暗变化,则可以让人眼感觉这个平面是凹凸不平的(虽然这个平面还是平的)。
To calculate the Lambertian (diffuse) lighting of a surface, the unit vector from the shading point to the light source is dotted with the unit vector normal to that surface, and the result is the intensity of the light on that surface.
点法线的计算
Wiki中的定义 - 三维平面的法线是垂直于该平面的三维向量。曲面在某点P处的法线为垂直于该点切平面的向量。
Unity Mesh的法线方向 Unity中,对于一个三角面,法线向外的面就是正面,相反的就是背面。而三角形法线的方向 是由顶点的顺序决定的。 Unity里是左手坐标系,左手指尖指尖朝向顶点循环方向,拇指就指向法线的方向。由此可知,如果看到顶点顺序是顺时针方向的话那么就是在三角形的正面。
直接求平均
多个面共用一个顶点,可以通过取所有面的平均值来计算出这个法线。 Np = (N0 +N1 + N2) / 3
存在的问题:
当一个很大的面和一个很小的面邻面,两个面求平均,合适吗?
并非所有的应用都是需要这种平滑的光照渲染,例如有一些GIS相关的应用,说不定不需要平滑的光照,而是希望得到面与面的清晰边界的光照。
若共用顶点的两个面不是平滑过渡的,两个面的法线变化过大,则求平均的方法不合适。
多顶点
每一个面的法线都要,每个顶点的法线就是面法线。同一位置,有多个顶点,坐标相同,法线不同 。 如下图,左边的Cube每个面使用各自的顶点,而右边的Cube共用顶点。

光照的计算与插值
计算光照时,需保证光照方向和法线是处在同一坐标空间。光照方向在世界空间中,而法线在切线空间。有两种计算方法:
将光照方向变换到切线空间 - 只需针对每个顶点进行变换,然后在三角形中对变换后的光照方向进行插值
将法线变换到世界空间 - 需要对每个像素进行变换
片段着色器执行的次数远大于顶点着色器执行的次数 在光源较少的情况下,使用第一种方案可以减少计算量。在光源数量较多的情况下,使用第二种方案可能开销比较低。因为欸二中方案只需要变换一次法线,而第一种方案需要有多个光照方向需要变换。
法线坐标的变换
Vertex Shader中,View space空间中的顶点位置可由以下公式计算:
![]()
而若法线通过MV * N 来进行坐标变换,当模型发生non-uniform缩放(三个坐标轴的缩放比例不同)时,会出现以下问题:

变换前

变换后 (图片摘自:https://zhuanlan.zhihu.com/p/72734738。侵删。)
把法线从local space变换到view space的变换矩阵为顶点位置变换矩阵MV的逆矩阵的转置矩阵。即:
![]()
推导见:https://zhuanlan.zhihu.com/p/72734738
故在Unity Shader中法线坐标变换通常这样写:
string s = String.Empty;
等价于:
string s = String.Empty;
法线贴图(Normal Map)
In 3D computer graphics, normal mapping , or Dot3 bump mapping , is a texture mapping technique used for faking the lighting of bumps and dents – an implementation of bump mapping. It is used to add details without using more polygons. A common use of this technique is to greatly enhance the appearance and details of a low polygon model by generating a normal map from a high polygon model or height map.
IDEA: transferring details with normal maps from high to low polygon meshes
意义: 将具有高细节的模型通过映射烘焙出法线贴图,然后贴在低端模型的法线贴图通道上,使其表面拥有光影分布的渲染效果,能大大降低表现物体渲染时需要的面数和计算内容,从而达到优化动画和游戏的渲染效果。
tangent space
在3D世界中定了如此多的坐标系,每个坐标系当然都有它的用途。比如局部空间,或者叫模型空间,它的目的就是方便我们对3D模型进行建模。在这个空间中,我们不需要考虑该模型在场景中可能出现的位置、朝向等众多细节,而专注于模型本身。在世界空间中,我们关心的问题是场景中各个物体的位置、朝向,即如何构建场景,而不必关注摄像机的观察位置及其朝向。可见,一个坐标系的根本用途,即让我们在处理不同的问题时,能够以合适的参照系,抛开不相关的因素,从而减小问题的复杂度。
局部空间存储法线
法线贴图中存储的法线最初是定义在世界空间中,但在实际中,这种方式很少见,因为只要物体移动,法线贴图则不再有效。
将法线存储在物体的局部空间中 A straightforward implementation encodes normals in object-space, so that red, green, and blue components correspond directly with X, Y, and Z coordinates. 物体可以进行刚体变换(平移,旋转,缩放),法线贴图依旧有效,但是这种方法并不能应对任何方式的变换,并且法线贴图不能在不同物体进行复用,增加了美工的负担。
However object-space normal maps cannot be easily reused on multiple models, as the orientation of the surfaces differ. Since color texture maps can be reused freely, and normal maps tend to correspond with a particular texture map, it is desirable for artists that normal maps have the same property.
- Normal map reuse is made possible by encoding maps in tangent space.
切线空间
模型顶点中的纹理坐标,就定义于切线空间。普通2维纹理坐标包含U、V两项,其中U坐标增长的方向, 即切线空间中的tangent轴,V坐标增加的方向,为切线空间中的bitangent轴。 模型中不同的三角形,都有对应的切线空间,其tangent轴和bitangent(副切线)轴分别位于三角形所在平面上,结合三角形面对应的法线,我们称tangant轴(T)、bitangent轴(B)及法线轴(N)所组成的坐标系,即切线空间(TBN)。

e0 =t1T+ b1B
e1 = t2T+ b2B
在3D模型文件中,所有顶点的位置坐标、纹理坐标、法线等信息一般都会提供的,但却缺少切线坐标系相关信息。而在应用Normal Mapping等技术时,切线空间又是必不可少的,因此就需要我们自己手动来获取切线坐标系了。很多读取模型的库都提供了生成切线空间的功能。
使用TBN坐标系的目的只是为了把从Normal Map中得到的法线从切线空间转换到世界空间
顶点的切线空间 - 对于任一顶点,我们使用其所在的所有三角形所对应的切线空间向量的平均值,作为该顶点的切线空间。
法线贴图的存储
把法线坐标限制在长度为1内,那么可以通过范围[-1,1]来表示法线在360度内的偏移。把法线的x y z通过 f(x) = (x + 1) / 2 映射到[0,1],当作RGB 3个颜色的值存储,就可以存储在图片里面了。如 [0, 128, 255]表示法向量(-1, 0, 1)。
绝大多数时候,法线都是垂直于所在的三角面的,也就是在切线空间的(0,0,1),存储在RGB图片中像素值即为(0.5,0.5,1),再转化为颜色的(0,255)区间,最终就变成了(127,127,255)。因此法线贴图的颜色以蓝紫色为主,如下图:

UnityCG.cginc中已经实现了UnpackNormal函数,将Normal Map的颜色值映射为法线坐标值:
string s = String.Empty;
其中,UnpackNormalDXT5nm 是一种时间换空间的做法。DXT5nm格式的normal map只存储法向量中的两个通道,然后解开的时候,需要计算一下,重新算出另一个向量方向。这样可以实现的原理在于,存储的向量是单位向量,长度一定的情况下,就可以通过sqrt(1 - x^2 - y^2)来求得。
//### 法线贴图的压缩与Mipmap
其他bump mapping技术
Bump Map是最早的法线贴图实现方式,而随着GPU的发展,Geforce3的出现,真正的Normal Mapping技术才出现,也叫作Dot3 bump mapping。 此外,Parallax mapping、Displacement Mapping都属于bump mapping范畴,而且都基于Normal Mapping, 但相比Normal Mapping,后两种方法可以提供更加逼真物体表面的凹凸感。
凹凸贴图(bump map)
Bump Map是最早的法线贴图实现方式,这也是制作上最容易的一种模式。通过灰度表现界面的凹凸程度,进而修改法线。越凸起的地方颜色越亮。
怎样判断一个点处在凹凸的边缘呢?答案是通过斜率,比如我要对(x,y)进行采样,怎样求这一点的斜率呢,学过数学的都知道,我们可以通过两点确定一条直线,进而求出这条直线的斜率。那么我们就可以对(x-1,y)和(x+1,y)两点进行采样,竖向也是一样,通过(x,y-1)和(x,y+1)进行采样,那么,我们就可以获得这一点上灰度值的变化,如果灰度值不变,说明该点不在边缘,如果灰度值有改变,那么说明该点在边缘,那么我们就可以根据这个斜率值来修改法线,进而修改光照结果。
Unity可以从灰度凹凸贴图生成Normal Map:

Normal Map与原始的Bump Map的区别: Normal Map直接将法线存储到了法线贴图中,也就是说,我们从法线贴图读取的法线直接就可以使用了,而不是像凹凸贴图一样,通过灰度渐变值来修改法线。
string s = String.Empty;
ref
https://en.wikipedia.org/wiki/Normal_mapping https://www.cnblogs.com/wangchengfeng/p/3470310.html https://blog.csdn.net/bonchoix/article/details/8619624 https://gameinstitute.qq.com/community/detail/126511 https://zhuanlan.zhihu.com/p/72734738 https://zhuanlan.zhihu.com/p/102131805 https://blog.csdn.net/puppet_master/article/details/53591167