OpenGL自制3D游戏引擎入门III
绘制三角形
一、概述
- 先来记三个单词
- 顶点数组对象:Vertex Array Object,VAO
- 顶点缓冲对象:Vertex Buffer Object,VBO
- 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
- 图形渲染管线可以被划分为两个主要部分:第一部分把3D坐标转换为2D,第二部分是把2D坐标转变为实际有颜色的像素(Tip:2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制)
- 我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做 顶点数据(Vertex Data) ;顶点数据是一系列点的集合,一个顶点是一个3D坐标的数据集合,顶点数据是用 顶点属性(Vertex Attribute) 表示的,它可以包含任何我们相用的数据
- 图元(Primitive): 给OpenGL一些提示,让其知道需要使用哪些图像原型
- GL_POINTS(点)
- GL_TRIANGLES(三角形)
- GL_LINE_STRIP(直线)
二、顶点输入
标准化设备坐标:OpenGL仅当3D坐标在3个轴上都为-1.0f到1.0f范围内时才处理它
简单给出一个例子,可以看到一个简单的图形中顶点数据包括了顶点坐标,法线,顶点与面(法线)的关系等等众多信息
工具软件给出一个.obj文件,CPU提出其中的信息数组并传递到GPU中缓存起来(VBO),根据规则形成索引(VAO),进入顶点着色器使用glGenBuffers函数和一个缓冲ID生成一个VBO对象(VAO对象类似)。
OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER,我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上。
绑定完成后调用glBufferData函数,把之前定义的顶点数据复制到缓冲的内存中。float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
这里提一句glBufferData函数中的第四个参数指定了我们希望显卡如何管理给定的数据,三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分
- GL_STATIC_DRAW :数据不会或几乎不会改变
- GL_DYNAMIC_DRAW:数据会被改变很多
- GL_STREAM_DRAW :数据每次绘制时都会改变
三、顶点着色器&片段着色器
暂时将顶点着色器的源代码硬编码在代码文件顶部的C风格字符串中
const char* vertexShaderSource = "#version 330 core\n" "layout(location = 0) in vec3 aPos;\n" "void main()\n" "{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}\n"; const char* fragmentShaderSource = "v#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n";
创建着色器对象并把着色器源码附加到着色器对象上然后编译
unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader);
着色器程序是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。
unsigned int shaderProgram; shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); glLinkProgram(fragmentShader);
链接顶点属性
位置数据被储存为32位(4字节)浮点值
每个位置包含3个这样的值
在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)
数据中第一个值在缓冲开始的位置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);
四、绘制三角形
OpenGL给我们提供了glDrawArrays函数,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元
glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3);
附上目前为止的全部代码以供参考
#define GLEW_STATIC #include <GL/glew.h> #include <GLFW/glfw3.h> #include <iostream> float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f }; const char* vertexShaderSource = "#version 330 core\n" "layout(location = 0) in vec3 aPos;\n" "void main()\n" "{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}\n"; const char* fragmentShaderSource = "v#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);}\n"; void processInput(GLFWwindow* window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, true); } } int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本号 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //MAC开发需要声明这一行 // Open GLFW Window GLFWwindow* window = glfwCreateWindow(800, 600, "MyWindow", NULL, NULL); if (window == NULL) { std::cout << "Open window failed" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // Init GLEW glewExperimental = true; if (glewInit() != GLEW_OK) { std::cout << "Init GLEW failed" << std::endl; glfwTerminate(); return -1; } glViewport(0, 0, 800, 600);//前俩参数为锚点,后俩为渲染窗口的宽高 unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); unsigned int VBO; glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); unsigned int shaderProgram; shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); glLinkProgram(fragmentShader); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); while (!glfwWindowShouldClose(window)) { processInput(window); glClearColor(0.7f, 0, 0, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; }