【Learn OpenGL 学习笔记】一 · 前四章总结

Learn OpenGL CN
Learn OpenGL EN

本来是懒得写笔记了,毕竟原教程已经很详细了,步骤也很清晰
一口气做完前四章后(Hello Triangle、Shaders、Texture、Transform),感觉还是需要一个总结,老了记性不大好(爬
另一方面原教程的代码基本都挤在 main() 里,看着属实难受
 

OpenGL 简介

OpenGL 是什么

  • 本身是一套规范,而不是API
    • 规定函数的功能、结果
    • 函数的具体实现由库的开发者决定(通常是显卡厂商)
    • 包含了一系列可以操作图形、图像的函数
  • 本质上是状态机
    • 使用 State-Changing 函数设置 Context 状态,使用 State-Using 函数执行
  • 内核是 C 库
    • 对象、绑定对象
       

OpenGL 中经常用到的流程

// 1. 创建对象
unsigned int objectId = 0;
glGenObject(1, &objectId);

// 2. 绑定对象至上下文 context
glBindObject(GL_WINDOW_TARGET, objectId);

// 3. 设置当前绑定到 GL_WINDOW_TARGET 的对象的一些选项
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);

// 4. 将上下文对象设回默认
glBindObject(GL_WINDOW_TARGET, 0);

OpenGL 渲染模式

立即渲染模式(Immediate mode,固定渲染管线):抽象了很多细节,自由度低
核心渲染模式(Core-profile)
 

OpenGL 的扩展(Extension)

可以使用扩展是 OpenGL 的一大特性
当一个显卡公司提出一个新特性或者渲染上的大优化,通常会以扩展的方式在驱动中实现
开发者不必等待一个新的 OpenGL 规范面世,就可以使用这些新的渲染特性了,只需要简单地检查一下显卡是否支持此扩展
通常,当一个扩展非常流行或者非常有用的时候,它将最终成为未来的OpenGL规范的一部分

if(GL_ARB_extension_name)
{
    
    
    // 使用硬件支持的全新的现代特性
}
else
{
    
    
    // 不支持此扩展: 用旧的方式去做
}

渲染流水线

简化版渲染流水线
记忆恢复术·一图流(之前的笔记印象还是比较深的,就不再整理了,只做一些补充)

  • 图元 Primitive:想要绘制成的样子,如 GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP
  • 像素 Pixel 和片元 Fragment 的区别:片元是潜在的像素(当片元通过了各种测试被渲染到屏幕,它就成了像素)
     

常用对象

  • 顶点数组对象:Vertex Array Object,VAO
  • 顶点缓冲对象:Vertex Buffer Object,VBO
  • 元素缓冲对象(索引缓冲对象):Element Buffer Object,EBO(Index Buffer Object,IBO)
     

GPU 上的 Buffer 操作

新手村常用 Buffer:GL_ARRAY_BUFFERGL_ELEMENT_BUFFER

// 1. 生成 Buffer,并有一个独一无二的 ID
unsigned int VBO; 
glGenBuffers(1, &VBO);

// 2. Bind 类型,可同时绑定多个不同类型的 Buffer
glBindBuffer(GL_ARRAY_BUFFER, VBO);

// 3. 填充数据,将数据复制到当前与 GL_ARRAY_BUFFER 绑定的内存空间中
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
  • GL_STATIC_DRAW :数据不会或几乎不会改变,如静态图形
  • GL_DYNAMIC_DRAW:数据会被改变很多。
  • GL_STREAM_DRAW :数据每次绘制时都会改变。

还有个 glBufferSubData(),可以往 Buffer 里补充数据,需要计算好 Offset

整体流程

