此篇博文为个人OpenGL学习笔记,内容参考LearnOpenGL CNGLFW官方文档OpenGL 4 Reference Pages、OpenGL编程指南(原书第9版)

窗口创建好后在窗口内绘制一个简单的三角形。

OpenGL中,任何事物都在3D空间中,而屏幕和窗口确是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线管理的。渲染管线是一系列数据处理过程,并且将应用程序的数据转换到最终渲染的图像。图形渲染管线可以被划分为两个主要部分:第一部分把3D坐标转换为2D坐标,第二部分把2D坐标转变为实际的有颜色的像素。

2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素收到屏幕/窗口分辨率的限制。

图形渲染管线接受一组3D做坐标,然后把它们转变为屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的着色器(Shader),从而在图形渲染管线中快速处理数据。

有些着色器允许开发者自己配置,允许自己写着色器来替换默认着色器。这样可以更细致地控制图形渲染管线中的特定部分。OpenGL着色器用OpenGL着色器语言(GLSL)写成。

下图为图形渲染管线每个阶段的抽象展示,蓝色部分代表可以注入自定义着色器的部分:

图形渲染管线各个阶段

图形渲染管线包含很多部分,每个部分都将在转换顶点数据到最终像素这一过程中处理各自特定的阶段。

  1. 以数组的形式传递3个3D坐标作为图形渲染管线的输入用来表示一个三角形,数组叫做顶点数据,顶点数据是一系列顶点的集合。一个顶点是一个3D坐标的数据的集合,顶点数据是用顶点属性表示的,它可以包含任何数据。
  2. 顶点着色器,把一个单独的顶点作为输入,主要目的是把3D坐标转为另一种3D坐标。顶点着色器允许开发者对顶点属性进行一些基本处理。
  3. 图元装配,将顶点着色器输出的所有顶点作为输入并将所有点装配成指定图元的形状。
  4. 几何着色器,接收图元装配阶段的输出,把图元形式的一系列顶点的集合作为输入,可以通过产生新顶点构造出新的(或其它的)图元来生成其它形状。
  5. 光栅化阶段,接收几何着色器的输出把图元映射为最终屏幕上相应的像素,生成供片段着色器使用的片段。在片段着色器运行之前会执行裁切,丢弃超出视图以外的所有像素用来提升执行效率。
  6. 片段着色器,计算一个像素的最终颜色,这是所有OpenGL高级效果产生的阶段。通常片段着色器包含3D场景的数据(光照、阴影、光的颜色等),这些数据可以被用来计算最终像素的颜色。
  7. Alpha测试和混合,检测片段对应的深度和模板值用来判断像素与其它物体的前后关系并决定是否丢弃,检查alpha(透明度)值并对物体进行混合。

OpenGL中的一个片段是OpenGL渲染一个像素所需的所有数据。

图形渲染管线非常复杂并包含很多可配置的部分,对于大多数场合只需要配置顶点和片段着色器即可,几何着色器通常使用默认着色器。

现代OpenGL中,必须至少定义一个顶点着色器和一个片段着色器(因为GPU中没有默认的顶点着色器、片段着色器)。

三角形都有三个顶点,所以绘制三角形需要三个顶点的坐标,定义一个float数组通过3D位置指定三个顶点:

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

OpenGL中指定的所有坐标都是3D坐标(x、y和z),当且仅当3D坐标在三个轴上都在$[-1.0,1.0]$范围内即标准化设备坐标时才处理它们。

现在我们需要渲染出一个2D的三角形,所以将三个顶点的z坐标设置为0.0f,这样每个顶点的深度都是一样的,使三角形看上去像是2D的。

OpenGL内的z坐标与通常情况下的z坐标轴向不同,它代表像素所处的深度。

定义好顶点数据之后希望把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。这一步通过在GPU上创建内存来完成,在GPU上存储顶点数据、配置OpenGL如何解释内存并指定如何将数据发送到显卡。之后顶点着色器处理在内存中指定数量的顶点。

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

顶点缓冲对象有一个独一无二的ID,所以使用glGenBuffers()函数和一个缓冲ID生成一个顶点缓冲对象:

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

OpenGL有很多缓冲对象类型(具体查看下方“相关内容”),顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。使用glBindBuffer()函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:

glBindBuffer(GL_ARRAY_BUFFER, vbo);

