OpenGL入门-画一个三角形(VS2019可运行源码)
2021/10/7 17:13:28
本文主要是介绍OpenGL入门-画一个三角形(VS2019可运行源码),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
2.画一个三角形
配置VS2019,加载常用的库,参考1.1:
链接: link.
2.1预先需要知道的知识
1、屏幕和窗口是2D像素数组,因此当我们描绘3D图形的时候,我们需要工具把3D坐标转变为适应屏幕的2D像素,这就是OpenGL需要完成的工作。
2、这部分工作主要有图形渲染管线来完成(Graphics Pipeline)
第一部分把3D坐标转换为2D坐标
第二部分把2D坐标转换为实际的有颜色的像素
3、2D坐标和像素的不同:2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
4、着色器:在渲染管线当中各自运行的一些小程序,在图形渲染管线中处理我们的数据
5、我们在接下来的代码中以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形
这个数组叫做顶点数据(Vertex Data)。顶点数据是一系列顶点的集合,一个顶点(Vertex)是一个3D坐标的数据的集合。
6、顶点着色器:输入:一个单独的顶点 作用:把3D坐标转为另一种3D坐标,同时可以对顶点数据进行基本处理
7、图元装配(primitive Assembly):将顶点着色器输出的所有顶点作为输入,将所有的顶点装配成指定图元的形状
8、几何着色器(Geoetry Shader):接受图元装配的输出作为输入,通过产生新顶点构造出新的图元来生成其他形状
9、光栅化阶段(Rasterization Stage),把图元映射为最终屏幕上相应的像素。生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
10、片段着色器的主要目的是计算一个像素的最终颜色,通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
2.2 顶点输入
OpenGL是3D图形库,因此我们输入的所有坐标都是3D坐标(x,y,z)的形式。
OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。
所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。
创建数组,指定三角形的三个顶点:
GLfloat vertices[]={ -0.5f,-0.5f,0.0f, 0.5f,-0.5f,0.0f, 0.0f,0.5f,0.0f }; //创建了一个GLfloat数组,因为这里渲染的是一个2D三角形,所以三个顶点的z坐标都为0
定义了顶点数据后,顶点着色器接收数据在GPU上创建内存用于储存顶点数据,并解释内存发送给显卡。
通过VBO管理内存
VBO(Vertex Buffer Objects, VBO)顶点缓冲对象,可以一次性地发送一大批数据到显卡上,而不是每个顶点发送一次。
通过glGenBuffers函数和一个缓冲ID生成一个VBO对象
GLuint VBO;//声明一个GLuint变量,GLuint相当于GL里面类似C++的unsigned int glGenBuffers(1, &VBO); //glGenBuffers 官方解释:generate buffer object names //第一个参数是要生成的缓冲对象的数量,第二个是要输入用来存储缓冲对象名称的数组 //该函数会在buffers里返回n个缓冲对象的名称。 //在本程序中,我们只需要他返回一个数组
声明一个GLuint变量,然后使用glGenBuffers后,它就会把缓冲对象保存在vbo里
glGenBuffers()函数仅仅是生成一个缓冲对象的名称,这个缓冲对象并不具备任何意义,它仅仅是个缓冲对象,还不是一个顶点数组缓冲,它类似于C语言中的一个指针变量,我们可以分配内存对象并且用它的名称来引用这个内存对象。
使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标
顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。
我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
glBindBuffer(GL_ARRAY_BUFFER, VBO); //glBindBuffer 官方解释:bind a named buffer object //第一个就是缓冲对象的类型,第二个参数就是要绑定的缓冲对象的名称,也就是我们在上一个函数里生成的名称VBO
绑定对象的过程就像设置铁路的道岔开关,每一个缓冲类型中的各个对象就像不同的轨道一样,我们将开关设置为其中一个状态,那么之后的列车都会驶入这条轨道。
从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof
计算出顶点数据大小就行。第三个参数是我们希望发送的实际数据。
第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
- GL_STATIC_DRAW :数据不会或几乎不会改变。(基本都是用这个)
- GL_DYNAMIC_DRAW:数据会被改变很多。
- GL_STREAM_DRAW :数据每次绘制时都会改变。
2.3顶点着色器
如果我们打算做渲染的话,我们需要至少设置一个顶点和一个片段着色器
#version 330 core //声明OpenGL版本,必不可少 每个着色器都起始于一个版本声明。 layout (location = 0) in vec3 position; //创建一个vec3输入变量position //位置变量的属性位置值为0 void main() { gl_Position = vec4(position.x, position.y, position.z, 1.0); //设置顶点着色器的输出,把位置数据赋值非预定义的gl_Position变量, //由于我们的输入是一个3分量的向量,我们必须把它转换为4分量的。 //我们可以把vec3的数据作为vec4构造器的参数,同时把w分量设置为1.0f //(我们会在后面解释为什么)来完成这一任务 }
2.4 编译着色器
2.4.1创建着色器对象
GLuint vertexShader vertexShader=glcreateShader(GL_VERTEX_SHADER);
2.4.2 将着色器源码附加到着色器对象上并编译
glshaderSource(vertexShader,1,&vertexShaderSource,NULL); //第一个参数是要编译的着色器对象:VertexShader //第二个参数是指定传递的源码字符串数量 //第三个参数是顶点着色器真正的源码 glCompileShader(vertexShader);
2.5片段着色器
片段着色器全是关于计算像素最后的颜色输出
2.5.1在OpenGL中定义颜色
在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分度,缩写为RGBA。
颜色的每个分量的强度设置在0.0到1.0之间
#version 330 core out vec4 color //声明是一个4分量向量 void main() { color=vec4(1.0f,0.5f,0.2f,1.0f);//橘黄色 //alpha值为1.0代表完全不透明 }
2.5.2 编译片段着色器
使用GL_FRAGMENT_SHADER常量作为着色器类型
GLuint fragmentShader;//定义一个fragmentShader整形变量 fragmentShader = glCreateShader(GL_Fragment_SHADER); //glCreateShader创建一个着色器对象,括号内的参数指定要创建的着色器类型 //只支持两种类型的着色器:GL_VERTEX_SHADER和GL_FRAGMENT_SHADER。 glShaderSource(fragmentShader,1,&fragmentShaderSource,null); glCompileShader(fragmentShader);
GL_VERTEX_SHADER类型的着色器是一个用于在可编程顶点处理器上运行的着色器。 GL_FRAGMENT_SHADER类型的着色器是一个着色器,旨在在可编程片段处理器上运行。
2.6着色器程序
根据前面的步骤,顶点着色器和片段着色器都已经编译了。
着色器程序对象是多个着色器合并之后并最终链接完成的版本
创建一个程序对象
GLuint shaderProgram; shaderProgram = glCreateProgram(); //glCreateProgram函数创建一个程序,并返回新创建程序对象ID引用
把之前编译的着色器附加到程序对象上,然后用函数链接它们
glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram);
得到一个完整的着色器程序后,我们调用glUserProgram函数用刚创建的程序对象作为它的参数,以激活这个程序对象
glUseProgram(shaderProgram);
现在可以删除着色器对象:
glDeleteShader(vertexShader); glDeleteShader(fragmentShader);
2.7 链接顶点属性
我们需要在渲染前指定OpenGL该如何解释顶点数据。
我们的顶点缓冲数据将被如下图解释:
-
位置数据被储存为32-bit(4字节)浮点值
-
每个位置包含3个这样的值
-
在这3个值之间没有间隙(或其他值)。这几个值在数组中紧密排列
-
数据中第一个值在缓冲开始的位置
2.7.1 glVertexAttribPointer函数
使用glVertexAttribPointer函数告诉OpenGL该如何解析顶点数据
glvertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*siezeof(GLfloat),(GLvoid*)0);
glVertexAttribPointer函数的参数:
第一个参数:指定我们要配置的顶点属性。
在前面的顶点着色器中,我们使用了layout(location=0)定义了position顶点属性的位置值location,也就是把顶点属性的位置值设置了为0
第二个参数:指定顶点属性的大小
这里的顶点属性是vec3,由3个值组成,所以大小是3
第三个参数:指定数据类型
vec*都是由浮点数值GL_FLOAT组成
第四个参数:决定数据是否被标准化。
TRUE则所有数据映射到0
第五个参数:输入在连续的顶点属性组之间的间隔。也就是一个顶点的内存大小。
每个顶点的数据占据了3*sizeof(GLfloat)的大小
第六个参数:表示位置数据在缓冲中起始位置的偏移量(Offset)。(不太清楚实际意思。。。)
2.7.1 glEnableVertexAttribArray函数
glEnableVertexAttribArray(0);//以顶点属性位置值作为参数,启用顶点属性,顶点属性默认是禁用的
顶点数组对象(VAO)
VAO存储所有的顶点属性调用,当配置顶点属性指针时,只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
只需要绑定不同的VAO就可以在不同的顶点数据和属性配置之间切换。
VAO中储存的内容:
- glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
- 通过glVertexAttribPointer设置的顶点属性配置。
- 通过
glVertexAttribPointer
调用进行的顶点缓冲对象与顶点属性链接。
VAO和VBO的关系图解
创建和使用VAO
Gluint VAO; glGenVertexArrays(1,&VAO);//创建了一个VAO对象,与创建一个VBO对象类似
要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。
使用glDrawArrays函数激活着色器绘制图形
glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0);
glDrawArrays函数第一个参数是我们打算绘制的OpenGL图元的类型。由于我们在一开始时说过,我们希望绘制的是一个三角形,这里传递GL_TRIANGLES给它。第二个参数指定了顶点数组的起始索引,我们这里填0
。最后一个参数指定我们打算绘制多少个顶点,这里是3(我们只从我们的数据中渲染一个三角形,它只有3个顶点长)。
完整源代码(在vs2019上可运行)
#include <iostream> #include <GL/glew.h> #include <GLFW/glfw3.h> #include <GL/glut.h> // Function prototypes void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); // Window dimensions const GLuint WIDTH = 800, HEIGHT = 600; // Shaders const GLchar* vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 position;\n" "void main()\n" "{\n" "gl_Position = vec4(position.x, position.y, position.z, 1.0);\n" "}\0"; const GLchar* fragmentShaderSource = "#version 330 core\n" "out vec4 color;\n" "void main()\n" "{\n" "color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\n\0"; // The MAIN function, from here we start the application and run the game loop int main() { // Init GLFW glfwInit(); // Set all the required options for GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Create a GLFWwindow object that we can use for GLFW's functions GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr); glfwMakeContextCurrent(window); // Set the required callback functions glfwSetKeyCallback(window, key_callback); // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions glewExperimental = GL_TRUE; // Initialize GLEW to setup the OpenGL Function pointers glewInit(); // Define the viewport dimensions int width, height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); // Build and compile our shader program // Vertex shader GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); // Check for compile time errors GLint success; GLchar infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } // Fragment shader GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); // Check for compile time errors glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } // Link shaders GLuint shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); // Check for linking errors glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); // Set up vertex data (and buffer(s)) and attribute pointers GLfloat vertices[] = { -0.5f, -0.5f, 0.0f, // Left 0.5f, -0.5f, 0.0f, // Right 0.0f, 0.5f, 0.0f // Top }; GLuint VBO, VAO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); // Bind the Vertex Array Object first, then bind and set vertex buffer(s) and attribute pointer(s). glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); // Note that this is allowed, the call to glVertexAttribPointer registered VBO as the currently bound vertex buffer object so afterwards we can safely unbind glBindVertexArray(0); // Unbind VAO (it's always a good thing to unbind any buffer/array to prevent strange bugs) // Game loop while (!glfwWindowShouldClose(window)) { // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions glfwPollEvents(); // Render // Clear the colorbuffer glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Draw our first triangle glUseProgram(shaderProgram); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0); // Swap the screen buffers glfwSwapBuffers(window); } // Properly de-allocate all resources once they've outlived their purpose glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); // Terminate GLFW, clearing any resources allocated by GLFW. glfwTerminate(); return 0; } // Is called whenever a key is pressed/released via GLFW void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); }
这篇关于OpenGL入门-画一个三角形(VS2019可运行源码)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-02Java微服务系统项目实战入门教程
- 2024-11-02Java微服务项目实战:新手入门指南
- 2024-11-02Java项目实战:新手入门教程
- 2024-11-02Java小程序项目实战:从入门到简单应用
- 2024-11-02Java支付系统项目实战入门教程
- 2024-11-02SpringCloud Alibaba项目实战:新手入门教程
- 2024-11-02Swagger项目实战:新手入门教程
- 2024-11-02UNI-APP项目实战:新手入门与初级教程
- 2024-11-02编译部署资料入门教程:一步步学会编译和部署
- 2024-11-02地图服务资料入门指南