【OpenGL】绘制三角形

  • 效果展示
  • 准备条件
  • 图形渲染管线的各个阶段概览
  • 创建着色器程序与绘制OpenGL图元
  • 参考资料

效果展示

准备条件

首先已经通过【OpenGL】使用OpenGL创建窗口使用OpenGL创建出了窗口并且启动了渲染循环

图形渲染管线的各个阶段概览

  1. 蓝色部分代表的是我们可以注入自定义的着色器的部分。

创建着色器程序与绘制OpenGL图元

/*
* 因为OpenGL只是一个标准/规范,具体的实现是由驱动开发商针对特定显卡实现的。
* 由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要运行时查询。
* 所以任务就落在了开发者身上,开发者需要在运行时获取函数地址并将其保存在一个函数指针中,供以后使用。
*
* glad是一个开源的库,它能解决我们上面提到的获取函数地址并将其保存在一个函数指针中供以后使用繁琐的问题
*/
#include <glad/glad.h>
/*
* GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户创造OpenGL上下文,定义窗口参数以及处理用户输入
*/
#include <GLFW/glfw3.h>
/*
* C++标准库
*/
#include <iostream>// 处理所有输入: 查询 GLFW 是否在此框架内按下/释放相关键,并做出相应反应
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// glfw: 每当窗口大小改变(由操作系统或用户调整大小)时,这个回调函数就会执行
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// 确保视口与新窗口尺寸匹配;请注意,宽度和高度将明显大于视网膜显示器上指定的宽度和高度// 此方法提供的数据进行视口变换:标准化设备坐标会变成屏幕空间坐标glViewport(0, 0, width, height);
}const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// 顶点着色器(Vertex Shader)是几个可编程着色器中的一个。如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器。/*
* 向量(Vector)
* 在图形编程中我们经常会使用向量这个数学概念,因为它简明地表达了任意空间中的位置和方向,并且它有非常有用的数学属性。
*/// 第一件事:使用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器
// 将顶点着色器的源代码硬编码在代碼文件顶部的C风格字符串中
const char* vertexShaderSource = "#version 330 core\n"     // 版本声明,OpenGL3.3
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";// 将片段着色器的源代码硬编码在代碼文件顶部的C风格字符串中
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";/*
* 顶点数组对象:Vertex Array Object,VAO
* 顶点缓冲对象:Vertex Buffer Object,VBO
* 索引缓冲对象:Element Buffer Object,EBO 或Index Buffer Object,IBO
*//*
* OpenGL任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。
* 这个处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途径一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。
* 图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转化为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
*//*
* 2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制
*/int main()
{// glfw: 初始化和配置// ------------------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // 设置主版本号glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // 设置次版本号glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用的是核心模式#ifdef __APPLE__glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // MacOXS系统才需要设置
#endif// glfw 创建窗口对象// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: 加载所有OpenGL函数指针// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// 构建并编译我们的着色器程序// ------------------------------------// 顶点着色器vertex shader// 如果我们打算做渲染的话,现代OpenGL需要我们至少设置一个顶点和一个片段着色器// 创建一个着色器对象(GL_VERTEX_SHADER类型表示顶点着色器)unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);// 把这个着色器源码附加到着色器对象上glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);// 然后进行编译glCompileShader(vertexShader);// 检查着色器编译错误int success;char 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// 片段着色器所作的是计算像素最后的颜色输出。为了让事情更简单,我们的片段着色器将会一直输出橘黄色。/** 在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,通常缩写为RGBA。*/// 创建片段着色器(GL_FRAGMENT_SHADER类型表示片段着色器)unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);// 把片段着色器源码附加到着色器对象上glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);// 然后进行编译glCompileShader(fragmentShader);// 检查着色器编译错误glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); // 获取错误消息std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// 顶点着色器与片段着色器都编译完成后,剩下的事情是把两个着色器对象链接到一个用来渲染的着色器程序// 链接着色器// 创建着色器程序unsigned int shaderProgram = glCreateProgram();// 把之前编译的着色器附加到程序对象上glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);// glLinkProgram链接它们glLinkProgram(shaderProgram);// 检查链接错误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);// 设置顶点数据(和缓冲区)并配置顶点属性// ------------------------------------------------------------------// 指定3个顶点,每个顶点都有一个3D位置。将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组// 这个顶点数据,会作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于存储我们的顶点数据,还要配置OpenGL如何解释这些内存,// 并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点float vertices[] = {-0.5f, -0.5f, 0.0f, // left  由于是2D的,所以z坐标设置为0.0(通常深度可以理解为z坐标,它代表一个像素在空间中和你的距离,如果离你远就可能被别的像素遮挡,你就看不到它了,它会被丢弃,以节省资源)0.5f, -0.5f, 0.0f, // right 0.0f,  0.5f, 0.0f  // top   };// 顶点缓冲对象与顶点数组对象unsigned int VBO, VAO;// 顶点数组对象glGenVertexArrays(1, &VAO);// glGenBuffers+缓冲ID生成一个顶点缓冲对象,即VBO对象:VBO就是用来管理顶点着色器创建的内存,它会在GPU内存(通常被称为显存)中储存大量顶点。// 使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。// 当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这个是非常快的过程。glGenBuffers(1, &VBO); // 首先绑定顶点数组对象,然后绑定并设置顶点缓冲区,然后配置顶点属性glBindVertexArray(VAO);// OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。// OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上。glBindBuffer(GL_ARRAY_BUFFER, VBO);// 该函数会把之前定义的顶点数据复制到缓冲的内存中;glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。// 第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。// 第二个参数指定传输数据的大小(以字节为单位)// 第三个参数就是希望我们发送的数据/**  第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:*  GL_STATIC_DRAW :数据不会或几乎不会改变。*  GL_DYNAMIC_DRAW:数据会被改变很多。      第二种和第三种就能确保显卡把数据放在能够高速写入的内存部分*  GL_STREAM_DRAW :数据每次绘制时都会改变。*/glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 现在为止,我们已经把顶点数据存储在显卡的内存中,用VBO这个顶点缓冲对象管理//--------------------------------------------------------// 使用此函数告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)/** 第一个参数指定我们要配置的顶点属性:0表示我们希望把数据传递到这一个顶点属性中* 第二个参数指定顶点属性的大小:vec3,它由三个值组成,所以大小是3* 第三个参数指定数据的类型:浮点型* 第四个参数表示是否希望数据被标准化:GL_TRUE表示所有数据都会被映射到0(有符号是-1)到1之间* 第五个参数叫步长:表示连续的顶点属性组之间的间隔。* 最后一个参数表示位置数据在缓冲中起始位置的偏移量。*/glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);// 使用此函数,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的glEnableVertexAttribArray(0);// 请注意,这是允许的,对glVertexAttribPointer的调用将VBO注册为顶点属性的绑定顶点缓冲区对象,以便以后可以安全地解除绑定glBindBuffer(GL_ARRAY_BUFFER, 0);// 之后可以解除VAO的绑定,这样其它VAO调用就不会意外地修改此VAO,但这种情况很少发生。无论如何,修改其它VAO// 都需要调用glBindVertexArray,这样我们通常不会在不直接需要的情况下接触VAO(或VBO)的绑定。glBindVertexArray(0);// 取消注释此调用以绘制线框多边形//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 渲染循环// -----------while (!glfwWindowShouldClose(window)){// 输入// -----processInput(window);// 渲染指令// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清空屏幕所用的颜色(此函数是一个状态设置函数)glClear(GL_COLOR_BUFFER_BIT);// 清除颜色缓冲(是一个状态使用函数)// 绘制我们的首个三角形// 调用glUseProgram函数,用刚创建的程序对象作为它的参数,以激活这个程序对象glUseProgram(shaderProgram);// 顶点数组对象可以像顶点缓冲对象(VBO)那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。// 这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。/** OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。*//** 一个顶点数组对象会存储以下内容:* 1.glEnableVertexAttribArray和glDisableVertexAttribArray的调用* 2.通过glVertexAttribPointer设置的顶点属性配置* 3.通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象(VBO)*/glBindVertexArray(VAO); // 因为我们只有一个VAO,所以没有必要每次都绑定它,但我们会这样做使事情更有条理// 它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元/** 第一个参数是打算绘制的OpenGL图元的类型* 第二个参数是制定了顶点数组的起始索引* 第三个参数是我们打算绘制多少个顶点*/glDrawArrays(GL_TRIANGLES, 0, 5);// glBindVertexArray(0); // no need to unbind it every time // glfw: 交换缓冲区和轮询IO事件(按键按下/释放、鼠标移动等)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// 可选: 一旦自选超出其用途,则取消分配// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteProgram(shaderProgram);// glfw: 终止, 清除之前分配的所有GLFW资源.// ------------------------------------------------------------------glfwTerminate();return 0;
}