现在我们使用的任何在GL_ARRAY_BUFFER目标上的缓冲调用都回来配置当前绑定的顶点缓冲对象,调用glBufferData()函数把之前定义的顶点数据复制到缓冲的内存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

顶点着色器允许指定任何以顶点属性为形式的输入。这使其具有很强的灵活性的同时还意味着必须手动指定输入数据的哪个部分对应顶点着色器的哪个顶点属性。

顶点缓冲数据会被解析为下图这样:

解析VBO

  • 位置数据被存储为32位(4字节)浮点值。
  • 每个位置由3个这样的值组成。
  • 每3个值之间没有空格(或其它值)。这些值在数组中紧密排列。
  • 第一个值在数据中处于缓冲起始位置。

有了这些信息我们可以使用glVertexAttribPointer()函数告诉OpenGL如何解释这些顶点数据(每个顶点属性)并启用顶点属性:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

现在已经把定点数据存储在显卡的内存中用顶点缓冲对象管理并告诉了OpenGL如何把定点数据链接到顶点着色器的顶点属性上。

现在用着色器语言GLSL(OpenGL Shading language)编写一个非常基础的顶点着色器然后编译这个着色器:

#version 460 core
layout (location = 0) in vec4 pos;

void main() {
    gl_Position = pos;
};

第一行#version 460 core指定了用的OpenGL着色语言版本(OpenGL 3.3以及更高的版本中GLSL版本号和OpenGL的版本是匹配的,例如GLSL 460版本对应于OpenGL 4.6版本)并表示使用OpenGL核心模式(新的应用程序应当采用的模式)。

每个着色器的第一行都应该设置#version,否则系统会假设使用“110”版本,但此版本与核心模式不兼容。

第二行layout (location = 0) in vec3 pos;分配了一个着色器变量,着色器变量是着色器与外部世界的联系所在(着色器并不知道数据从何而来,它只是在每次运行时直接获取数据对应的输入变量)。其中pos为变量名称,vec3为变量pos的数据类型,in指定了数据进入着色器的流向(所以还可以声明变量为out但不适用于此处),layout (location = 0)为布局限定符为变量提供元数据并设置pos的位置属性location为0。

vec4的默认值为(0.0, 0.0, 0.0, 1.0),因此仅指定部分值时OpenGL会用默认数值自动填充缺失的坐标值。

最后在着色器的main()函数中实现它的主体部分,对于这个着色器而言它所实现的就是将输入的顶点位置复制到顶点着色器的指定输出位置gl_Position中。

向量,简明地表达了任意空间中的位置和方向。在GLSL中一个向量最多有4个分量,每个分量值都代表空间中的一个坐标,它们可以通过vec.xvec.yvec.zvec.w来获取。vec.w分量并不是用作表达空间中的位置而是用在透视除法上。

这样就写出了一个最简单的顶点着色器,这个顶点着色器对数据没有做任何处理就传递到输出。通常情况下输入数据都不是标准化设备坐标,所以首先必须把它们转换至OpenGL可视区域内。

将顶点着色器源码储存在字符串中:

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

编译此顶点着色器源码,使用glCreateShader()创建一个顶点着色器对象(和顶点缓冲对象相同都是通过ID引用):

unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);

将顶点着色器源码附加到着色器对象上并进行编译:

glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
glCompileShader(vertex_shader);

由于现代OpenGL需要至少设置一个顶点着色器和一个片段着色器,所以还需创建一个片段着色器。

片段着色器计算像素最后的颜色输出,为了使其更简单这里的片段着色器将会一直输出橘黄色。

片段着色器的大部分代码和顶点着色器看起来很类似:

#version 460 core
layout (location = 0) out vec4 FragColor;

void main() {
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
};

片段着色器同样需要声明版本号、变量以及main()函数。

这里声明FragColor变量时使用了out限定符,着色器将会把FragColor对应的数值输出,这也是片段对应的颜色值。

片段着色器可以设置多个输出值,而某个变量所对应的输出结果就是通过location来设置的。虽然此片段着色器只用到一个输出值但还是有必要养成给所有的输出和输出变量都设置location的好习惯。

OpenGL使用了RGBA颜色空间,其中每个颜色分量的范围都是$[0,1]$。

同样将源码储存到字符串中:

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

