OpenGL3.3+GLFW+GLEW+GLM实现小人行走动画
来源:互联网 发布:肌研洗面奶怎么样 知乎 编辑:程序博客网 时间:2024/06/06 00:59
现在在网上找比较系统的教程基本都是旧版OpenGL,都是基于glut的,然而这个库已经是上个世纪的了……新版的OpenGL3.3及以上的教程,强烈推荐以下这两个:
openGL tutorial http://www.opengl-tutorial.org/cn/
learn openGL https://learnopengl-cn.github.io
两份教程一个侧重理解,一个侧重代码实现,可以穿插着一起看。
另外非常感谢xuhongxu同学。
本文原发于这里,是图形学课的一次实验。
1、环境
OpenGL 3.3
GLFW,用于创建窗口和处理用户输入
GLEW,用于确定OpenGL函数的具体实现
GLM,图像相关的数学计算工具库
OSX 10.10
2、窗口和库的初始化
GLFW窗口的初始化:
if(!glfwInit()){ return -1;}GLFWwindow* window;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//OpenGL版本为3glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//最低兼容版本glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//使用新版本的核心模式glfwWindowHint(GLFW_RESIZABLE , GL_FALSE);//窗口尺寸不可变glfwWindowHint(GLFW_SAMPLES, 4);window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World", NULL, NULL);if (!window){ glfwTerminate(); return -1;}glfwMakeContextCurrent(window);//设为当前窗口glfwSetKeyCallback(window, key_callback);
GLEW库的初始化:
glewExperimental = GL_TRUE;if (glewInit() != GLEW_OK){ glfwTerminate(); return -1;}
3、着色器
OpenGL3.3及以上版本不再使用立即渲染模式,采用核心模式,着色器使用的语言是GLSL。核心模式的工作流程为如下图所示图形渲染管线,处理过程中一步步将3D的图形渲染成屏幕上显示的2D图像。在实验中,需要定义自己的顶点着色器(vertex shader)和片段着色器(fragment shader)。
顶点着色器定义如下:
#version 330 core//第一层缓冲,编号为0,从C++输入数据,顶点位置layout(location = 0) in vec3 vertexPosition_modelspace;//第二层缓冲,编号为1,从C++输入数据,顶点颜色layout(location = 1) in vec3 vertexColor;//输出数据, 给片段着色器out vec4 fragmentColor;//全局数据,MVP矩阵uniform mat4 MVP;void main(){ //顶点位置,参数 gl_Position = MVP * vec4(vertexPosition_modelspace,1); //片段颜色,传递给片段着色器 fragmentColor.xyz = vertexColor; fragmentColor.a=0.9;}
片段着色器定义如下:
#version 330 core//输入,来自顶点着色器的颜色in vec4 fragmentColor;//输出out vec4 color;void main(){ color = fragmentColor;}
在代码中通过文件路径加载顶点着色器和片段着色器,并使用一个变量存储索引。所使用的加载函数loadShaders的主要内容是读取文件和解析等操作,较为繁琐,不再贴出。
const GLchar* vertexShaderFile = "ColorArrays.vertexshader";const GLchar* fragmentShaderFile = "ColorArrays.fragmentshader";GLuint programID = loadShaders(vertexShaderFile, fragmentShaderFile);
在图像渲染的每次循环中,在绘图之前要先获取当前的着色器。
glUseProgram(programID);
4、VAO,VBO,EBO和colorbuffer
创建顶点数组对象(vertex array object),储存所有的顶点属性调用。
GLuint VAO;glGenVertexArrays(1, &VAO);//绑定至上下文glBindVertexArray(VAO);
创建顶点缓冲对象(vertex buffer object)和索引缓冲对象(element buffer object):
GLuint VBO;GLuint EBO;glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);
创建一个通用的立方体。一个立方体有6个面,每个面由2个三角形拼成,一共需要12个三角形。静态创建图像需要将每个三角形的三个顶点的三维坐标依次写出太过繁琐,且有很多重复的数据,因此使用索引的方式动态创建。vertices数组存储立方体的八个顶点,indices数组存储12个三角形所对应的顶点的索引。
static const GLfloat vertices[] = { -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,};static const GLuint indices[] = { 0, 1, 2, 1, 2, 3, 1, 0, 5, 0, 5, 4, 5, 6, 7, 4, 5, 6, 2, 3, 7, 6, 2, 7, 1, 5, 3, 3, 5, 7, 0, 2, 4, 2, 4, 6,};
将对象绑定缓冲,并将数据载入缓冲。
//绑定到arraybuffer上glBindBuffer(GL_ARRAY_BUFFER, VBO);//把数组数据设置到buffer里glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_DYNAMIC_DRAW);
为顶点设置颜色属性,同样使用一个缓冲对象管理,颜色随机生成。
GLuint colorbuffer;glGenBuffers(1, &colorbuffer);//8个顶点,RGB三个值static GLfloat colors[8*3];//srand(time(NULL));for (int v = 0; v < 8 ; v++){ colors[3*v+0] = (float)rand()/RAND_MAX; colors[3*v+1] = (float)rand()/RAND_MAX; colors[3*v+2] = (float)rand()/RAND_MAX;}glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
位置数据和颜色数据是图像的两个属性。将他们传给之前定义好的顶点着色器。
glBindBuffer(GL_ARRAY_BUFFER, VBO);glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);glEnableVertexAttribArray(0);glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);glEnableVertexAttribArray(1);
最后解除VAO的绑定。
glBindVertexArray(0);
5、MVP矩阵
每个物体原本都位于自己的局部空间内,坐标范围在-1到1之间。
模型(Model)矩阵通过平移、旋转、缩放的运算,将图形的局部坐标映射至世界坐标。
观察(View)矩阵定义了相机观察世界空间的位置和角度。
投影(Projection)矩阵确定世界空间中的坐标最终展现在屏幕前的方式。
因此,物体最终的位置由原本的位置数据加上以上三个矩阵变换而得,MVP矩阵的运算在C++代码中进行,然后在顶点着色器中计算最终的坐标数据。
MVP=projection*view*model;//C++代码gl_Position = MVP * vec4(vertexPosition_modelspace,1);//顶点着色器代码
观察矩阵和投影矩阵对整个作图都是统一的。
//从着色器获得uniform变量MVP的索引GLuint MatrixID = glGetUniformLocation(programID, "MVP");//45°水平视野, 4:3, 展示范围远近截面从0.1到100mat4 projection=perspective(45.0f,4.0f/3.0f,0.1f,100.0f);//三个参数分别为相机位置,相机朝向的点的位置,相机头的方向向量mat4 view=lookAt(vec3(4,2,0),vec3(0,0,3),vec3(0,1,0));
用之前定义的通用的立方体作为小人身体的各个部分的元件,分别设置他们的模型矩阵。一共包括身体,头,左臂,右臂,左腿,右腿,6个部件。小人的运动以身体为中心,其他五个部件以身体的位置为基础进行运动。以左手臂为例,想让小人向前走,每次渲染图像时,身体都向前平移一点儿。得到身体的模型矩阵后,计算出左肩膀位置的坐标。先缩放变换出左手臂的形状,再旋转变换左手臂在当前帧的旋转幅度,最后用平移变换将左手臂移动到之前计算出来的左肩膀位置。左手臂的旋转动作随着时间变化。
//身子每次向Z轴方向平移0.05model_body=translate(model_body, vec3(0.0f,0.0f,0.05f));//计算身子的MVPMVP=projection*view*model_body;//将MVP数据传入着色器glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);//绘制图像glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//左手vec4 shd_left(1.0f,0.0f,0.0f,1.0f);//获得身体的左肩膀位置shd_left=model_body*shd_left;model_hand_left=mat4(1.0f);//手臂移动到左肩膀处model_hand_left=translate(model_hand_left,vec3(shd_left.x+0.1,shd_left.y+0.5,shd_left.z));//手臂随时间摆动model_hand_left=rotate(model_hand_left,(float)sin(glfwGetTime()*2)/3,vec3(1.0f,0.0f,0.5f));//平移手臂位置,使旋转轴位于手臂的顶端model_hand_left=translate(model_hand_left,vec3(0.0f,-0.5f,0.0f));//将正方体缩放成长条形的手臂形状model_hand_left=scale(model_hand_left, vec3(0.1f,0.5f,0.1f));//计算手臂的MVPMVP=projection*view*model_hand_left;//传入着色器glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);//绘制图像glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);
其他身体部位同上类似。另外给小人走过的路径绘制一个地板。
//地板mat4 model_stay=mat4(1.0f);model_stay=translate(model_stay, vec3(0.0f,-1.1f,5.0f));model_stay=scale(model_stay, vec3(1.0f,0.1f,5.0f));//...//进入渲染循环//地板MVP=projection*view*model_stay;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//脑袋vec4 neck(0.0f,1.0f,0.0f,1.0f);neck=model_body*neck;model_head=mat4(1.0f);model_head=translate(model_head,vec3(neck.x,neck.y+0.3,neck.z));model_head=rotate(model_head,(float)sin(glfwGetTime()*2)/5,vec3(0.0f,0.0f,1.0f));model_head=scale(model_head, vec3(0.1f,0.1f,0.1f));MVP=projection*view*model_head;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//左手vec4 shd_left(1.0f,0.0f,0.0f,1.0f);shd_left=model_body*shd_left;model_hand_left=mat4(1.0f);model_hand_left=translate(model_hand_left,vec3(shd_left.x+0.1,shd_left.y+0.5,shd_left.z));model_hand_left=rotate(model_hand_left,(float)sin(glfwGetTime()*2)/3,vec3(1.0f,0.0f,0.5f));model_hand_left=translate(model_hand_left,vec3(0.0f,-0.5f,0.0f));model_hand_left=scale(model_hand_left, vec3(0.1f,0.5f,0.1f));MVP=projection*view*model_hand_left;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//右手vec4 shd_right(-1.0f,0.0f,0.0f,1.0f);shd_right=model_body*shd_right;model_hand_right=mat4(1.0f);model_hand_right=translate(model_hand_right,vec3(shd_right.x-0.1,shd_right.y+0.5,shd_right.z));model_hand_right=rotate(model_hand_right,(float)sin(glfwGetTime()*2)/3,vec3(-1.0f,0.0f,-0.5f));model_hand_right=translate(model_hand_right,vec3(0.0f,-0.5f,0.0f));model_hand_right=scale(model_hand_right, vec3(0.1f,0.5f,0.1f));MVP=projection*view*model_hand_right;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//左脚vec4 pp_left(0.5f,-1.0f,0.0f,1.0f);pp_left=model_body*pp_left;model_leg_left=mat4(1.0f);model_leg_left=translate(model_leg_left,vec3(pp_left.x+0.05,pp_left.y,pp_left.z));model_leg_left=rotate(model_leg_left,(float)sin(glfwGetTime()*2)/3,vec3(-1.0f,0.0f,0.0f));model_leg_left=translate(model_leg_left,vec3(0.0f,-0.5f,0.0f));model_leg_left=scale(model_leg_left, vec3(0.1f,0.5f,0.1f));MVP=projection*view*model_leg_left;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);//右脚vec4 pp_right(-0.5f,-1.0f,0.0f,1.0f);pp_right=model_body*pp_right;model_leg_right=mat4(1.0f);model_leg_right=translate(model_leg_right,vec3(pp_right.x-0.05,pp_right.y,pp_right.z));model_leg_right=rotate(model_leg_right,(float)sin(glfwGetTime()*2)/3,vec3(1.0f,0.0f,0.0f));model_leg_right=translate(model_leg_right,vec3(0.0f,-0.5f,0.0f));model_leg_right=scale(model_leg_right, vec3(0.1f,0.5f,0.1f));MVP=projection*view*model_leg_right;glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);glDrawElements(GL_TRIANGLES, 12*3, GL_UNSIGNED_INT, (void*)0);
6、结果
成果图gif太大,贴不上来,可以戳进这里看。
- OpenGL3.3+GLFW+GLEW+GLM实现小人行走动画
- 什么是GLEW GLFW GLM
- vs2010 OpenGL+glfw+glew+glm
- Using GLEW, GLFW, and GLM
- VS 2008 OpenGL+glfw+glew+glm 配置
- 使用canvas实现行走的小人动画
- windows和ubuntu安装opengl:glfw+glew+glm
- openGL编程-环境配置(glfw;glew;glm相关 )
- 用glew,glfw实现opengl-学习笔记3着色器
- 用glew,glfw实现opengl绘制3D学习笔记1-实现一个窗口
- 用glew,glfw,FreeImag实现opengl画图->第五课 摄像机
- VS2015配置OpenGL(glfw,glew)
- mac 下配置GLEW + GLFW
- glew,glfw实现最新的opengl-学习笔记4实现纹理
- 用glew,glfw实现的opengl 学习笔记2画一个四方形
- 用glew,glfw实现opengl学习笔记5课纹理(2)
- 用glew,glfw,FreeImage实现opengl学习笔记6坐标变换
- visual studio 2010 + sdl2 + glm + glew安装
- Android反编译
- Codevs 1533互斥的数-hash
- Android问题—Android中Cookie问题的处理
- BZOJ-1191 (二分图匹配)
- 表单验证 ajax异步请求实例 json传参
- OpenGL3.3+GLFW+GLEW+GLM实现小人行走动画
- 数据挖掘---分类评估指标和回归问题
- mysql注入绕过空格过滤的方法
- 递归递推练习——D
- 设计模式(十九) 备忘录模式
- Hex、bin、axf、elf格式文件小结
- shiro验证【springboot mybatis个人博客系统(二)】
- Spring AOP 演化过程(二):基于代理的经典Spring AOP
- 1. Java NIO系列之介绍