目录
实验目的与要求
- 了解层级建模基本概念
- 掌握简单平移,缩放,旋转的矩阵构建
- 了解变换矩阵在层级模型父子节点间的传递关系
实验过程及内容
理论背景
层次模型是指用树型(层次)结构表示实体类型及实体间联系的数据模型称为层次模型(Hierarchical Model)。
以实例代码中的机械手臂为例,其对应的层次模型表示如下
在对层次模型中的节点进行操作的时候,此操作不但会影响该节点的位置和方向,同样会影响该节点的所有子孙节点。
如在机械手臂模型中,底座的操作会影响整个模型,但是小臂的操作只影响自己。
实验内容
- 绘制每一个节点,完成base(),upper_arm(),lower_arm()三个函数。
- 构建子节点局部变化矩阵,使底座绕y轴旋转,大小臂绕z轴旋转,且大臂的旋转中心为大臂与底座的关节,小臂的旋转中心为大小臂的关节。根据遍历顺序完成display()函数。
节点的绘制以底座为例:
实验中按照层次模型深度优先遍历顺序,按照(底座,大臂,小臂)的顺序对节点进行绘制。注意,子节点的变换矩阵等于父节点的变换矩阵乘以子节点的局部变换矩阵。
示例和练习
1)示例代码中已经实现了base()函数,在display()函数添加代码
可以得到如下(a)的结果,将整个实验内容完成可以得到(b)的效果
2)示例代码中已经实现部分键盘触发功能,学生只需选择1、2或3触发相应部件操作;如图(c)所示,选中需要操作的节点以后,通过键盘A键和S键完成机器模型的变化。u/shift+u、i/shift+i和o/shift+o可以改变观察视角。
实验代码
实验补充一——main.cpp
#include "Angel.h"
#include "TriMesh.h"
#include "Camera.h"
#include "MeshPainter.h"
#include <vector>
#include <string>
#define White glm::vec3(1.0, 1.0, 1.0)
#define Yellow glm::vec3(1.0, 1.0, 0.0)
#define Green glm::vec3(0.0, 1.0, 0.0)
#define Cyan glm::vec3(0.0, 1.0, 1.0)
#define Magenta glm::vec3(1.0, 0.0, 1.0)
#define Red glm::vec3(1.0, 0.0, 0.0)
#define Black glm::vec3(0.0, 0.0, 0.0)
#define Blue glm::vec3(0.0, 0.0, 1.0)
#define Brown glm::vec3(0.5, 0.5, 0.5)
int WIDTH = 600;
int HEIGHT = 600;
int mainWindow;
TriMesh* cube = new TriMesh();
Camera* camera = new Camera();
Light* light = new Light();
MeshPainter *painter = new MeshPainter();
// 这个用来回收和删除我们创建的物体对象
std::vector<TriMesh *> meshList;
// 关节角
enum{
Base = 0,
LowerArm = 1,
UpperArm = 2,
NumAngles = 3
};
int Axis = Base;
GLfloat Theta[NumAngles] = {0.0};
// 菜单选项值
const int Quit = 4;
// 尺寸参数
const GLfloat BASE_HEIGHT = 0.2;
const GLfloat BASE_WIDTH = 0.5;
const GLfloat UPPER_ARM_HEIGHT = 0.3;
const GLfloat UPPER_ARM_WIDTH = 0.2;
const GLfloat LOWER_ARM_HEIGHT = 0.4;
const GLfloat LOWER_ARM_WIDTH = 0.1;
// 绘制底座
void base(glm::mat4 modelView)
{
// 按长宽高缩放正方体,平移至合适位置
glm::mat4 instance = glm::mat4(1.0);
instance = glm::translate(instance, glm::vec3(0.0, BASE_HEIGHT / 2, 0.0));
instance = glm::scale(instance, glm::vec3(BASE_WIDTH, BASE_HEIGHT, BASE_WIDTH));
// 绘制,由于我们只有一个立方体数据,所以这里可以直接指定绘制painter中存储的第0个立方体
painter->drawMesh(0, modelView * instance, light, camera);
}
// 绘制大臂
void upper_arm(glm::mat4 modelView)
{
// @TODO: 参考底座的绘制,在此添加代码绘制大臂
// 创建一个模型矩阵实例,初始化为单位矩阵
glm::mat4 instance = glm::mat4(1.0);
// 平移到大臂中心位置
instance = glm::translate(instance, glm::vec3(0.0, UPPER_ARM_HEIGHT / 2, 0.0));
// 缩放大臂,使其具有指定的宽度、高度和宽度
instance = glm::scale(instance, glm::vec3(UPPER_ARM_WIDTH, UPPER_ARM_HEIGHT, UPPER_ARM_WIDTH));
// 绘制,由于只有一个立方体数据,这里直接指定绘制painter中存储的第0个立方体
painter->drawMesh(0, modelView * instance, light, camera);
}
// 绘制小臂
void lower_arm(glm::mat4 modelView)
{
// @TODO: 参考底座的绘制,在此添加代码绘制小臂
// 创建一个模型矩阵实例,初始化为单位矩阵
glm::mat4 instance = glm::mat4(1.0);
// 平移到小臂中心位置
instance = glm::translate(instance, glm::vec3(0.0, LOWER_ARM_HEIGHT / 2, 0.0));
// 缩放小臂,使其具有指定的宽度、高度和宽度
instance = glm::scale(instance, glm::vec3(LOWER_ARM_WIDTH, LOWER_ARM_HEIGHT, LOWER_ARM_WIDTH));
// 绘制,由于只有一个立方体数据,这里直接指定绘制painter中存储的第0个立方体
painter->drawMesh(0, modelView * instance, light, camera);
}
void init()
{
std::string vshader, fshader;
// 读取着色器并使用
vshader = "shaders/vshader.glsl";
fshader = "shaders/fshader.glsl";
//创建机器臂的基础立方体
cube->setNormalize(false);
cube->generateCube();
cube->setTranslation(glm::vec3(0.0, 0.0, 0.0));
cube->setRotation(glm::vec3(0.0, 0.0, 0.0));
cube->setScale(glm::vec3(1.0, 1.0, 1.0));
// 加到painter中
// 指定纹理与着色器,因为不用纹理图片所以就传个空字符串进去了
painter->addMesh(cube, "Cube", "", vshader, fshader);
meshList.push_back(cube);
glClearColor(1.0, 1.0, 1.0, 1.0);
}
void display()
{
// #ifdef __APPLE__ // 解决 macOS 10.15 显示画面缩小问题
// glViewport(0, 0, WIDTH * 2, HEIGHT * 2);
// #endif
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 绘制底座
glm::mat4 modelView = glm::mat4(1.0);
// 稍微下移一下,让机器人整体居中在原点
modelView = glm::translate(modelView, glm::vec3(0.0, -BASE_HEIGHT, 0.0));
// 底座旋转矩阵
modelView = glm::rotate(modelView, glm::radians(Theta[Base]), glm::vec3(0.0, 1.0, 0.0));
base(modelView); // 首先绘制底座
// @TODO: 在此添加代码完成整个机械手臂绘制
// 大臂变换矩阵
modelView = glm::translate(modelView, glm::vec3(0.0, BASE_HEIGHT, 0.0));
modelView = glm::rotate(modelView, glm::radians(Theta[UpperArm]), glm::vec3(0.0, 0.0, 1.0));
// 绘制大臂
upper_arm(modelView);
// 小臂变换矩阵
modelView = glm::translate(modelView, glm::vec3(0.0, UPPER_ARM_HEIGHT, 0.0));
modelView = glm::rotate(modelView, glm::radians(Theta[LowerArm]), glm::vec3(0.0, 0.0, 1.0));
// 绘制小臂
lower_arm(modelView);
}
void printHelp()
{
std::cout << "================================================" << std::endl;
std::cout << "Use mouse to controll the light position (drag)." << std::endl;
std::cout << "================================================" << std::endl << std::endl;
std::cout << "Use right click to open Menu." << std::endl;
std::cout << "================================================" << std::endl << std::endl;
std::cout << "Keyboard Usage" << std::endl;
std::cout <<
"[Window]" << std::endl <<
"ESC: Exit" << std::endl <<
"h: Print help message" << std::endl <<
std::endl <<
"[Part]" << std::endl <<
"1: Base" << std::endl <<
"2: LowerArm" << std::endl <<
"3: UpperArm" << std::endl <<
std::endl <<
"[Model]" << std::endl <<
"a/A: Increase rotate angle" << std::endl <<
"s/S: Decrease rotate angle" << std::endl <<
std::endl <<
"[Camera]" << std::endl <<
"SPACE: Reset camera parameters" << std::endl <<
"u/U: Increase/Decrease the rotate angle" << std::endl <<
"i/I: Increase/Decrease the up angle" << std::endl <<
"o/O: Increase/Decrease the camera radius" << std::endl << std::endl;
}
// 键盘响应函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
float tmp;
glm::vec4 ambient;
if (action == GLFW_PRESS) {
switch (key)
{
case GLFW_KEY_ESCAPE: exit(EXIT_SUCCESS); break;
case GLFW_KEY_Q: exit(EXIT_SUCCESS); break;
case GLFW_KEY_1: Axis = Base; break;
case GLFW_KEY_2: Axis = UpperArm; break;
case GLFW_KEY_3: Axis = LowerArm; break;
// 通过按键旋转
case GLFW_KEY_A:
Theta[Axis] += 5.0;
if (Theta[Axis] > 360.0)
Theta[Axis] -= 360.0;
break;
case GLFW_KEY_S:
Theta[Axis] -= 5.0;
if (Theta[Axis] < 0.0)
Theta[Axis] += 360.0;
break;
default:
camera->keyboard(key, action, mode);
break;
}
}
}
void cleanData() {
// 释放内存
delete camera;
camera = NULL;
delete light;
light = NULL;
painter->cleanMeshes();
delete painter;
painter = NULL;
for (int i=0; i<meshList.size(); i++) {
delete meshList[i];
}
meshList.clear();
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
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
// 配置窗口属性
GLFWwindow* window = glfwCreateWindow(600, 600, "xxxx_xxx_实验补充1", 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 mesh, shaders, buffer
init();
// 输出帮助信息
printHelp();
// 启用深度测试
glEnable(GL_DEPTH_TEST);
// 启用混合
glEnable(GL_BLEND);
while (!glfwWindowShouldClose(window))
{
display();
// 交换颜色缓冲 以及 检查有没有触发什么事件(比如键盘输入、鼠标移动等)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
cleanData();
return 0;
}
// 每当窗口改变大小,GLFW会调用这个函数并填充相应的参数供你处理。
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}
(by 归忆)