计算机图形学 实验二 三维模型读取与控制
目录
一、实验内容
二、具体内容
(在实验2.3的基础上进行修改)
1、OFF格式三维模型文件的读取
2、三维模型的旋转动画
3、键盘鼠标的交互
4、模型的修改
三、代码
一、实验内容
- 读取实验提供的off格式三维模型,并对其赋色。利用鼠标和键盘的交互,控制动画效果,模型的颜色自己可以自行设置,好看就行。
二、具体内容
(在实验2.3的基础上进行修改)
1、OFF格式三维模型文件的读取
参考上机实验2.2的内容,完成对OFF格式三维模型文件的读取与显示,可改变物体的显示颜色,尽量特别,但不要太难看。
1)修改init()方法,读取OFF格式三维模型文件cow.off。
2)修改颜色:
在readoff()方法中:将坐标值([-1,1])映射到颜色值([0,1])
方法1:坐标值加1,除2(结果和实验给的类似)
方法2:坐标值取绝对值
(应该还不算难看)
2、三维模型的旋转动画
参考实验2.1中动画的生成方式,并结合实验2.3中对模型进行旋转变换的过程,生成旋转动画。
默认绕X轴旋转,每1000毫秒转一下rotateDelta角度。
定义相关变量:
在mian函数中定义相关操作:
3、键盘鼠标的交互
参考实验2.1中鼠标与键盘的交互,通过键盘设定选择绕x、y、z轴进行旋转,通过鼠标左右键控制动画的开始与暂停。
1)通过键盘设定选择绕x、y、z轴进行旋转:
在key_callback函数内增加增加如下代码,通过按“X”、“Y”、“Z”控制。
2)通过鼠标左右键控制动画的开始与暂停:
增加mouse_button_callback函数:
然后在main函数中绑定。
3)修改提示语:
4、模型的修改
参考以下代码,通过键盘设定可以在cow.off和cube.off之间切换。
清除顶点数组缓存:
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
二维向量:
std::vector<std::vector<?>>
其他STL相关代码可以查阅C++ STL 教程 | 菜鸟教程 (runoob.com)
1)添加变量表示绘制的是牛还是方块:
2)修改init()函数:通过判断currentModel选择读取的文件:
3)在key_callback函数中添加如下代码:
通过按下“N”绘制牛,按下“M”绘制方块。
4)并修改相关提示语。
效果如下:
三、代码
1、main.cpp
#include "Angel.h"
#include "TriMesh.h"
#include <vector>
#include <string>
//#include "main.h"
using namespace std;
const int X_AXIS = 0;
const int Y_AXIS = 1;
const int Z_AXIS = 2;
const int TRANSFORM_SCALE = 0;
const int TRANSFORM_ROTATE = 1;
const int TRANSFORM_TRANSLATE = 2;
const double DELTA_DELTA = 0.3; // Delta的变化率
const double DEFAULT_DELTA = 0.5; // 默认的Delta值
double scaleDelta = DEFAULT_DELTA;
double rotateDelta = DEFAULT_DELTA;
double translateDelta = DEFAULT_DELTA;
glm::vec3 scaleTheta(1.0, 1.0, 1.0); // 缩放控制变量
glm::vec3 rotateTheta(0.0, 0.0, 0.0); // 旋转控制变量
glm::vec3 translateTheta(0.0, 0.0, 0.0); // 平移控制变量
int currentTransform = TRANSFORM_ROTATE; // 设置当前变换
int mainWindow;
//------------------------------------------------------------------------
bool isplaying = true; // 动画状态
int rotationAxis = X_AXIS; // 当前旋转轴,默认为 X 轴
int rotationTime = 1000; // 每1000帧旋转一次
string currentModel ="cow";
struct openGLObject
{
// 顶点数组对象
GLuint vao;
// 顶点缓存对象
GLuint vbo;
// 着色器程序
GLuint program;
// 着色器文件
std::string vshader;
std::string fshader;
// 着色器变量
GLuint pLocation;
GLuint cLocation;
GLuint matrixLocation;
GLuint darkLocation;
};
openGLObject cube_object;
TriMesh* cube = new TriMesh();
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void bindObjectAndData(TriMesh* mesh, openGLObject& object, const std::string& vshader, const std::string& fshader) {
// 创建顶点数组对象
glGenVertexArrays(1, &object.vao); // 分配1个顶点数组对象
glBindVertexArray(object.vao); // 绑定顶点数组对象
// 创建并初始化顶点缓存对象
glGenBuffers(1, &object.vbo);
glBindBuffer(GL_ARRAY_BUFFER, object.vbo);
glBufferData(GL_ARRAY_BUFFER,
mesh->getPoints().size() * sizeof(glm::vec3) + mesh->getColors().size() * sizeof(glm::vec3),
NULL,
GL_STATIC_DRAW);
// @TODO: Task3-修改完TriMesh.cpp的代码成后再打开下面注释,否则程序会报错
glBufferSubData(GL_ARRAY_BUFFER, 0, mesh->getPoints().size() * sizeof(glm::vec3), &mesh->getPoints()[0]);
glBufferSubData(GL_ARRAY_BUFFER, mesh->getPoints().size() * sizeof(glm::vec3), mesh->getColors().size() * sizeof(glm::vec3), &mesh->getColors()[0]);
object.vshader = vshader;
object.fshader = fshader;
object.program = InitShader(object.vshader.c_str(), object.fshader.c_str());
// 从顶点着色器中初始化顶点的位置
object.pLocation = glGetAttribLocation(object.program, "vPosition");
glEnableVertexAttribArray(object.pLocation);
glVertexAttribPointer(object.pLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
// 从顶点着色器中初始化顶点的颜色
object.cLocation = glGetAttribLocation(object.program, "vColor");
glEnableVertexAttribArray(object.cLocation);
glVertexAttribPointer(object.cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(mesh->getPoints().size() * sizeof(glm::vec3)));
// 获得矩阵存储位置
object.matrixLocation = glGetUniformLocation(object.program, "matrix");
}
void init()
{
std::string vshader, fshader;
// 读取着色器文件路径
vshader = "shaders/vshader.glsl";
fshader = "shaders/fshader.glsl";
//cube->generateCube();
//cube->readOff("./Models/cow.off");
if (currentModel == "cow") {
cube->readOff("./Models/cow.off");
}
else {
cube->generateCube();
}
bindObjectAndData(cube, cube_object, vshader, fshader);
// 设置背景色为黑色
glClearColor(0.0, 0.0, 0.0, 1.0);
}
// 渲染函数
void display()
{
// 清空颜色缓冲和深度缓冲
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(cube_object.program);
glBindVertexArray(cube_object.vao);
// 初始化变换矩阵 glm::mat4表示 4x4 矩阵
glm::mat4 m(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
// @TODO: Task4-在此处修改函数,计算最终的变换矩阵
// 调用函数传入三种变化的变化量,累加得到变化矩阵
// 注意三种变化累加的顺序
// 构建旋转矩阵
glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0), rotateTheta.x, glm::vec3(1.0, 0.0, 0.0))
* glm::rotate(glm::mat4(1.0), rotateTheta.y, glm::vec3(0.0, 1.0, 0.0))
* glm::rotate(glm::mat4(1.0), rotateTheta.z, glm::vec3(0.0, 0.0, 1.0));
// 构建缩放矩阵
glm::mat4 scaleMatrix = glm::scale(glm::mat4(1.0), glm::vec3(scaleTheta.x, scaleTheta.y, scaleTheta.z));
// 构建平移矩阵
glm::mat4 translateMatrix = glm::translate(glm::mat4(1.0), glm::vec3(translateTheta.x, translateTheta.y, translateTheta.z));
// 按照平移、旋转、缩放的顺序相乘得到最终的变换矩阵
m = translateMatrix * rotationMatrix * scaleMatrix;
// 传递变换矩阵到着色器
glUniformMatrix4fv(cube_object.matrixLocation, 1, GL_FALSE, glm::value_ptr(m));
// 绘制立方体中的各个三角形
glDrawArrays(GL_TRIANGLES, 0, cube->getPoints().size());
}
// 通过Delta值更新Theta
// axis 表示坐标轴,sign 表示增加或减少
// currentTransform 表示当前变换类型
void updateTheta(int axis, int sign) {
switch (currentTransform) {
// 根据变换类型,增加或减少某种变换的变化量
case TRANSFORM_SCALE:
//增加或减少缩放的 Theta 值
scaleTheta[axis] += sign * scaleDelta;
break;
case TRANSFORM_ROTATE:
//增加或减少旋转的 Theta 值
rotateTheta[axis] += sign * rotateDelta;
break;
case TRANSFORM_TRANSLATE:
//增加或减少平移的 Theta 值
translateTheta[axis] += sign * translateDelta;
break;
}
}
// 复原Theta和Delta
void resetTheta()
{
scaleTheta = glm::vec3(1.0, 1.0, 1.0); //scaleTheta 表示缩放变换的角度
rotateTheta = glm::vec3(0.0, 0.0, 0.0);
translateTheta = glm::vec3(0.0, 0.0, 0.0);
scaleDelta = DEFAULT_DELTA; //缩放变换的单位变化量
rotateDelta = DEFAULT_DELTA;
translateDelta = DEFAULT_DELTA;
}
// 更新变化Delta值
void updateDelta(int sign)
{
switch (currentTransform) {
// 根据变化类型增加或减少每一次变化的单位变化量
case TRANSFORM_SCALE:
scaleDelta += sign * DELTA_DELTA;
break;
case TRANSFORM_ROTATE:
rotateDelta += sign * DELTA_DELTA;
break;
case TRANSFORM_TRANSLATE:
translateDelta += sign * DELTA_DELTA;
break;
}
}
void cleanData();
// 处理键盘输入的回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
switch (key)
{
// 退出。
case GLFW_KEY_ESCAPE:
if (action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE);
break;
// 1:缩放模式
case GLFW_KEY_1:
if (action == GLFW_PRESS) currentTransform = TRANSFORM_SCALE;
break;
// 2: 旋转模式
case GLFW_KEY_2:
if (action == GLFW_PRESS) currentTransform = TRANSFORM_ROTATE;
break;
// 3: 移动模式
case GLFW_KEY_3:
if (action == GLFW_PRESS) currentTransform = TRANSFORM_TRANSLATE;
break;
// 4: 绘制线。
case GLFW_KEY_4:
if (action == GLFW_PRESS) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
break;
// 5: 绘制面。
case GLFW_KEY_5:
if (action == GLFW_PRESS) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
break;
// Q: 增加 x。
case GLFW_KEY_Q:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(X_AXIS, 1);
break;
// A: 减少 x。
case GLFW_KEY_A:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(X_AXIS, -1);
break;
// W: 增加 y。
case GLFW_KEY_W:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Y_AXIS, 1);
break;
// S: 减少 y。
case GLFW_KEY_S:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Y_AXIS, -1);
break;
// E: 增加 z。
case GLFW_KEY_E:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Z_AXIS, 1);
break;
// D: 减少 z。
case GLFW_KEY_D:
if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Z_AXIS, -1);
break;
// R: 增加变化量。
case GLFW_KEY_R:
if (action == GLFW_PRESS) updateDelta(1);
break;
// F: 减少变化量。
case GLFW_KEY_F:
if (action == GLFW_PRESS) updateDelta(-1);
break;
// T: 所有值重置。
case GLFW_KEY_T:
if (action == GLFW_PRESS) resetTheta();
break;
//-------------------------------------------------------------------
// 选择绕 X 轴旋转
case GLFW_KEY_X:
if (action == GLFW_PRESS) rotationAxis = X_AXIS;
break;
// 选择绕 Y 轴旋转
case GLFW_KEY_Y:
if (action == GLFW_PRESS) rotationAxis = Y_AXIS;
break;
// 选择绕 Z 轴旋转
case GLFW_KEY_Z:
if (action == GLFW_PRESS) rotationAxis = Z_AXIS;
break;
// N: 加载cow.off模型
case GLFW_KEY_N:
if (action == GLFW_PRESS) {
glBindVertexArray(0);//清除顶点数组缓存
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
currentModel = "cow";
init();
}
break;
// M: 加载cube模型
case GLFW_KEY_M:
if (action == GLFW_PRESS) {
glBindVertexArray(0);//清除顶点数组缓存
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
currentModel = "cube";
init();
}
break;
}
}
//-------------------------------------------------------------------------------
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
isplaying = true; // 左键按下,开始动画
}
if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
isplaying = false; // 右键按下,暂停动画
}
}
// 输出帮助信息
void printHelp() {
printf("%s\n\n", "3D Transfomations");
printf("Keyboard options:\n");
printf("n: Draw cow\n");
printf("m: Draw block\n");
printf("left mouse button: start playing\n");
printf("right mouse button: Pause playback\n");
printf("x: Rotate around the X-axis\n");
printf("y: Rotate around the Y-axis\n");
printf("z: Rotate around the Z-axis\n");
printf("The following are the operations previously used:\n");
printf("1: Transform Scale\n");
printf("2: Transform Rotate\n");
printf("3: Transform Translate\n");
printf("q: Increase x\n");
printf("a: Decrease x\n");
printf("w: Increase y\n");
printf("s: Decrease y\n");
printf("e: Increase z\n");
printf("d: Decrease z\n");
printf("r: Increase delta of currently selected transform\n");
printf("f: Decrease delta of currently selected transform\n");
printf("t: Reset all transformations and deltas\n");
}
// 清理数据
void cleanData() {
cube->cleanData();
// 释放内存
delete cube;
cube = NULL;
// 删除绑定的对象
glDeleteVertexArrays(1, &cube_object.vao);
glDeleteBuffers(1, &cube_object.vbo);
glDeleteProgram(cube_object.program);
}
int main(int argc, char** argv)
{
// 初始化GLFW库,必须是应用程序调用的第一个GLFW函数
glfwInit();
// 配置GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
//设置字符格式
#pragma execution_character_set("GBK");
GLFWwindow* window = glfwCreateWindow(600, 600, "homework", NULL, NULL);
if (window == NULL) {
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 调用任何OpenGL的函数之前初始化GLAD
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
init();
// 输出帮助信息
printHelp();
// 启用深度测试
glEnable(GL_DEPTH_TEST);
//------------------------------------------------------------------------
glfwSetMouseButtonCallback(window, mouse_button_callback); // 设置鼠标回调
int flag = clock();
while (!glfwWindowShouldClose(window))
{
display();
// 交换颜色缓冲 以及 检查有没有触发什么事件(比如键盘输入、鼠标移动等)
glfwSwapBuffers(window);
glfwPollEvents();
//--------------------------------------------------------------------------------
//处理动画
int now = clock();
if (isplaying) {
// 每1000帧旋转一定的角度
if ((now - flag) >= rotationTime) {
if (rotationAxis == X_AXIS) {
rotateTheta.x += rotateDelta; // 沿X轴旋转
}
else if (rotationAxis == Y_AXIS) {
rotateTheta.y += rotateDelta; // 沿Y轴旋转
}
else if (rotationAxis == Z_AXIS) {
rotateTheta.z += rotateDelta; // 沿Z轴旋转
}
flag = now;
}
}
}
cleanData();
return 0;
}
2、TriMesh.cpp
#include "TriMesh.h"
// 一些基础颜色
const glm::vec3 basic_colors[8] = {
glm::vec3(1.0, 1.0, 1.0), // White
glm::vec3(1.0, 1.0, 0.0), // Yellow
glm::vec3(0.0, 1.0, 0.0), // Green
glm::vec3(0.0, 1.0, 1.0), // Cyan
glm::vec3(1.0, 0.0, 1.0), // Magenta
glm::vec3(1.0, 0.0, 0.0), // Red
glm::vec3(0.0, 0.0, 0.0), // Black
glm::vec3(0.0, 0.0, 1.0) // Blue
};
// 立方体的各个点
const glm::vec3 cube_vertices[8] = {
glm::vec3(-0.5, -0.5, -0.5),
glm::vec3(0.5, -0.5, -0.5),
glm::vec3(-0.5, 0.5, -0.5),
glm::vec3(0.5, 0.5, -0.5),
glm::vec3(-0.5, -0.5, 0.5),
glm::vec3(0.5, -0.5, 0.5),
glm::vec3(-0.5, 0.5, 0.5),
glm::vec3(0.5, 0.5, 0.5)
};
TriMesh::TriMesh()
{
}
TriMesh::~TriMesh()
{
}
std::vector<glm::vec3> TriMesh::getVertexPositions()
{
return vertex_positions;
}
std::vector<glm::vec3> TriMesh::getVertexColors()
{
return vertex_colors;
}
std::vector<vec3i> TriMesh::getFaces()
{
return faces;
}
std::vector<glm::vec3> TriMesh::getPoints()
{
return points;
}
std::vector<glm::vec3> TriMesh::getColors()
{
return colors;
}
void TriMesh::cleanData() {
vertex_positions.clear();
vertex_colors.clear();
faces.clear();
points.clear();
colors.clear();
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
void TriMesh::storeFacesPoints() {
// @TODO: Task-2修改此函数在points和colors容器中存储每个三角面片的各个点和颜色信息
// 根据每个三角面片的顶点下标存储要传入GPU的数据
// 清空points和colors容器
points.clear();
colors.clear();
// 遍历每个面
for (const auto& face : faces) {
// 根据索引获取顶点的位置和颜色
unsigned int x = face.x;
unsigned int y = face.y;
unsigned int z = face.z;
glm::vec3 pos1 = vertex_positions[x];
glm::vec3 col1 = vertex_colors[x];
glm::vec3 pos2 = vertex_positions[y];
glm::vec3 col2 = vertex_colors[y];
glm::vec3 pos3 = vertex_positions[z];
glm::vec3 col3 = vertex_colors[z];
// 将顶点位置和颜色添加到points和colors容器中
points.push_back(pos1);
colors.push_back(col1);
points.push_back(pos2);
colors.push_back(col2);
points.push_back(pos3);
colors.push_back(col3);
}
}
// 立方体生成12个三角形的顶点索引
void TriMesh::generateCube() {
// 创建顶点前要先把那些vector清空
cleanData();
// @TODO: Task1-修改此函数,存储立方体的各个面信息
// vertex_positions和vertex_colors先保存每个顶点的数据
for (int i = 0; i < 8; ++i) {
vertex_positions.push_back(cube_vertices[i]);
// 这里简单使用基本颜色数组中的颜色,每个顶点按顺序分配颜色
vertex_colors.push_back(basic_colors[i % 8]);
}
// faces再记录每个面片上顶点的下标
// 立方体12个面的顶点索引
// 每个面由两个三角形组成
faces.push_back(vec3i(1, 3, 7)); // 前面
faces.push_back(vec3i(1, 7, 5));
faces.push_back(vec3i(0, 2, 6)); // 后面
faces.push_back(vec3i(0, 6, 4));
faces.push_back(vec3i(2, 6, 7)); // 右面
faces.push_back(vec3i(2, 7, 3));
faces.push_back(vec3i(0, 4, 5)); // 左面
faces.push_back(vec3i(0, 5, 1));
faces.push_back(vec3i(4, 5, 7)); // 顶面
faces.push_back(vec3i(4, 7, 6));
faces.push_back(vec3i(0, 1, 3)); // 底面
faces.push_back(vec3i(0, 3, 2));
storeFacesPoints();
}
void TriMesh::readOff(const std::string& filename)
{
// fin打开文件读取文件信息
if (filename.empty())
{
return;
}
std::ifstream fin;
fin.open(filename);
if (!fin)
{
printf("File on error\n");
return;
}
else
{
printf("File open success\n");
cleanData();
int nVertices, nFaces, nEdges;
// 读取OFF字符串
std::string str;
fin >> str;
// 读取文件中顶点数、面片数、边数
fin >> nVertices >> nFaces >> nEdges;
// 根据顶点数,循环读取每个顶点坐标
for (int i = 0; i < nVertices; i++)
{
glm::vec3 tmp_node;
fin >> tmp_node.x >> tmp_node.y >> tmp_node.z;
vertex_positions.push_back(tmp_node);
//vertex_colors.push_back(tmp_node);
// 将坐标值([-1,1])映射到颜色值([0,1])
/*
//方法1:加1,除2(结果和实验给的一样)
glm::vec3 color = (tmp_node + glm::vec3(1.0f)) * 0.5f;
*/
//方法2:取坐标绝对值
float a = tmp_node.x>0? tmp_node.x:-tmp_node.x;
float b = tmp_node.y>0? tmp_node.y:-tmp_node.y;
float g = tmp_node.z>0? tmp_node.z:-tmp_node.z;
glm::vec3 color(a,b,g);
vertex_colors.push_back(color);
}
// 根据面片数,循环读取每个面片信息,并用构建的vec3i结构体保存
for (int i = 0; i < nFaces; i++)
{
int num, a, b, c;
// num记录此面片由几个顶点构成,a、b、c为构成该面片顶点序号
fin >> num >> a >> b >> c;
faces.push_back(vec3i(a, b, c));
}
}
fin.close();
storeFacesPoints();
};