最近在使用OpenGL折腾Normal Mapping。说白了就是有一个纹理,里面存储的是法向量。在计算光照时,用该纹理中采样得到的法向量来替代几何体原法向量进行光照计算。这个存储法向量的纹理叫做Normal Map。有时候场景资源不会直接给你Normal Map,而是给你一个Height Map,该纹理中只存储了一个通道,是[0,1]的灰度值,可以理解成几何体表面凹凸不平的高度,可以根据这个Height Map生成Normal Map。生成过程如下:
uniform sampler2D HeightMapTex; float h21 = textureOffset(HeightMapTex,Coord,ivec2(1,0)).r; float h01 = textureOffset(HeightMapTex,Coord,ivec2(-1,0)).r; float h10 = textureOffset(HeightMapTex,Coord,ivec2(0,-1)).r; float h12 = textureOffset(HeightMapTex,Coord,ivec2(0,1)).r; vec3 vr = normalize(vec3(2.0f,0.0f,h21 - h01)); vec3 vt = normalize(vec3(0.0f,2.0f,h12 - h10)); // normal in tangent space vec3 normal = normalize(cross(vr,vt));
直接从Normal Map中采样得到的和从Height Map中计算得到的normal向量是在tangent space之中。值得注意的是,直接从Normal Map中获取的法向量的每个轴的值是缩放到[0,1]之后的法向量,要先还原才能使用,而从HeightMap计算得到的法相可以直接使用。缩放和还原过程如下:
// 缩放 normal = normal * 0.5f + vec3(0.5f); //还原 normal = normal * 2.0f - vec3(1.0f);
在进行光照计算时,我们需要光的方向LightDir,视角的方向ViewDir,和法向量n,这些向量必须在转换到同一个坐标系中进行计算。为了提高计算效率,通常我们在VertexShader中把LightDir和ViewDir变换到tangent space中,经过插值后传递到Fragment Shader中,然后对Normal Map进行采样,得到tangent space的法向量之后,进行光照计算。
关于Tangent Space到Object Space的变换矩阵构建,可参考以下链接:http://www.terathon.com/code/tangent.html