int main()
{
    
    
    // 1. 初始化 GLFW 窗口
    GLFWwindow* window = GLFWWindowSetup();

    // 2. 初始化 GLAD
    // GLAD: 管理 OpenGL 的指针,必须在使用 OpenGL 前初始化
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
    
    
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // 3. 设置视口尺寸
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    // 4. 读取、编译 Shader,设置 Shader Program
    Shader shader("VertexShader.glsl","FragmentShader.glsl");

    // 5. 准备顶点属性 VAO、纹理单元 Texture Unit
    unsigned int VAO, VBO;
    PrepareVertexAttriPtr(&VAO, &VBO);

    unsigned int texture0, texture1;
    PrepareTexture("Resource/TestTexture.jpg", &texture0);
    PrepareTexture("Resource/sakura.jpg", &texture1);

    // 6. 渲染循环
    while (!glfwWindowShouldClose(window))
    {
    
    
        // 6.1 检测关闭窗口的输入
        ProcessInput(window);

        // 6.2 真正的渲染循环部分
        RenderLoop(shader, VAO, texture0, texture1);

        // 6.3 交换 Buffer, 监听事件
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 7. 释放
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shader.ID);

    glfwTerminate();


    return 0;
}

链接顶点属性

渲出来的东西奇奇怪怪,八成是这儿整叉劈了
在这里插入图片描述
在这里插入图片描述

void PrepareVertexAttriPtr(unsigned int* outVAO, unsigned int* outVBO) {
    
    

    float vertices[] = {
    
    
    0.5f, 0.5f, 0.0f, 1.0, 0.0, 0.0, 1.0, 1.0, // Top right
    0.5f, -0.5f, 0.0f, 0.0, 1.0, 0.0, 1.0, 0.0,// Bot right
    -0.5f, -0.5f, 0.0f, 0.0, 0.0, 1.0, 0.0, 0.0,// Bot left
    -0.5f, 0.5f, 0.0f, 0.0, 1.0, 1.0, 0.0, 1.0 // Top left
    };

    GLint indices[] = {
    
    
        0, 1, 3,
        1, 2, 3
    };

    // 渲染模式 - States-setting
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 线框模式
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 填充模式(默认)


    // 1. 绑定 VAO
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    *outVAO = VAO;

    // 2. 把 VAO 的数据复制到 VBO
    GLuint VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    *outVBO = VBO;

    // 3. 把顶点索引复制到 EBO - EBO 被放在了 VAO 的末尾
    GLuint EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // 4. 设置顶点属性指针
    // Position
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    // Color
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    // UV
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));

    // 5. 解绑
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

glVertexAttribPointer()

  • 参数 1 指定要配置的顶点属性(跟 Shader 中的 layout (location = x) 对应)
  • 参数 2 指定顶点属性的大小(Vector 1,2,3,4?)
  • 参数 3 指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  • 参数 4 是否 Normalize(对于有符号型 signed 数据是 -1 ~1)
  • 参数 5 步长 Stride,下一次轮到该数据中间间隔了的长度,头到头,看图更直观
  • 参数 6 是偏移量 Offset,第一次出现时离 0 的距离,看图更直观。因为类型是 void*,要做个强制类型转换(虽然不懂为什么要整成这个类型)
     

读取纹理

借助 stb_image.h 库

void PrepareTexture(const char* texturePath, unsigned int* textureOut) {
    
    

    // 1. 绑定纹理
    unsigned int texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    *textureOut = texture;

    // 2. 设置 wrap & filter
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 3. 加载纹理
    int width, height, nrChannels;
    // OpenGL - bottomY, texture - topY
    stbi_set_flip_vertically_on_load(true);
    unsigned char* data = stbi_load(texturePath, &width, &height, &nrChannels, 0);

    // 4. 生成纹理单元和 MipMap
    if (data)
    {
    
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
    
    
        std::cout << "Failed to load texture" << std::endl;
    }

    // 5. 释放
    stbi_image_free(data);
}

纹理这里有两个需要设置的属性:Wrap Mode 和 Filter,引擎里见多了就不细写了
除了代码里的 GL_LINEAR 这种统一设置,还有类似 GL_LINEAR_MIPMAP_NEAREST 这样对 MipMap 区别对待的设置:使用 Nearest 的多级渐远纹理级别,并使用 Linear 进行采样

