第十三课:法线贴图

文章推薦指數: 80 %
投票人數:10人

镜面纹理(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;i0”;故只需检查dot(cross(n,t),b)是否大于0。

若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



請為這篇文章評分?