参考资料

【1】LearnOpenGL

【OpenGL】绘制三角形相关推荐

  1. C++Opengl绘制三角形源码

    C++Opengl绘制三角形源码 项目开发环境 项目功能 项目演示 项目源码传送门 项目开发环境 开发语言:C++和IDE:VS2017,操作系统Windows版本windows SDK8.1,三方库 ...

  2. OpenGL绘制三角形

    OpenGL绘制三角形 1. 可编程渲染管线 2. 标准化设备坐标(Normalized Device Coordinates) 3. 三角形顶点数据输入 4. 顶点着色器(Vertex Shader ...

  3. 音视频之opengl绘制三角形

    音视频之opengl绘制三角形 音视频之opengl渲染图片 音视频之渲染yuv图片 2018年用了一年业余时间学习了音视频,直播,解码,编码,倍速,跳转,滤镜,倒放等.,慢慢把这部分内容写到博客上, ...

  4. 【OpenGL】十五、OpenGL 绘制三角形 ( 绘制 GL_TRIANGLE_FAN 三角形扇 )

    文章目录 一.绘制 GL_TRIANGLE_FAN 三角形 1.绘制 3 个点的情况 2.绘制 4 个点的情况 3.绘制 5 个点的情况 4.绘制 6 个点的情况 二.相关资源 一.绘制 GL_TRI ...

  5. 【OpenGL】十四、OpenGL 绘制三角形 ( 绘制 GL_TRIANGLE_STRIP 三角形 | GL_TRIANGLE_STRIP 三角形绘制分析 )

    文章目录 一.绘制 GL_TRIANGLE_STRIP 三角形 二.GL_TRIANGLE_STRIP 三角形绘制分析 三.相关资源 一.绘制 GL_TRIANGLE_STRIP 三角形 该模式绘制首 ...

  6. 【OpenGL】十三、OpenGL 绘制三角形 ( 绘制单个三角形 | 三角形绘制顺序 | 绘制多个三角形 )

    文章目录 一.绘制三角形 二.三角形绘制顺序 1.绘制正面 2.三个点逆时针方向排列 3.三个点顺时针方向排列 4.设置点的正面方向 三.绘制多个三角形 四.相关资源 一.绘制三角形 三角形绘制即绘制 ...

  7. C++ opengl 绘制三角形带

    程序运行截图如下: 程序源码如下: ggl.h #pragma once #include <windows.h> #include <gl/GL.h> #include &l ...

  8. C++ opengl 绘制三角形扇

    程序运行截图如下: 伪代码如下: void Draw() {glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //擦除背景使用的颜色(分别表示颜色分量的值)glClear(G ...

  9. Qt|C++-OpenGL绘制三角形带

    程序运行截图如下: MyFristMSVCQt.h #pragma once#include <QtWidgets/QWidget> #include "ui_MyFristMS ...

  10. OpenGL绘制五颜六色的三角形并旋转

    OpenGL绘制三角形并旋转 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/gl.h> #define GLFW_INC ...

最新文章

  1. 使用C语言来实现模块化
  2. 国家开放大学2021春1378管理英语3题目
  3. Windows与Linux下tftp服务的使用
  4. 大物实验总结模板_期中总结大会amp;期末动员大会
  5. Python之数据分析(Numpy中的除法和取余、三角函数、ufunc对象的位运算)
  6. UITableViewCell点击不能push解决方法
  7. SAP License:电子行业ERP实施
  8. window下用主机名登录MySQL数据库出现报错解决方案
  9. Matlab Tricks(十八)—— 矩阵间元素距离的计算
  10. 0基础学python做什么工作好-转行零基础该如何学习python?很庆幸,三年前的我选对了...
  11. 基于公网smtp协议实现邮件服务器
  12. RLC电阻电容电感基础知识——电阻篇
  13. Apple Watch简述
  14. 一个简单的推荐系统实现
  15. 经典点云去噪算法总结
  16. 2022引流新玩法,异业联盟打造商业闭环
  17. 解决memoryerror
  18. iOS 通过github自动打包ipa
  19. node.js和npm下载及安装详细教程
  20. 松弛(SOR)迭代法

热门文章

  1. 海外开发者推荐:10个顶级2D游戏资源站
  2. c语言exp2是什么函数,C ++ STL中的exp2()函数
  3. Spring中Bean定义的注册流程
  4. 清远市城市品牌及五大百亿农业产业区域公用品牌亮相
  5. Java的垃圾回收机制、年轻代与老年代
  6. 刘涛入职阿里,年薪超过欧阳娜娜。一起来看阿里明星员工和职级薪资
  7. 深部位移监测系统wk813应用边坡、大坝、堤防、铁路和建筑基坑开挖等深部位移测量
  8. MYSQL差异备份、恢复与多表查询
  9. Mac上 vue-cli使用命令创建项目
  10. 误删win10应用商店 修复方法