使用实例化绘制多个立方体
1 #include <GL/glew.h> 2 #include <GLFW/glfw3.h> 3 #include <string> 4 #include <iostream> 5 #include <fstream> 6 #include <cmath> 7 #include <glm/glm.hpp> 8 #include <glm/gtc/type_ptr.hpp> 9 #include <glm/gtc/matrix_transform.hpp> 10 #include "Utils.h" 11 using namespace std; 12 13 const int numVAOs = 1; 14 const int numVBOs = 2; 15 16 float cameraX, cameraY, cameraZ; 17 float cubeLocX, cubeLocY, cubeLocZ; 18 19 GLuint renderingProgram; 20 GLuint vao[numVAOs]; 21 GLuint vbo[numVBOs]; 22 23 GLuint vLoc, projLoc, tfLoc; 24 int width, height; 25 float aspect; 26 glm::mat4 pMat, vMat, mMat, mvMat; 27 28 29 void setupVertices() 30 { 31 // 一个立方体,6个面需要12个三角形,共有36个点(包含重复点),108个坐标值 32 float vertexPositions[108] = { 33 -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 34 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 35 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 36 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 37 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 38 -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 39 -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 40 -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 41 -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 42 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 43 -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 44 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f 45 }; 46 // 生成numVAOs个顶点数组对象 47 glGenVertexArrays(numVAOs, vao); 48 // 绑定(激活)第一个顶点数组对象 49 glBindVertexArray(vao[0]); 50 // 生成numVBOs个缓存区对象 51 glGenBuffers(numVBOs, vbo); 52 // 绑定第一个缓存区对象为顶点缓存类型 53 glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); 54 // 把顶点数据送入顶点缓存 55 glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW); 56 } 57 58 // 在循环外部调用,负责初始化只需执行一次的任务 59 void init(GLFWwindow* window) { 60 // 创建着色器程序 61 renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl"); 62 // 相机在世界坐标中的位置 63 cameraX = 0.0f, cameraY = 0.0f, cameraZ = 8.0f; 64 // 立方体在世界坐标中的位置 65 cubeLocX = 0.0f, cubeLocY = -2.0f, cubeLocZ = 0.0f; 66 // 将立方体顶点数据加载到顶点缓存对象中 67 setupVertices(); 68 } 69 // 在循环内部调用,实现重复执行,调用它的速率被称为帧率 70 void display(GLFWwindow* window, double currentTime) 71 { 72 // 清除深度缓存 73 glClear(GL_DEPTH_BUFFER_BIT); 74 // 指定颜色缓冲区清除后填充的值 75 glClearColor(0.0, 0.0, 0.0, 1.0); 76 // 用指定颜色清除(填充)颜色缓存区 77 glClear(GL_COLOR_BUFFER_BIT); 78 // 将含有两个已编译着色器的程序载入OpenGL管线阶段,并没有运行着色器 79 glUseProgram(renderingProgram); 80 81 // 获取MV矩阵和投影矩阵的统一变量 82 vLoc = glGetUniformLocation(renderingProgram, "v_matrix"); 83 projLoc = glGetUniformLocation(renderingProgram, "proj_matrix"); 84 85 // 构建透视矩阵 86 glfwGetFramebufferSize(window, &width, &height); // 检索窗口的帧缓冲区的当前大小 87 aspect = (float)width / (float)height; // 计算纵横比 88 pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees 89 90 // 构建视图矩阵 91 vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ)); 92 93 // 将视图矩阵赋值给相应的统一变量 94 glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat)); 95 glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat)); 96 97 float timeFactor = (float)currentTime; 98 tfLoc = glGetUniformLocation(renderingProgram, "tf"); 99 glUniform1f(tfLoc, timeFactor); 100 101 // 将VBO关联给顶点着色器中相应的顶点属性 102 glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); // 激活第一个顶点缓存对象 103 // 第一个参数指定我们要配置的顶点属性layout(location = 0); 104 // 第二个参数指定顶点属性的大小; 105 // 第三个参数指定数据的类型; 106 // 第四个参数定义我们是否希望数据被标准化(Normalize); 107 // 第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。 108 // 由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。 109 // 要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。 110 // 一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节。 111 // 最后一个参数的类型是void * ,数据指针, 这个值受到VBO的影响; 112 // 1:在不使用VBO的情况下,就是一个指针,指向的是需要上传到顶点数据指针, 113 // 项目中通常在不使用VBO的情况下,绘制之前,执行glBindBuffer(GL_ARRAY_BUFFER, 0),否则会导致数组顶点无效,界面无法显示; 114 // 2:使用VBO的情况下,先要执行glBindBuffer(GL_ARRAY_BUFFER, 1), 115 // 如果一个名称非零的缓冲对象被绑定至GL_ARRAY_BUFFER目标(见glBindBuffer)且此时一个定点属性数组被指定了, 116 // 那么pointer被当做该缓冲对象数据存储区的字节偏移量。并且,缓冲对象绑定(GL_ARRAY_BUFFER_BINDING)会被存为索引为index的顶点属性数组客户端状态; 117 // 此时指针指向的就不是具体的数据了。因为数据已经缓存在缓冲区了。这里的指针表示位置数据在缓冲中起始位置的偏移量(Offset)。 118 // 由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。 119 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); 120 // 启用第一个顶点属性 121 glEnableVertexAttribArray(0); 122 // 调整OpenGL设置 123 glEnable(GL_DEPTH_TEST); 124 // 深度测试函数:如果输入的深度值小于或等于参考值,则通过 125 glDepthFunc(GL_LEQUAL); 126 // 线框模式 127 //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 128 // 启动管线处理过程,开始绘制 129 //glDrawArrays(GL_TRIANGLES, 0, 36); 130 // 实例化,指定绘制24个立方体 131 glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 24); 132 } 133 134 int main(void) 135 { 136 // 如果glfw初始化失败则返回 137 if (!glfwInit()) { exit(EXIT_FAILURE); } 138 // 设置OpenGL程序的主版本号 139 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); 140 // 设置OpenGL程序的副版本号 141 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 142 // 借助GLFW创建一个窗口 143 GLFWwindow* window = glfwCreateWindow(600, 600, "Graphics Program With OpenGL", nullptr, nullptr); 144 // 将GLFW创建的窗口与当前OpenGL的上下文关联起来 145 glfwMakeContextCurrent(window); 146 // 如果glew初始化失败则返回,glew负责调用OpenGL相关的函数 147 if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); } 148 // 交换间隔指示了直到交换缓冲区前需要等待多少帧,通常被理解为垂直同步。 149 // 默认情况下,交换间隔为0,意味着缓冲区交换会立即发生。 150 // 在一些快速的机器上,因为屏幕保持以典型的60 - 75次每秒的速度更新,许多帧会永远看不到,所以这会浪费许多CPU和GPU周期。 151 // 而且,因为缓冲区可能会在屏幕更新的中途被交换,导致画面撕裂。 152 // 因此,应用需要代表性地设置交换间隔为1。 153 glfwSwapInterval(1); 154 155 init(window); 156 157 // 当用户点击关闭窗口按钮时会返回true 158 while (!glfwWindowShouldClose(window)) 159 { 160 display(window, glfwGetTime()); 161 // GLFW默认使用了双缓冲技术。这意味着每个窗口会有两个渲染缓冲区,一个前置缓冲区和一个后置缓冲区。 162 // 前置缓冲区会在屏幕上显示而后置缓冲区是你渲染的目标。 163 // 当整个帧已经渲染完毕时,两个缓冲区需要进行交换,所以后置缓冲区会变成前置缓冲区,反之亦然。 164 glfwSwapBuffers(window); 165 // 处理窗口相关事件 166 glfwPollEvents(); 167 } 168 // 销毁窗口 169 glfwDestroyWindow(window); 170 // 终止运行 171 glfwTerminate(); 172 173 exit(EXIT_SUCCESS); 174 }
顶点着色器源码:
1 #version 430 2 3 layout (location=0) in vec3 position; 4 5 uniform mat4 v_matrix; 6 uniform mat4 proj_matrix; 7 uniform float tf; // 用于动画和放置立方体的时间因子 8 9 out vec4 varyingColor; 10 11 mat4 buildRotateX(float rad); 12 mat4 buildRotateY(float rad); 13 mat4 buildRotateZ(float rad); 14 mat4 buildTranslate(float x, float y, float z); 15 16 void main(void) 17 { 18 float i = gl_InstanceID + tf; 19 float a = sin(2.0 * i) * 8.0; 20 float b = sin(3.0 * i) * 8.0; 21 float c = sin(4.0 * i) * 8.0; 22 23 mat4 localRotX = buildRotateX(1000 * i); 24 mat4 localRotY = buildRotateY(1000 * i); 25 mat4 localRotZ = buildRotateZ(1000 * i); 26 mat4 localTrans = buildTranslate(a, b, c); 27 28 mat4 newM_matrix = localTrans * localRotX * localRotY * localRotZ; 29 mat4 mv_matrix = v_matrix * newM_matrix; 30 31 gl_Position = proj_matrix * mv_matrix * vec4(position, 1.0); 32 varyingColor = vec4(position, 1.0) * 0.5 + vec4(0.5, 0.5, 0.5, 0.5); 33 } 34 35 // 构建并返回平移矩阵 36 mat4 buildTranslate(float x, float y, float z) 37 { 38 mat4 trans = mat4(1.0, 0.0, 0.0, 0.0, 39 0.0, 1.0, 0.0, 0.0, 40 0.0, 0.0, 1.0, 0.0, 41 x, y, z, 1.0 ); 42 return trans; 43 } 44 45 // 构建并返回绕 x 轴的旋转矩阵 46 mat4 buildRotateX(float rad) 47 { 48 mat4 xrot = mat4(1.0, 0.0, 0.0, 0.0, 49 0.0, cos(rad), -sin(rad), 0.0, 50 0.0, sin(rad), cos(rad), 0.0, 51 0.0, 0.0, 0.0, 1.0 ); 52 return xrot; 53 } 54 55 // 构建并返回绕 y 轴的旋转矩阵 56 mat4 buildRotateY(float rad) 57 { 58 mat4 yrot = mat4(cos(rad), 0.0, sin(rad), 0.0, 59 0.0, 1.0, 0.0, 0.0, 60 -sin(rad), 0.0, cos(rad), 0.0, 61 0.0, 0.0, 0.0, 1.0 ); 62 return yrot; 63 } 64 65 // 构建并返回绕 z 轴的旋转矩阵 66 mat4 buildRotateZ(float rad) 67 { 68 mat4 zrot = mat4(cos(rad), -sin(rad), 0.0, 0.0, 69 sin(rad), cos(rad), 0.0, 0.0, 70 0.0, 0.0, 1.0, 0.0, 71 0.0, 0.0, 0.0, 1.0 ); 72 return zrot; 73 }
片段着色器源码:
1 #version 430 2 3 in vec4 varyingColor; 4 out vec4 color; 5 6 void main(void) 7 { 8 color = varyingColor; 9 }
工具类源码见《第一个3D程序》