opengl的大致流程

头文件

opengl一般用到的头文件:

1
2
3
4
5
6
7
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>

其中,glm 为转为opengl设计的矩阵运算库, glad 用于加载函数指针, GLFW 用于获得窗口。

头文件的引用的先后顺序是很讲究的,`glad` 必须 先于 `glfw` 导入!

工程上不使用 using namespace std; 命名空间,输出日志使用 std::cout

创建窗口

需要头文件 glfw3.h

1
#include <GLFW/glfw3.h>
  1. 初始化 glfw

    glfwInit():初始化,返回0表示正常

  2. 创建窗口

    glfwCreateWindow():创建窗口,接收窗口的长和宽,返回指向窗口对象的 指针

    1
    
    GLFWwindow *window = glfwCreateWindow(1280, 720, "GED", nullptr, nullptr);
    
  3. 设置窗口位置

    glfwSetWindowPos():设置窗口出现在电脑屏幕的何处,视具体电脑而异。

    1
    
    glfwSetWindowPos(window, 1000, 150)
    

这一步最重要的是获得窗口对象的指针。之后要设置和操作这个窗口,就要传入指针。

初始化OPENGL

OPENGL的驱动一般包含在各个显卡的驱动中,不用额外下载。除非你电脑刚买或者完全不联网不玩游戏

需要一个库来获得OPENGL的函数指针,常用glad。

包含头文件 glad.h

1
#include <glad/glad.h>
  1. 确定opengl版本

    这一步是对整个glfw操作,所以要在初始化glfw之后,创建window之前

    glfwWindowHint:多重载函数,接收不同的命令设置不同的参数。

    1
    2
    3
    
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  // opengl大版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);  // opengl小版本
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    
  2. 与window关联

    glfwMakeContextCurrent:将设置的OPENGL信息与window关联。

    1
    
    glfwMakeContextCurrent(window);
    
  3. 使用glad加载函数指针

    gladLoadGLLoader():返回值应为0。

    1
    
    gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)
    

颜色

OPENGL有四个颜色通道:

  • Red:红
  • Green:绿
  • Blue:蓝
  • Alpha:透明度

传值可以用RGB,取值范围: $[0,255]$ ;也可以是将其归一化后的浮点数,取整范围 $[0.0,1.0]$

旧版本一些函数不支持整数类型,因此必须使用归一化后的浮点数。可自定义归一化函数,方便传入RGB常规色值。

  1. 设置window背景颜色

    glClearColor():接收red、green、blue、alpha四个值。

    1
    
    glClearColor(1.0f, 0.5f, 0.7f, 0.2f);
    
  2. 让设置生效

    glClear():从颜色缓存里加载。

    1
    
    glClear(GL_COLOR_BUFFER_BIT);
    

上面的设置背景实际上不会生效,因为opengl使用双缓冲方案。

交换双缓冲

opengl使用双缓冲方案,在每生成下一帧时实际操作的是后缓冲区,要把后缓冲区交换成当前缓冲区。

交换缓冲的函数:

1
glfwSwapBuffers(window);

顶点

把所有顶点的三维坐标放在一个数组里。

1
2
3
4
5
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};
上面的每个顶点只包含了 xyz的值,实际上可以是 “空间坐标+纹理坐标”,“空间坐标加法线”等。

数组是一维的,每个坐标都在 $[-1.0, 1.0]$。

VBO & VAO

一个常见的说法来理解 VBO/VAO是:VBO是顶点数据的快递箱,负责打包数据,运送至显存;VAO则是附在上面的说明书,告诉显卡如何解包这些顶点。

VBO & VAO 都是opengl对象,它们构建了cpu与gpu沟通的桥梁。
  1. 申请 VBO 和 VAO :
1
2
3
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
  1. 绑定 VAO

    VAO 和 VBO 应当是捆绑在一起的。opengl是一个状态机,当我们执行某一条命令后,之后的操作默认与之绑定,直到解绑为止。

1
glBindVertexArray(VAO);  //绑定开始

执行这条命令后,后面的操作与VAO绑定了,即便不是,也应当绑定。

  1. 把顶点数组复制到缓冲中
1
2
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  1. 设置顶点属性指针

告诉 OpenGL 如何解析这些数据。 参数:属性位置(0), 组成元素个数(3), 类型, 是否标准化, 步长, 偏移量

1
2
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
  1. 解绑 glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0); // 绑定结束

glBindVertexArray(0) 重新设置为0, 这一轮的VAO绑定结束了。

顶点着色器

着色器语言GLSL:

1
2
3
4
5
6
7
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

创建着色器对象:

1
2
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

把源码字符串附加给着色器对象:

1
2
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

检测是否编译成功:

1
2
3
4
5
6
7
8
9
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;
}

片段着色器

片段着色器决定了像素最终显示的颜色。

抗锯齿发生在这一阶段。

GLSL:

1
2
3
4
5
6
7
#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 
1
2
3
4
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

着色器程序

创建了上面两个着色器对象之后,还需要着色器程序对象组装多个着色器对象。

创建着色器程序对象:

1
2
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

将之前的着色器对象附加到程序对象,然后链接:

1
2
3
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

检查:

1
2
3
4
5
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
    ...
}

激活程序对象:

1
glUseProgram(shaderProgram);

删除着色器对象:

1
2
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

帧循环

要在 while 循环里生成 每一帧 画面。基本步骤是:

  • 接收键盘等输入,执行逻辑

  • 清除颜色缓冲、深度缓冲

  • 激活着色器程序

  • 绑定对应VAO

  • 确定 uniform 变量

  • 绘制

  • 交换缓冲区

Licensed under CC BY-NC-SA 4.0
最后更新于 2025年12月23日 20:14
使用 Hugo 构建
主题 StackJimmy 设计