第十三课:法线贴图
文章推薦指數: 80 %
镜面纹理(Specular texture); 用立即模式(immediate mode)进行调试; 利用颜色进行调试; 利用变量名进行调试 ... 今天的内容是法线贴图(normal mapping)。
法线纹理
切线和副切线(TangentandBitangent)
准备VBO
计算切线和副切线
索引
着色器
新增缓冲和uniform变量
顶点着色器
片段着色器
结果
延伸阅读
正交化(Orthogonalization)
左手系还是右手系?
镜面纹理(Speculartexture)
用立即模式(immediatemode)进行调试
利用颜色进行调试
利用变量名进行调试
怎样制作法线贴图
练习
工具和链接
参考文献
欢迎来到第十三课!今天的内容是法线贴图(normalmapping)。
学完第八课:基本着色后,我们知道了如何用三角形法线得到不错的着色效果。
需要注意的是,截至目前,每个顶点仅有一条法线。
在三角形内部,法线是平滑过渡的,而颜色则是通过纹理采样得到的(译注:三角形内部法线由插值计算得出,颜色则是直接从纹理取数据)。
法线贴图的基本思想就是像纹理采样一样为法线取值。
法线纹理
下图是一张法线纹理:
每个纹素的RGB值实际上表示的是XYZ向量:颜色的分量取值范围为0到1,而向量的分量取值范围是-1到1;可以建立从纹素到法线的简单映射
normal=(2*color)-1//oneachcomponent
由于法线基本都是指向”曲面外侧”的(按照惯例,X轴朝右,Y轴朝上),因此法线纹理整体呈蓝色。
法线纹理的映射方式和漫反射纹理相似。
麻烦之处在于如何将法线从各三角形局部空间(切线空间tangentspace,亦称图像空间imagespace)变换到模型空间(着色计算所采用的空间)。
切线和副切线(TangentandBitangent)
大家对矩阵已经十分熟悉了,应该知道定义一个空间(本例是切线空间)需要三个向量。
现在Up向量已经有了,即法线:可用Blender生成,或由一个简单的叉乘计算得到。
下图中蓝色箭头代表法线(法线贴图整体颜色也恰好是蓝色)。
然后是切线T:垂直于法线的向量。
但这样的切线有很多个:
这么多切线中该选哪个呢?理论上哪一个都行。
但我们必须保持连续一致性,以免衔接处出现瑕疵。
标准的做法是将切线方向和纹理空间对齐:
定义一组基需要三个向量,因此我们还得计算副切线B(本可以随便选一条切线,但选定垂直于另外两条轴的切线,计算会方便些)。
算法如下:记三角形的两条边为deltaPos1和deltaPos2,deltaUV1和deltaUV2是对应的UV坐标下的差值;则问题可用如下方程表示:
deltaPos1=deltaUV1.x*T+deltaUV1.y*B
deltaPos2=deltaUV2.x*T+deltaUV2.y*B
求解T和B就得到了切线和副切线!(代码见下文)
已知T、B、N向量之后,即可得下面这个漂亮的矩阵,完成从切线空间到模型空间的变换:
有了TBN矩阵,我们就能把(从法线纹理中获取的)法线变换到模型空间。
可我们需要的却是从切线空间到模型空间的变换,法线则保持不变。
所有计算均在切线空间中进行,不会对其他计算产生影响。
只需对上述矩阵求逆即可得逆变换。
这个矩阵(正交阵,即各向量相互正交的矩阵,参见下文”延伸阅读”小节)的逆矩阵恰好也就是其转置矩阵,计算十分简单:
invTBN=transpose(TBN)
亦即:
准备VBO
计算切线和副切线
我们需要为整个模型计算切线、副切线和法线。
我们用一个单独的函数完成这些计算
voidcomputeTangentBasis(
//inputs
std::vector&vertices,
std::vector&uvs,
std::vector&normals,
//outputs
std::vector&tangents,
std::vector&bitangents
){
为每个三角形计算边(deltaPos)和deltaUV
for(inti=0;i
若dot(cross(n,t),b)<0,就要翻转t:
if(glm::dot(glm::cross(n,t),b)<0.0f){
t=t*-1.0f;
}
在computeTangentBasis()末对每个顶点都做这个操作。
镜面纹理(Speculartexture)
为了增强趣味性,我在代码里加上了镜面纹理;取代了原先作为镜面颜色的灰色vec3(0.3,0.3,0.3)。
镜面纹理看起来像这样:
请注意,由于如上镜面纹理中没有镜面分量,水泥部分均呈黑色。
用立即模式(immediatemode)进行调试
本站的初衷是让大家不再使用已被废弃、缓慢、问题频出的立即模式。
不过,用立即模式进行调试却十分方便:
这里,我们在立即模式下画了一些线条表示切线空间。
要进入立即模式,必须先关闭3.3CoreProfile:
glfwOpenWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_COMPAT_PROFILE);
然后把矩阵传给旧式的OpenGL流水线(你也可以另写一个着色器,不过这样做更简单,反正都是在hacking):
glMatrixMode(GL_PROJECTION);
glLoadMatrixf((constGLfloat*)&ProjectionMatrix[0]);
glMatrixMode(GL_MODELVIEW);
glm::mat4MV=ViewMatrix*ModelMatrix;
glLoadMatrixf((constGLfloat*)&MV[0]);
禁用着色器:
glUseProgram(0);
然后绘制线条(本例中法线都已被归一化,乘以0.1,置于对应顶点上):
glColor3f(0,0,1);
glBegin(GL_LINES);
for(inti=0;i
延伸文章資訊
- 1Normal Mapping - LearnOpenGL
The normal map is defined in tangent space, so one way to solve the problem is to calculate a mat...
- 2OpenGL 2.1 - GLSL normal mapping - 3D C/C++ tutorials
The normal map used in this tutorial is a Direct3D normal map and before it can be mapped on tria...
- 3Tutorial 13 : Normal Mapping
The basic idea of normal mapping is to give normals similar variations. Normal textures. A “norma...
- 4第十三课:法线贴图
镜面纹理(Specular texture); 用立即模式(immediate mode)进行调试; 利用颜色进行调试; 利用变量名进行调试 ... 今天的内容是法线贴图(normal mapp...
- 5Implementing Normal Mapping using OpenGL/GLSL - Stack ...
That normal map is in tangent-space, but you are treating it as object-space. You need a bitangen...