文章目录

  • 基础知识
  • 顶点输入
  • 创建顶点着色器
  • 片段着色器
  • 链接顶点属性和VAO顶点数组对象
  • 元素缓冲对象EBO
  • 完整程序

基础知识

在学习图形渲染前,首先了解3个词汇:

  1. 顶点数组对象:Vertex Array Object,VAO
  2. 顶点缓冲对象:Vertex Buffer Object,VBO
  3. 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO

另外,在图形渲染中,要记住2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。

3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线(Graphics Pipeline,大多译为管线,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的。图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

在GPU上并行处理图形渲染管线的小程序叫做着色器(Shader)。OpenGL着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的。


为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP(这几个指令在前面几节已经用过了)。

从上面的图形渲染过程图可以看出,前3步是坐标处理,后3步是像素处理,详细过程如下:

  1. 顶点数据进入顶点着色器,可以设置顶点属性
  2. 所有顶点进入图元装配阶段,形成几何图形,上例是一个三角形
  3. 图形生成后进入几何着色器,这时可以添加新的顶点,例如添加一个新顶点形成2个三角形
  4. 几何处理完成后,进入光栅化阶段,会将图元转变为屏幕上真实显示的像素,形成片段,并且会丢弃掉视图之外的元素
  5. 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方
  6. 片段着色器确定好所有元素的颜色值后,进入测试混合阶段,主要会检测元素的深度值等信息

顶点输入

开始绘制图形之前,我们需要先给OpenGL输入一些顶点数据。OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标

标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。而屏幕显示坐标是以屏幕左上角为原点,x右为正,y下为正。


三角形的定点定义如下:

float vertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f,  0.5f, 0.0f
};

定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器

我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。

我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:

unsigned int VBO;
glGenBuffers(1, &VBO);

OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);  绑定顶点缓冲对象

然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);   //将数据绑定到缓冲

创建顶点着色器

第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器,这样我们就可以在程序中使用它了。

一个非常基础的GLSL顶点着色器的源代码:

#version 330 core
layout (location = 0) in vec3 aPos;void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

为了设置顶点着色器的输出,我们必须把位置数据赋值给预定义的gl_Position变量,它在幕后是vec4类型的。

然后需要将顶点着色器的源代码硬编码在代码文件顶部的C风格字符串中:

// 定义顶点着色器
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";

然后要创建一个顶点着色器对象:

 // vertex shader 创建并编译顶点着色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// check for shader compile errors 检测是否编译成功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;}

片段着色器

片段着色器所做的是计算像素最后的颜色输出。为了让事情更简单,我们的片段着色器将会一直输出橘黄色。

在计算机图形中颜色被表示为有4个元素的数组:红色、绿色、蓝色和alpha(透明度)分量,通常缩写为RGBA。当在OpenGL或GLSL中定义一个颜色的时候,我们把颜色每个分量的强度设置在0.0到1.0之间。比如说我们设置红为1.0f,绿为1.0f,我们会得到两个颜色的混合色,即黄色。

定义片段着色器:

// 定义片段着色器
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";

创建片段着色器对象:

 // fragment shader 创建并编译片段着色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// check for shader compile 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)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序,所以需要创建一个着色器程序对象。它是多个着色器合并之后并最终链接完成的版本。

着色器程序对象代码如下:

 // link shaders 创建并链接着色器程序对象unsigned int 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);    //删除片段着色器

链接顶点属性和VAO顶点数组对象

我们必须告诉OpenGL如何去解析顶点数据,我们使用一个顶点缓冲对象将顶点数据初始化至缓冲中,建立了一个顶点和一个片段着色器,并告诉了OpenGL如何把顶点数据链接到顶点着色器的顶点属性上。在OpenGL中绘制一个物体,代码会像是这样:

// 0. 复制顶点数组到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 当我们渲染一个物体时要使用着色器程序
glUseProgram(shaderProgram);
// 3. 绘制物体
someOpenGLFunctionThatDrawsOurTriangle();

但绘制每一个物体的时候都必须重复这一过程,如果成百上千个物体的话就不容易了,因此需要使用VAO。

顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中(OpenGL核心模式要求使用VAO)。

元素缓冲对象EBO

EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。这种所谓的索引绘制(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:

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);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

然后从索引缓冲区渲染三角形:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

另外,还可以设置绘制时的模式,如线框模式或常规模式:

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);   //线框模式GL_LINE / 填充模式GL_FILL

最终渲染图形如下:


完整程序

main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>#include <iostream>void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;// 定义顶点着色器
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 FragColor;\n""void main()\n""{\n""   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""}\n\0";int main()
{// glfw: initialize and configure 初始化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);
#endif// glfw window creation 窗口创建// --------------------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: load all OpenGL function pointers 初始化glad// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------// vertex shader 创建并编译顶点着色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// check for shader compile errors 检测是否编译成功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 创建并编译片段着色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// check for shader compile 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 创建并链接着色器程序对象unsigned int 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 configure vertex attributes 定义顶点数组(顶点输入)// ------------------------------------------------------------------float vertices[] = {0.5f,  0.5f, 0.0f,  // top right0.5f, -0.5f, 0.0f,  // bottom right-0.5f, -0.5f, 0.0f,  // bottom left-0.5f,  0.5f, 0.0f   // top left };unsigned int indices[] = {  // note that we start from 0! 数组索引从0开始0, 1, 3,  // first Triangle1, 2, 3   // second Triangle};// 创建VAO、VBO和EBOunsigned int VBO, VAO, EBO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s). 绑定缓冲glBindVertexArray(VAO);   //绑定顶点数组对象glBindBuffer(GL_ARRAY_BUFFER, VBO);   //绑定顶点缓冲对象  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);  //将数据绑定到缓冲glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);   //绑定EBO元素缓冲对象glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);  //链接顶点属性(设置顶点属性指针)glEnableVertexAttribArray(0);   //启用顶点属性// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbindglBindBuffer(GL_ARRAY_BUFFER, 0);// remember: do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// render loop 循环渲染// -----------while (!glfwWindowShouldClose(window)){// input 接受输入// -----processInput(window);// render 渲染// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// draw our first triangle 画出两个三角形glUseProgram(shaderProgram); //使用着色器程序glBindVertexArray(VAO); // 绑定VAO seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized//glDrawArrays(GL_TRIANGLES, 0, 6);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);   //渲染元素glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);    //线框模式GL_LINE / 填充模式GL_FILL// glBindVertexArray(0); // no need to unbind it every time // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose: 资源释放// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteProgram(shaderProgram);// glfw: terminate, clearing all previously allocated GLFW resources. 结束窗口// ------------------------------------------------------------------glfwTerminate();return 0;
}// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true);
}// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{// make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height);
}

