计算机图形学 | 实验十:几何纹理(法线贴图)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

计算机图形学 | 实验十几何纹理法线贴图

华中科技大学《计算机图形学》课程

MOOC地址计算机图形学HUST

计算机图形学 | 实验十几何纹理法线贴图

在正式搭建环境之前我们先来介绍一下读完下面的部分你会了解些什么

  • 了解什么是法线贴图
  • 为什么需要切线空间
  • 如何加载和使用法线贴图
  • 如何引入切线空间并在着色器中使用

接下来我们来介绍一下绘制具有法线贴图的立方体的效果。

绘制效果如下左图为无切线空间的法线贴图右图为有切线空间的法线贴图

在这里插入图片描述

什么是法线贴图

在我们进行各种图形的绘制时为了尽量提高真实感会采用纹理贴图的方法但是例如我们在一个正方形上贴砖墙的纹理现实中的砖墙是凹凸不平的而且我们绘制的正方形却是一个平面在加入光照模型时也不能通过光照反应砖墙的细节这时我们引入法线贴图的办法去赋予纹理每个点相应的法线信息去形成不同的光照效果。

如下图左图为无法线贴图的情况右图为引入法线贴图的情况。

在这里插入图片描述

法线贴图相当于针对原纹理贴图的每个像素点添加的其独特的法线细节。所有的法线贴图都是对应局部坐标的法线他们是一种偏蓝色调的纹理你在网上找到的几乎所有法线贴图都是这样的。这是因为所有法线的指向都偏向z轴0, 0, 1这是一种偏蓝的颜色。法线向量从z轴方向也向其他方向轻微偏移颜色也就发生了轻微变化这样看起来便有了一种深度。例如你可以看到在每个砖块的顶部颜色倾向于偏绿这是因为砖块的顶部的法线偏向于指向正y轴方向0, 1, 0这样它就是绿色的了。

在这里插入图片描述

为什么需要切线空间

使用Heightmap可以为物体表面增加法线细节但是从heightmap提取的法线是局部坐标系内的法线正常的使用需要我们建立一个切线空间去将heightmap中提取出的法线转换到世界坐标系中之前的效果图我们可以看到左侧为不引入切线空间的法线贴图它的每个面的法线都是一样的所以显得亮度相同而右侧是引入了切线空间的法线贴图它每个面的法线朝向都不相同。

在这里插入图片描述

加载法线贴图

加载法线贴图和加载纹理的方式可以说是一模一样的在此不再赘述。

Texture cube_normal; 
//加载法线贴图
unsigned int cube_normal_texture = cube_normal.LoadTextureFromFile("res/texture/cube_normal.jpg");

法线贴图是把法线数据储存在纹理图片中所以我们需要根据法线贴图和纹理坐标去采样获得一点的法线贴图的颜色值将其rgb颜色值作为法线的xyz轴读取成法线向量但是颜色的范围是在0 ~ 1之间而法线参数是在-1 ~ 1之间所以我们需要进行范围的转化先乘以2扩大范围到0 ~ 2再减去1使法线范围变化到 -1 ~ 1最后一般我们使用的法线都应该是单位向量所以我们需要进行标准化操作。

vec3 normal = texture(texture_normal, fs_in.TexCoords).rgb;
normal = normalize(normal * 2.0f - 1.0f);

然后我们使用从法线贴图中取样获得的法线来计算光照即可。

引入切线空间

法线贴图中的法线向量在切线空间中法线永远指着正z方向。如果模型上有无数的朝向不同方向的表面这就不可行了。所以一种解决问题的方式是计算出一种矩阵把法线从局部空间变换到一个世界空间这样它们就能和表面法线方向对齐了。

这种矩阵叫做TBN矩阵这三个字母分别代表tangent、bitangent和normal向量。这是建构这个矩阵所需的向量。要建构这样一个把切线空间转变为不同空间的变异矩阵我们需要三个相互垂直的向量它们沿一个表面的法线贴图对齐于上、右、前。

在这里插入图片描述

为了获得TBN矩阵需要进行以下步骤

首先我们可以把边E1和E2用切线向量T和副切线向量B的线性组合表示出来。

E是两个向量位置的差ΔU和ΔV是纹理坐标的差。

图中我们可以看到边E2纹理坐标的不同E2是一个三角形的边这个三角形的另外两条边是ΔU2和ΔV2它们与切线向量T和副切线向量B方向相同。这样我们可以把边E1和E2用切线向量T和副切线向量B的线性组合表示出来注意T和B都是单位长度在TB平面中所有点的T、B坐标都在0到1之间因此可以进行这样的组合

在这里插入图片描述

然后也可以写成这样

在这里插入图片描述

上面的方程允许我们把它们写成另一种格式矩阵乘法。

在这里插入图片描述

两边都乘以ΔUΔV的逆矩阵等于

在这里插入图片描述

最后用1除以逆矩阵的行列式再乘以它的共轭矩阵。

在这里插入图片描述

由此我们可以手工算出TBN矩阵当然也可以通过编写算法去计算相应的TBN矩阵。

以下为TBN矩阵的计算算法其中pos1pos2pos3为世界空间内坐标uv1uv2uv3为纹理坐标由于三个向量两两正交则可以计算出第三个向量

//我们先计算三角形的边和deltaUV坐标 
glm::vec3 edge1 = pos2 - pos1; //即计算公式中的E1 
glm::vec3 edge2 = pos3 - pos1; //即计算公式中的E2 
glm::vec2 deltaUV1 = uv2 - uv1; //即计算公式中的U1 V1 
glm::vec2 deltaUV2 = uv3 - uv1; //即计算公式中的U2 V2 
//有了计算切线和副切线的必备数据计算 
GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y); 
tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); 
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); 
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); 
tangent1 = glm::normalize(tangent1); 
//把结果转换成单位矩阵 
bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x); 
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y); 
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); 
bitangent1 = glm::normalize(bitangent1); //把结果转换成单位矩阵

切线空间的TN向量我们从顶点数组中传入但是需要注意的是传入的T N向量是局部坐标系的TN向量我们需要乘以Model将其转换至世界坐标系model矩阵是mat4所以我们需要先将T向量变化为4维向量乘完后再变回三维向量最后依旧不要忘记标准化操作最后得到了我们需要的TN向量

vec3 T = normalize(vec3(model * vec4(aTangent, 0.0f))); 
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0f))); 
vec3 B = normalize(cross(T, N));

接下来只需要在从法线贴图读取了法线数据并进行范围转化之后将TBN矩阵与法线相乘即可引入切线空间将法线变换到世界空间中。

// 从法线贴图范围[0,1]获取法线 
vec3 normal = texture(texture_normal, fs_in.TexCoords).rgb; 
// 将法线向量转换为范围[-1,1] 
normal = normalize(normal * 2.0f - 1.0f); 
//引入切线到世界空间变换
normal = normalize(fs_in.TBN * normal);

结果

在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6