OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
本系列文章为Learn OpenGL个人学习总结!
OpenGL入门(一)之认识OpenGL和创建Window
OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
OpenGL入门(三)之着色器Shader
OpenGL入门(四)之纹理Texture
OpenGL入门(五)之Matrix矩阵操作和坐标系统
OpenGL进阶(一)之帧缓冲FrameBuffer
OpenGL进阶(二)之像素缓冲PixelBuffer
图像渲染管线
在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。
它们是由着色器(Shader)程序控制,且运行在GPU上!
一个经典的示图:
- Vertex Data:顶点数据,数组的形式传递3个3D坐标来表示一个三角形,这个数组就是顶点数据。而顶点(Vertex)就是一个3D坐标的数据的集合。每个顶点又包含多个顶点属性(Vertex Attribute),比如position,color,normal(法线)等。
- 顶点着色器(Vertex Shader):把3D坐标转为另一种3D坐标
- 图元装配(Primitive Assembly):将顶点着色器输出的所有顶点作为输入,装配成指定图元的形状。图元就是告诉OpenGL怎么绘制这些点,比如GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。
- 几何着色器(Geometry Shader):把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。
- 光栅化阶段(Rasterization Stage):会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
- 片段着色器(Fragment Shader):计算一个像素的最终颜色。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
- Alpha测试和混合(Blending)阶段:检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。
顶点输入
开始绘制图形之前,我们需要先给OpenGL输入一些顶点数据。OpenGL仅当3D坐标在3个轴(x、y和z)上 -1.0到1.0 的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。
绘制一个三角形:
float vertices[] = { //三个顶点,z轴都设置为0,表示相同的深度depth-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f
};
OpenGL的坐标系为右手坐标系,即坐标x轴向右为正,y轴向上为正,z轴屏幕朝外为正,原点为为图像中心!
顶点缓冲对象VBO
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。通过 顶点缓冲对象(Vertex Buffer Objects, VBO) 管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批cpu的数据到显卡上,而不是每个顶点发送一次。
unsigned int VBO;
glGenBuffers(1, &VBO); //生成一个vbo对象//顶点数据的缓存类型是GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定//将顶点数据复制到缓冲内存中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
着色器Shader
着色器是由着色器语言GLSL(OpenGL Shading Language)编写的!它非常类似于cpp!
顶点着色器
一个最基本的顶点着色器:
#version 330 core //定义版本 并指定core-profile
layout (location = 0) in vec3 aPos; //指定location为顶点的第0个属性void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); //w设置默认为1
}
这里vec4的最后一个参数w,是用于透视除法(Perspective Division)!
为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position
变量,它是vec4类型!
如何编译着色器
const char *vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 aPos;\n""void main()\n""{\n"" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n""}\0";unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器//第二个参数:传递的源码字符串数量,这里只有一个
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //设置源码
glCompileShader(vertexShader); //编译
片段着色器
片段着色器所做的是计算像素最后的颜色输出。
一个最基本的片段着色器:
#version 330 core
out vec4 FragColor;void main()
{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); //默认为rgba,取决于帧缓冲
}
同样的可以像顶点着色器那样编译:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
着色器程序Shader Program
着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。
unsigned int shaderProgram;
shaderProgram = glCreateProgram(); //创建programglAttachShader(shaderProgram, vertexShader); //attach
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram); //linkglUseProgram(shaderProgram);//使用//link完成之后就可以删除了
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);//绘制
glDrawArrays(GL_TRIANGLES, 0, 3);
链接顶点属性
现在我们将顶点数据传到GPU中了,但是OpenGL还不知道怎么使用这些数据,我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。
顶点缓冲数据会被解析为下面这样子:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);//启用
这里配合上面的图,重点关注一下glVertexAttribPointer
函数:
- 第一个参数:对应的是要配置顶点的哪一个属性,在shader中写了
layout (location = 0)
,所以这里写0。 - 第二个参数:顶点属性的大小,这里是一个vec3,有3个值组成
- 第三个参数:数据类型,这里是GL_FLOAT
- 第四个参数:是否对数据进行归一化处理,这里已经是标准化后的数据了,不需要
- 第五个参数:步长(Stride),表示连续的顶点属性组之间的间隔,单位byte。
- 第六个参数:表示位置数据在缓冲中起始位置偏移量(Offset)指针。
顶点数组对象VAO
OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
顶点数组对象(Vertex Array Object, VAO) 可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。
unsigned int VAO;
glGenVertexArrays(1, &VAO); //创建VAOglBindVertexArray(VAO);//绑定
//绘制代码
这里mac中的OpenGL可能找不到这个函数,所以需要一个三方库来寻找具体的函数指针!
下载GLAD,去官网:https://glad.dav1d.de/
将语言(Language)设置为C/C++,在API选项中,选择3.3以上的OpenGL(gl)版本。之后将模式(Profile)设置为Core,并且保证选中了生成加载器(Generate a loader)选项。现在可以先(暂时)忽略扩展(Extensions)中的内容。都选择完之后,点击生成(Generate)按钮来生成库文件。
将下载好的头文件和glad.c添加到工程中即可!
至此,一个完整的三角形就可以被绘制出来了!
完整的代码如下:
/**********************Shader begin******************/const char *vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 aPos;\n""void main()\n""{\n"" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n""}\0";const char *fragmentShaderSource = "#version 330 core\n""out vec4 color;\n""void main()\n""{\n"" color = vec4(1.0f, 0.5f, 0.2f, 1.0);\n""}\0";unsigned int vertexShader;vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器//第二个参数:传递的源码字符串数量,这里只有一个glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); //设置源码glCompileShader(vertexShader); //编译int success;glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){int length;glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length);char* infoLog = (char *)malloc(length*sizeof(char *));glGetShaderInfoLog(vertexShader, sizeof(infoLog), NULL, infoLog);cout<<"compile vertex failed:"<<infoLog<<endl;}unsigned int fragmentShader;fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//创建着色器glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);unsigned int shaderProgram;shaderProgram = glCreateProgram(); //创建programglAttachShader(shaderProgram, vertexShader); //attachglAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram); //link//link完成之后就可以删除了glDeleteShader(vertexShader);glDeleteShader(fragmentShader);/**********************Shader end******************/float vertices[] = { //三个顶点,z轴都设置为0,表示相同的深度depth-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); //生成一个vbo对象//顶点数据的缓存类型是GL_ARRAY_BUFFERglBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定//将顶点数据复制到缓冲内存中glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//启用//解绑glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0); //渲染while (!glfwWindowShouldClose(window)){...glUseProgram(shaderProgram);//使用glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);//绘制三角形...}glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteProgram(shaderProgram);...
元素缓冲对象EBO
元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)!
举个例子,如果我们要绘制一个矩形,它由两个三角形组成,按照前边的例子,顶点数据需要6个,而实际矩形只需要4个顶点即可!EBO就可以解决这个问题!
float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {// 注意索引从0开始! // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,// 这样可以由下标代表顶点组合成矩形0, 1, 3, // 第一个三角形1, 2, 3 // 第二个三角形
};unsigned int EBO;
glGenBuffers(1, &EBO);//创建EBOglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//绑定
//将索引数据复制到缓冲内存中
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
最终绘制的时候,需要做一些修改:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
- 第一个参数:指定绘制模式
- 第二个参数:绘制顶点的个数,这里是6个
- 第三个参数:索引的类型
- 第四个参数:当使用EBO的时候,传入数据的offset,这里为0
推荐阅读
https://www.cnblogs.com/xiangqi/p/14608073.html
OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO相关推荐
- OpenGL入门二——变换
OpenGL入门二--变换 一.预备知识 二.实现 三.控制 四.预览与源码 一.预备知识 齐次坐标:https://blog.csdn.net/zhanxi1992/article/details/ ...
- openGL 入门 2--顶点数组对象 VAO 和 缓存对象 VBO
用户输入的数据 以 顶点数组对象表示 Vertex Array Object,VAO void glGenVertexArrays(GLsizei n, GLuint *arrays); 返回 n个 ...
- OpenGL ES VAO、VBO、EBO、FBO、PBO、TBO、UBO
面试中经常被问到的 OpenGL ES 对象,你知道的有哪些? 该原创文章首发于微信公众号:字节流动 VBO 和 EBO VBO(Vertex Buffer Object)是指顶点缓冲区对象,而 EB ...
- OPenGL 学习笔记之 VAO VBO EBO 以及SHADER 并使用其绘制三角形
译注 在学习此节之前,建议将这三个单词先记下来: 顶点数组对象:Vertex Array Object,VAO 顶点缓冲对象:Vertex Buffer Object,VBO 索引缓冲对象:Eleme ...
- Android OpenGL ES 学习(二) -- 图形渲染管线和GLSL
OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...
- 关于VAO,VBO和EBO的理解-OpenGL学习笔记
本文章首发于我的个人博客,希望大家多多支持! Hi! This is Showhoop Studio! 如果要从代码层面去理解渲染管线的工作,学习使用OpenGL编程可以说是一个不错的选择.这里我将记 ...
- OpenGL学习笔记(十三):将纹理贴图应用到四边形上,对VAO/VBO/EBO/纹理/着色器的使用方式进行总结
原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7919 ...
- opengl顶点数据传送和着色器处理(vao,vbo)
OpenGL学习脚印: 顶点数据传送和着色器处理1 写在前面 本节内容翻译和整理自<Learning Modern 3D Graphics Programming>Chapter1内容.作 ...
- Learn OpenGL(一)图形渲染管线(Pipeline)
在OpenGL中任何事物都在3D空间中, 但是屏幕和窗口是一个2D像素阵列, 所以OpenGL的大部分工作都是关于如何把3D坐标转变为适应你屏幕的2D像素. 3D坐标转为2D坐标的处理过程是由Open ...
- OpenGL入门学习[二] 绘制简单的几何图形
OpenGL入门学习[二] 本次课程所要讲的是绘制简单的几何图形,在实际绘制之前,让我们先熟悉一些概念. 一.点.直线和多边形 我们知道数学(具体的说,是几何学)中有点.直线和多边形的概念,但这些概念 ...
最新文章
- svn的merge使用例子
- JUC多线程:CountDownLatch、CyclicBarrier、Semaphore同步器原理总结
- 生成TFrecord
- Android开发(十八)——头部、中部、底部布局技巧
- loj#2541. 「PKUWC2018」猎人杀
- 项目管理工具project软件学习(八) - 关键路径查询、资源可用性
- Python破解百度翻js代码
- 实锤了!Chrome存在严重漏洞!
- pic单片机c语言 程序,PIC单片机C语言编程教程(1)
- GPS卫星定位基本原理
- RIA案例不完全参考[0.01 草案]
- c语言用字符编程图形,C语言图形编程——字母符号
- Android MVP架构搭建
- php wps 读取word内容,关于PHP导出WORD带图片
- 与班尼特·胡迪一起攻破浮空城 (HZNU-2264)
- Swift - 实现公历、农历日期之间的相互转换
- 云端服务器部署前端工程
- NAO学习第二周——NAOqi Audio
- 国内外计算机科学与技术发展前景,计算机科学与技术的现状及发展趋势
- 如何开发出成功的iOS应用(流程图)