以上。

【OpenGL】GLFW创建三角形相关推荐

  1. 【OpenGL】绘制三角形

    [OpenGL]绘制三角形 效果展示 准备条件 图形渲染管线的各个阶段概览 创建着色器程序与绘制OpenGL图元 参考资料 效果展示 准备条件 首先已经通过[OpenGL]使用OpenGL创建窗口使用 ...

  2. OpenGL GLFW

    OpenGL GLFW OpenGL GLFW简介 源代码剖析 主要源代码 OpenGL GLFW简介 在第一课中,我们发现OpenGL是纯粹的图形API-它不包含用于创建窗口的函数:第三方API(G ...

  3. OpenGL绘制Triangle三角形

    OpenGL绘制Triangle三角形 前期知识准备 顶点输入 顶点着色器 编译着色器 片段着色器 着色器程序 链接顶点属性 顶点数组对象 我们一直期待的三角形 索引缓冲对象 前期知识准备 在Open ...

  4. OPenGL笔记--创建三维图形(立方体)

    文章目录 一.前置知识 二.运行效果 三.具体代码 一.前置知识 通过之前的学习,我们已经知道了如何创建多边形,立方体实际就是多边形围起来的三维图形,例如: 椎体是4个三角形围起来的三维图形: 正方体 ...

  5. OPenGL笔记--创建一个3D场景

    文章目录 一.前言 二.效果展示 三.详细流程 3.1.World.txt文件规则 3.2.加载World.txt 3.3.绘制场景 3.4.交互 四.详细代码 五.举一反三 一.前言 通过前面的学习 ...

  6. 利用GLFW创建一个窗口

    利用GLFW创建一个窗口 创建窗口前的准备工作 GLFW初始化 设置界面属性 界面相关属性 缓冲区相关属性 上下文相关属性 各个属性的默认值和取值范围 创建窗口 显示窗口 完整代码 窗口事件交互 完整 ...

  7. ITK:创建三角形四边形网格

    ITK:创建三角形四边形网格 内容提要 输出结果 C++实现代码 内容提要 使用itk :: QuadEdgeMes​​h创建三角形曲面网格 输出结果 C++实现代码 #include "i ...

  8. OpenGL 绘制彩色三角形的实例

    OpenGL 绘制彩色三角形 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <sb7.h> #define INTERPOLATE_ ...

  9. OpenGL绘制一个三角形

    OpenGL绘制一个三角形 OpenGL绘制一个三角形简介 源代码剖析 主要源代码 OpenGL绘制一个三角形简介 在本课中,我们仍然不转换坐标,因此顶点仅在正方形内可见.从 Z 的反轴看,正方形如下 ...

最新文章

  1. CentOS 6.x 播放 mp3 音乐 —— 成功
  2. android (java) 网络发送get/post请求参数设置
  3. 转载:如何开发高性能低成本的网站之技术选择
  4. java zk监听异步_zk事件的监听和处理2
  5. numpy教程:ndarray属性、内建函数及其运算、迭代
  6. java卡 apdu_java智能卡APDU学习笔记
  7. SOLIDWORKS教程:solidworks常用技巧大全
  8. 虚拟机自省XenAccess和vmitools
  9. 隐私空间伪装计算机,隐私空间app(文件夹隐藏) 6.1.9 免root
  10. 【codevs 4246】奶牛的身高 差分约束
  11. [收藏]经典爱情十句话
  12. 地图数据赋能ADAS的探索与实践
  13. SIGCHLD信号(重点)
  14. XILINX Ultrascale/Ultrascale+ 高速收发器时钟MGTHREFCLK原语调用
  15. 盘点2020年wordpress常用的50个插件合集
  16. 2022-2028全球对叔辛基苯酚行业调研及趋势分析报告
  17. [Android] Nexus 7 二代连接 Windows 7
  18. 金蝶K3仓存管理模块术语表
  19. 【哈士奇赠书活动 - 25期】-〖Python自动化办公应用大全(ChatGPT版) 〗
  20. V2X定位-GNSS系列定位方法小结

热门文章

  1. 水平排列元素的底部对齐
  2. 看完这篇学不会 Vim 定位操作我跟你姓!
  3. windows查看/停止正在运行端口号的命令
  4. BLA、CRA、IDR 解释
  5. 宝石与石头(c++)
  6. if x 和 if x is not None的区别(python)
  7. 【SCOI2007】修车
  8. maven 命令指定配置文件打包springboot项目
  9. 古筝考级与教学(周望)
  10. 阿里巴巴 史上最强《Java 开发手册》泰山版 全本开放下载!