编译片段着色器的过程与编译顶点着色器的过程类似,只不过使用GL_FRAGMENT_SHADER常量作为参数传递给glCreateShader()函数从而创建对应的片段着色器:

unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
glCompileShader(fragment_shader);

目前现代OpenGL图形渲染管线所必需的两个着色器都已编译,现在需要把顶点着色器和片段着色器对象链接到一个用来渲染的着色器程序中。

着色器程序对象是多个着色器合并之后最终链接完成的版本,渲染对象的时候激活着色器程序。

创建着色器程序对象:

unsigned int shader_program = glCreateProgram();

把之前编译的顶点着色器和片段着色器附加到程序对象上并链接它们:

glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);

着色器对象链接到程序对象之后删除着色器对象:

glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);

激活程序对象:

glUseProgram(shader_program);

现在已经把顶点数据发送给了GPU并指示了GPU如何在顶点着色器和片段着色器中处理他们,OpenGL也知道如何解释内存中的顶点数据和如何将顶点数据链接到顶点着色器的属性上。

在OpenGL中绘制一个物体流程如下所示:

  1. 创建顶点缓冲对象并将顶点数据复制到缓冲中
  2. 设置顶点属性指针
  3. 启用渲染物体所有的着色器程序
  4. 绘制

现在每绘制一个物体都必须重复这一过程,但如果有很多顶点属性或物体需要绘制的话绑定正确的缓冲对象、为每个物体配置所有的顶点属性就会成为一件麻烦事。

顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象一样被绑定,任何随后的顶点属性调用都会储存在这个顶点数组对象中。

OpenGL核心模式要求使用VAO,所以它直到该如何处理顶点输入。如果绑定VAO失败OpenGL会拒绝绘制任何东西。

顶点缓冲对象用来储存绘制顶点所需要的信息而顶点数组对象用来储存顶点缓冲对象。

VAO与VBO

创建顶点数组对象和创建顶点缓冲对象很类似:

unsigned int vao;
glGenVertexArrays(1, &vao);

绑定顶点数组对象:

glBindVertexArray(vao);

现在就可以在渲染循环中使用当前激活的着色器、之前定义的顶点属性配置和绘制三角形:

glDrawArrays(GL_TRIANGLES, 0, 3);

在程序退出之前删除顶点缓冲对象和顶点数组对象:

glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);

完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate(); 
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;  
    }
    float vertices[] = {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f,
        0.0f, 0.5f, 0.0f
    };
    unsigned int vbo, vao;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    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(float), (void*)0);
    glEnableVertexAttribArray(0);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vao);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

可以绘制三角形之后来尝试绘制一个矩形(由于OpenGL主要处理三角形所以矩形由两个三角形组成)。两个三角形的顶点:

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,
    -0.5f, -0.5f, 0.0f,
    -0.5f, 0.5f, 0.0f
};

其中前三行为第一个三角形的三个顶点,后三行为第二个三角形的三个顶点。显然两个三角形的两个顶点重合了,导致矩形的4个顶点需要使用6个顶点来表示,当我们绘制更加复杂的图形时这样的资源浪费情况会更加严重。索引缓冲对象(Element Buffer Object, EBO, 也叫Index Buffer Object, IBO)专门存储索引,OpenGL调用这些顶点的索引决定该绘制哪个顶点。

定义矩形的四个顶点和绘制矩形所需要的索引:

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, 1, 3,
    1, 2, 3
};

其中indices数组每个数字都由索引指向vertices数组的一个顶点而两行中的每一行都由三个顶点组成一个三角形。

创建索引缓冲对象:

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

索引缓冲对象的建立方式和顶点缓冲对象很相似。同样将索引缓冲对象绑定到GL_ELEMENT_ARRAY_BUFFER上并把数据复制到缓存中:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。

VAO、VBO、EBO

最后使用glDrawElements()函数替换glDrawArrays()函数来通过索引缓冲对象绘制图形:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);!

完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate(); 
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;  
    }
    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, 1, 3,
        1, 2, 3
    };
    unsigned int vbo, vao, ebo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glGenBuffers(1, &ebo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vao);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

调用glPolygonMode()函数还可以设置图元的显示模式。