glTexImage2D()

  • 参数 1 指定了纹理目标(Target)。除此之外还有 GL_TEXTURE_1DGL_TEXTURE_3D
  • 参数 2 为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别
  • 参数 3 告诉OpenGL我们希望把纹理储存为何种格式。还可以有 GL_RGBA
  • 参数 4、5 设置最终的纹理的宽度和高度
  • 参数 6 直接设 0(历史遗留问题)
  • 参数 7、8 定义源图的格式和数据类型:使用 RGB 值加载,储存为 char(byte) 数组
  • 参数 9 是图像数据
     

shader Program 设置

shader Program 由多个 Shader 合并链接(至少由一个 Vertex Shader 和 Fragment Shader 链接)

创建 Shader Program Object 的流程

// 1. 读取 Shader 文件
// iostream, fstream, sstream
// ...

// 2. 编译 Shader
unsigned int vertexShaderID, fragmentShaderID;

vertexShaderID= glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderID, 1, &vShaderCode, NULL);
glCompileShader(vertexShaderID);
checkCompileErrors(vertexShaderID, "VERTEX");

fragmentShaderID= glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderID, 1, &vShaderCode, NULL);
glCompileShader(fragmentShaderID);
checkCompileErrors(fragmentShaderID, "FRAGMENT");

// 3. 创建 Program
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

// 4. 链接 Shader 到 Program
glAttachShader(shaderProgramID, vertexShaderID);
glAttachShader(shaderProgramID, fragmentShaderID);
glLinkProgram(shaderProgramID);
checkCompileErrors(shaderProgramID, "PROGRAM");

// 5. 删除 Shader,链接完成,卸磨杀驴
glDeleteShader(vertexShaderID);
glDeleteShader(fragmentShaderID);

需要使用这个 Program 渲染时(包括设置 Uniform 参数时),要先调用 glUseProgram() 函数

渲染循环

向量和矩阵运算相关的不写了,回去看了看之前的笔记还是挺全的

void RenderLoop(Shader shader, unsigned int VAO, unsigned int texture0, unsigned int texture1 = NULL) {
    
    
    // --清空屏幕--
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);   // State-setting
    glClear(GL_COLOR_BUFFER_BIT); // State-using


    // --属性更新--
    // 1. Texture
    // 提前绑定好纹理单元,并进行采样
    shader.use();
    shader.setInt("texture0", 0); //glUniform1i(glGetUniformLocation(shader.ID, "texture0"), 0);
    shader.setInt("texture1", 1);
	// 采样多个纹理时,需要手动 Active 后再绑定
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture0);
    if (texture1) {
    
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture1);
    }
    
    // 2. Transform
    // 新版本的 glm 矩阵需要手动初始化为单位矩阵
    glm::mat4 transform = glm::mat4(1.0f); 
    
    // **** 实际变换顺序与代码相反:scale-rotate-translate
    transform = glm::translate(transform, glm::vec3(0.1, -0.5 * sin((float)glfwGetTime()), 0.0));
    transform = glm::rotate(transform, (float)glfwGetTime(), glm::vec3(0.0, 0.0, 1.0));
    transform = glm::scale(transform, glm::vec3(1.0, 1.0, 1.0));
    
    // 将数据通过 Uniform 传递给 Shader,需要确保变量名称相同
    // 实际调用的 OpenGL 函数见 Shader 类,或注释
    shader.use(); // glUseProgram(shader.ID);
    shader.setFloat("brightness", 1.5); // glUniform1f(glGetUniformLocation(shader.ID, "brightness"), 2.0);
    unsigned int transformLoc = glGetUniformLocation(shader.ID, "transform");
    // **** GL_TRUE 这里对矩阵做了个转置
    glUniformMatrix4fv(transformLoc, 1, GL_TRUE, glm::value_ptr(transform));
    glBindVertexArray(VAO);


    // --绘制--
    //glDrawArrays(GL_TRIANGLES, 0, 3);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

传递纹理用的是 Uniform int,因为实际上直接传递的不是纹理数据,而是指向存放纹理数据的 ID

