Normal Maping
法线贴图是一种特殊的texture,法线贴图中每个像素存储的不是rgb颜色值,而是该片元的法向量。
法向量可以通过模拟物体表面的明暗情况(只是改变了明暗而非在高度上做技巧,和高度贴图是不同的),使得渲染出来的物体更有凹凸感,看起来更为真实。同时性能消耗非常小
图1:
法线贴图的使用非常方便,只需要当做平常的texture一样对它进行采样,采样的值作为normal而非rgb。
下面是一个简单的GLSL Shader的例子。
#version 330 core
out vec4 fragcolor;
in SharedData
{
vec3 worldPos;
vec2 texcoord;
}fs_in;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform sampler2D diffuse_map;
uniform sampler2D normal_map;
void main()
{
vec3 color = texture(diffuse_map, fs_in.texcoord).rgb;
//采样的值作为normal
vec3 normal = texture(normal_map, fs_in.texcoord).rgb;
//为了把[0,1]的value映射到[-1,1]
normal = normalize(normal * 2.0 - 1.0);
vec3 lightDir = normalize(fs_in.worldPos - lightPos);
vec3 viewDir = normalize(fs_in.worldPos - viewPos);
//ambient
vec3 ambient = 0.2f * color;
//diffuse
float diff = dot(-lightDir, normal) * 0.5 + 0.5;
vec3 diffuse = diff * color;
//specular
vec3 half = -normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, half), 0.0f), 32.0f);
vec3 specualr = spec * color;
fragcolor = vec4(ambient + diffuse + specualr, 1.0f);
}
渲染出来的物体很有凹凸感于是看起来更为真实:
图2:
(上图对应的法线贴图)
图3:
你所能找到的法线贴图几乎都是偏蓝色的,这是因为法向量几乎都是指向Z轴的正向的,也就是说在(0,0,1)之间波动,蓝色的分量更大一些所以整个图看起来是蓝色的。
到现在为止一切都很好,可是在学习法线贴图的时候,有一个无论如何都绕不开的概念称为切线空间。那么我们为什么还要自找麻烦呢?
Tangent Space
原由和概念
其实不是我们自找麻烦,是因为真的有很多麻烦要处理。
上面的例子最后渲染的结果很好是因为原来的图片中的砖块的法线向量恰好是指向Z轴的正向的,但是如果原图是一个坑坑洼洼的不规则复杂地形,例如下图所示:
图4:
表面的法向量用红色标识,如果有无数方向不同的法向量,难不成我们还得给一张原图制作无数张法线贴图吗?显然这不现实。
另一个稍微有点难的解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线贴图向量总是指向这个坐标空间的正z方向;所有的光照向量都相对与这个正z方向进行变换。这样我们就能始终使用同样的法线贴图,不管朝向问题。这个坐标空间叫做切线空间(tangent space)。
法线贴图中的法线向量在切线空间中,法线永远指着正z方向。切线空间是位于三角形表面之上的空间(切线空间是顶点构造的一个局部坐标系,每一个三角面定义的切线空间是不一样的)因此,切线空间的一大好处是我们可以为任何类型的表面计算出一个这样的矩阵,由此我们可以把切线空间的z方向和表面的法线方向对齐。
我们需要定义一个空间基来描述这一空间,称为TBN矩阵。
TBN矩阵这三个字母分别代表tangent、bitangent和normal向量。这三个向量两两正交,它们沿一个表面的法线贴图对齐于:(B)上、(T)右、(N)前。
图5:
图6:
那么,对于某个给定了uv坐标的三角形面,我们有如下关系:
根据图5可知,我们想要的切线空间的B是平行于V,T是平行于U的。
设图6中:
E1→=p2−p1
E2→=p3−p1
又 E1→ , E2