例如将图形以线框模式显示完整代码(可以使用glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)设置回默认模式):

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate(); 
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;  
    }
    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, 1, 3,
        1, 2, 3
    };
    unsigned int vbo, vao, ebo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glGenBuffers(1, &ebo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    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(shader_program);
        glBindVertexArray(vao);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

对两个相同的三角形使用不同的顶点缓冲对象和顶点数组对象完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

const char* fragment_shader_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate(); 
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;  
    }
    float first_trianghle[] = {
        -0.9f, -0.5f, 0.0f,
        -0.0f, -0.5f, 0.0f,
        -0.45f, 0.5f, 0.0f,
    };
    float second_trianghle[] = {
        0.0f, -0.5f, 0.0f,
        0.9f, -0.5f, 0.0f,
        0.45f, 0.5f, 0.0f
    };
    unsigned int vbos[2], vaos[2];
    glGenVertexArrays(2, vaos);
    glGenBuffers(2, vbos);
    glBindVertexArray(vaos[0]);
    glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(first_trianghle), first_trianghle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(vaos[1]);
    glBindBuffer(GL_ARRAY_BUFFER, vbos[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(second_trianghle), second_trianghle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(0);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
    glCompileShader(fragment_shader);
    unsigned int shader_program = glCreateProgram();
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);
    glLinkProgram(shader_program);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program);
        glBindVertexArray(vaos[0]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glBindVertexArray(vaos[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(2, vaos);
    glDeleteBuffers(2, vbos);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

使用两个不同的着色器程序对上面两个相同的三角形绘制不同的颜色完整代码:

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

void ProcessInput(GLFWwindow* window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

const unsigned int window_width = 800;
const unsigned int window_height = 600;

const char* vertex_shader_source = "#version 460 core\n"
    "layout (location = 0) in vec4 pos;\n"
    "\n"
    "void main() {\n"
    "    gl_Position = pos;\n"
    "}\n";

const char* fragment_shader_orange_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
    "}\n";

const char* fragment_shader_yellow_source = "#version 460 core\n"
    "layout (location = 0) out vec4 FragColor;\n"
    "\n"
    "void main() {\n"
    "    FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n"
    "}\n";

int main() {
    if (!glfwInit()) {
        std::cout << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Hello OpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate(); 
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, FramebufferSizeCallback);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;  
    }
    float first_trianghle[] = {
        -0.9f, -0.5f, 0.0f,
        -0.0f, -0.5f, 0.0f,
        -0.45f, 0.5f, 0.0f,
    };
    float second_trianghle[] = {
        0.0f, -0.5f, 0.0f,
        0.9f, -0.5f, 0.0f,
        0.45f, 0.5f, 0.0f
    };
    unsigned int vbos[2], vaos[2];
    glGenVertexArrays(2, vaos);
    glGenBuffers(2, vbos);
    glBindVertexArray(vaos[0]);
    glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(first_trianghle), first_trianghle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(vaos[1]);
    glBindBuffer(GL_ARRAY_BUFFER, vbos[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(second_trianghle), second_trianghle, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glEnableVertexAttribArray(0);
    unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
    glCompileShader(vertex_shader);
    unsigned int fragment_shader_orange = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader_orange, 1, &fragment_shader_orange_source, NULL);
    glCompileShader(fragment_shader_orange);
    unsigned int shader_program_orange = glCreateProgram();
    glAttachShader(shader_program_orange, vertex_shader);
    glAttachShader(shader_program_orange, fragment_shader_orange);
    glLinkProgram(shader_program_orange);
    unsigned int fragment_shader_yellow = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader_yellow, 1, &fragment_shader_yellow_source, NULL);
    glCompileShader(fragment_shader_yellow);
    unsigned int shader_program_yellow = glCreateProgram();
    glAttachShader(shader_program_yellow, vertex_shader);
    glAttachShader(shader_program_yellow, fragment_shader_yellow);
    glLinkProgram(shader_program_yellow);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader_orange);
    glDeleteShader(fragment_shader_yellow);
    while (!glfwWindowShouldClose(window)) {
        ProcessInput(window);
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader_program_orange);
        glBindVertexArray(vaos[0]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glUseProgram(shader_program_yellow);
        glBindVertexArray(vaos[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(2, vaos);
    glDeleteBuffers(2, vbos);
    glfwTerminate();
    return 0;
}

运行效果:

运行效果

Last modification:February 24th, 2020 at 09:28 pm
如果觉得我的文章对你有用,请随意赞赏