glfwGetTime():获取时间,玩具++;

这里有个问题,原文说 OpenGL 是列主序,glm 也是列主序,所以在传递变换矩阵的时候不需要做转置
但是我自己跑起来是需要转置的,竟然也没查到它们到底分别是什么主序,浪迹网络多年的搜索技能竟然败得这么彻底,多少有点离谱…
 

向 Shader 传值

向 Shader 传递顶点属性之外的其他数据时,需要借助全局变量 Uniform

Vertex Shader

// 使用 3.3 版本 OpenGL,Core 渲染模式
#version 330 core

// 顶点属性
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 vColor;
layout (location = 2) in vec2 vTexcoord;

// 需要确保和 Fragment Shader 的输入属性名称相同
out vec4 vertexColor;
out vec2 texcoord;

// 接收的全局变量
uniform mat4 transform;

void main()
{
    
    
   gl_Position = vec4(vPosition, 1.0) * transform;
   vertexColor = vec4(vColor, 1.0);
   texcoord = vTexcoord;
}

Fragment Shader

#version 330 core

// 需要确保和 Vertex Shader 的输出属性名称相同
in vec4 vertexColor;
in vec2 texcoord;

out vec4 fragColor;

// 接收的全局变量
uniform float brightness;
uniform sampler2D texture0;
uniform sampler2D texture1;

void main()
{
    
    
   //fragColor = texture(texture0, texcoord);
   //fragColor *= vertexColor;
   fragColor = mix(texture(texture0, texcoord), texture(texture1, texcoord), 0.5) * brightness;
}

mix(),同 Lerp(),实用小玩具
 

传递 Uniform

// 1. 根据名称获取 Uniform 的位置
GLint paramLoc = glGetUniformLocation(ID, "paramName");
// 2. 给该位置赋值
glUniform1i(paramLoc, value);

// 懒人二合一
glUniform1i(glGetUniformLocation(ID, "paramName"), value);

Shader 类

// 防止多次定义起冲突
#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>;
#include <string>

class Shader
{
    
    
public:
    unsigned int ID;

    Shader(const char* vertexPath, const char* fragmentPath);

    void use();

    void setBool(const std::string& name, bool value) const;
    void setInt(const std::string& name, int value) const;
    void setFloat(const std::string& name, float value) const;

private:
    void checkCompileErrors(unsigned int shader, std::string type);
};

#endif
#include "Shader.h";

#include <glad/glad.h>;
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>


Shader::Shader(const char* vertexPath, const char* fragmentPath)
{
    
    
    // 1. 读取 Shader 文件
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;

    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    try
    {
    
    
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;

        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();

        vShaderFile.close();
        fShaderFile.close();

        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (std::ifstream::failure& e)
    {
    
    
        std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ: " << e.what() << std::endl;
    }
    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();

    // 2. 编译 shader
    unsigned int vertex, fragment;

    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    checkCompileErrors(vertex, "VERTEX");

    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    checkCompileErrors(fragment, "FRAGMENT");

    ID = glCreateProgram();
    glAttachShader(ID, vertex);
    glAttachShader(ID, fragment);
    glLinkProgram(ID);
    checkCompileErrors(ID, "PROGRAM");

    // 3. 删除 shader
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}


// 激活 program
void Shader::use()
{
    
    
    glUseProgram(ID);
}

// 设置 Uniform 更方便
void Shader::setBool(const std::string& name, bool value) const
{
    
    
    glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

void Shader::setInt(const std::string& name, int value) const
{
    
    
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setFloat(const std::string& name, float value) const
{
    
    
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}


// 检查编译和链接状态
void Shader::checkCompileErrors(unsigned int shader, std::string type)
{
    
    
    int success;
    char infoLog[1024];
    if (type != "PROGRAM")
    {
    
    
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
    
    
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n --- " << std::endl;
        }
    }
    else
    {
    
    
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if (!success)
        {
    
    
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n --- " << std::endl;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44045614/article/details/127858327