计算机图形学:实验四 带纹理的OBJ文件读取和显示
一、程序功能设计
在程序中读取带纹理的obj文件,载入相应的纹理图片文件,将带纹理的模型显示在程序窗口中。实现带纹理的OBJ文件读取与显示功能,具体设计如下:
OBJ文件解析与数据存储
通过实现TriMesh类中的readObj函数,解析OBJ文件中的顶点、法线、UV纹理坐标等信息,将其存储在指定的数据结构中:
- vertex_positions:存储顶点几何坐标。
- vertex_normals:存储顶点法线,用于光照计算。
- vertex_textures:存储UV纹理坐标,用于纹理映射。
- faces:存储面片顶点的索引信息,用于定义几何形状。
- normal_index和texture_index:分别存储面片顶点对应的法线索引和纹理坐标索引。
此外,由于OBJ文件不包含顶点颜色数据,本实验利用顶点法线的数值作为颜色数据进行渲染。
数据组织与存储逻辑完善
通过补全storeFacesPoints函数,进一步完善面片顶点的索引解析与数据存储过程:
- 遍历面片数据,按顶点、法线、UV坐标的索引组织顶点信息。
- 确保所有几何数据、法线数据和UV数据一一对应,以便后续渲染时正确应用纹理。
纹理加载与显示
在main.cpp文件中修改init函数,通过以下步骤实现带纹理模型的加载与显示:
- 加载纹理图片文件并绑定到模型的UV坐标上。
- 根据模型的顶点、法线、纹理坐标等数据渲染模型。
- 显示玩偶和桌子模型,通过调整它们的位置与比例,生成合理的场景布局。
实现效果
实现了多模型、多纹理的加载和显示功能。程序窗口中,带有纹理的玩偶模型和桌子模型正确显示,纹理映射效果清晰且无失真。
二、程序代码实现
补全TriMesh.cpp中的storeFacesPoints函数
通过补全storeFacesPoints函数,根据读取的模型数据,将三角面片的顶点属性(包括顶点位置、法线、颜色、纹理等)整理成适合传递给GPU的数据格式,为后续的渲染做准备。
顶点数据解析与存储(根据三角面片的索引将属性数据组织到GPU需要的格式):遍历每个三角面片的数据,根据顶点的索引将顶点属性依次存入对应的容器(points、colors、normals、textures)。每个三角面片的顶点属性都要按照顶点索引依次写入。要注意的是:
- faces:存储三角形面片的顶点索引。
- color_index:存储面片顶点的颜色索引。
- normal_index:存储面片顶点法线的索引。
- texture_index:存储面片顶点纹理坐标的索引。
补全TriMesh.cpp中的generateDisk函数和generateCone函数
generateDisk 函数生成圆盘,生成一个圆盘的网格,包括底面和相应的三角面片。generateCone 函数生成圆锥,生成一个圆锥体的网格,包括底面和圆锥侧面。两个函数分别用来生成圆盘和圆锥体(同实验4.1),依赖于三角形扇形的方式来连接底面和顶部(尖端)的顶点,生成相应的三角面片。
- generateDisk 函数:
- 生成顶点:使用循环计算每个切片的边界点(顶点坐标 x, y, z),其中 z 始终为 0(在 xy 平面上),并根据该顶点的法向量(法向量始终为 (0, 0, 1))添加颜色(这里简化为与法向量相同的颜色)。在循环结束后,添加一个圆盘的中心点(顶点坐标为 (0, 0, 0))。
- 生成三角面片:使用三角形扇形的方式将每个切片连接起来,形成三角面片,通过 faces.push_back() 存储每个三角形的三个顶点索引。为每个顶点生成相应的纹理坐标,并存储索引。
- 法向量和颜色设置:通过 normal_index 和 color_index 将每个三角面片的顶点与法向量和颜色的索引关联起来。
- generateCone 函数:
- 生成底部顶点:使用循环计算圆锥底部的每个顶点坐标(x, y, z),其中 z 始终为 0。根据每个顶点位置生成法向量(平面上每个顶点的法向量为 (x, y, 0) 的归一化向量),并生成颜色(与法向量相同)。
- 添加圆锥的顶点:圆锥顶端添加一个顶点,坐标为 (0, 0, height),法向量为 (0, 0, 1)。
- 生成圆锥侧面三角面片:每两个相邻的底面顶点和圆锥顶点连接,形成一个三角形。通过 faces.push_back() 将每个侧面三角形的三个顶点索引存储起来。对每个三角面片的三个顶点生成纹理坐标并存储。
- 法向量和颜色设置:通过 normal_index 和 color_index 将三角面片与法向量、颜色的索引关联起来。
最终,所有的顶点、法向量、颜色、纹理坐标和面片索引通过 storeFacesPoints() 存储起来,以便后续使用。
补全TriMesh.cpp中的readObj函数
readObj函数实现了从 .obj 文件中读取三维模型的顶点数据、法向量数据、纹理坐标数据和面片数据,并将这些数据存储在类的成员变量中,供后续的渲染或计算使用。需要补充的代码逻辑如下:
- 解析每一行的数据:
- 顶点数据 ("v"):读取顶点坐标(x, y, z),将其存入 vertex_positions 向量中。
- 法向量数据 ("vn"):读取法向量坐标(x, y, z),将其存入 vertex_normals 向量中。
- 纹理坐标数据 ("vt"):读取纹理坐标(x, y),将其存入 vertex_textures 向量中。
- 面数据 ("f"):读取面数据,包含三个顶点的索引(顶点、纹理和法向量索引),这些索引会被转换为从0开始的索引,并存储在 faces、texture_index、normal_index 中。
- 处理面数据:
每一行的面数据包含三个顶点,使用索引(如 a0, b0, c0)来表示这些顶点在对应的数组中的位置。索引是从1开始的,因此需要将其减去1来转换为从0开始的索引。
- 顶点颜色和法向量同步:
在 vertex_colors 中存储的是法向量(即 vertex_normals),因为通常情况下法向量用作顶点的颜色。
补全main.cpp中的init函数
在init() 函数加载桌子和娃娃的三维模型,并根据指定的平移、旋转和缩放变换进行调整。将这些模型添加到绘制管线(painter)中,供渲染使用。
- 加载并设置桌子模型
- 创建一个新的 TriMesh 对象 table,并读取桌子模型文件 table.obj。
- 通过 setNormalize(true) 方法确保模型数据被归一化。
- 使用 readObj("./assets/table.obj") 函数加载桌子的几何数据。
- 设置桌子的平移(setTranslation)、旋转(setRotation)、缩放(setScale)变换。
平移:glm::vec3(-0.7, 0.0, 0.0) 将桌子稍微平移。
旋转:glm::vec3(-90.0, 0.0, 0.0) 将桌子旋转 -90 度。
缩放:glm::vec3(2.0, 2.0, 2.0) 放大桌子的大小。
将桌子模型添加到 painter 中,通过 painter->addMesh 方法加载模型并指定纹理和着色器。
加载并设置娃娃模型
- 创建一个新的 TriMesh 对象 wawa,并读取娃娃模型文件 wawa.obj。
- 同样通过 setNormalize(true) 方法归一化模型数据,并使用 readObj("./assets/wawa.obj") 函数加载娃娃的几何数据。
- 设置娃娃的平移、旋转和缩放变换:
平移:glm::vec3(0.7, 0.0, 0.0) 将娃娃平移。
旋转:glm::vec3(-90.0, 0.0, 0.0) 旋转 -90 度。
缩放:glm::vec3(2.0, 2.0, 2.0) 放大娃娃的大小。
将娃娃模型添加到 painter 中,指定纹理和着色器。
三、程序运行结果
运行程序初始界面
进行水平方向左右旋转
进行竖直方向旋转