【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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一二三o-0-O

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值