OpenGL入门002——顶点着色器和片段着色器

文章目录

一些概念

坐标转换阶段

概述: 模型空间、世界空间、视图空间和裁剪空间是对象在3D场景中经历的不同坐标变换阶段。每个空间对应渲染管道的一个步骤,逐步将模型从其初始位置转换到最终屏幕上的位置

模型空间:

  • 定义:这是对象的本地坐标系,是模型创建时的坐标
  • 作用:模型空间定义了物体的基本形状和几何信息,不受场景中其他物体位置的影响。每个模型都有自己的模型空间坐标
  • 转换:通过模型矩阵,可以将模型空间的坐标转换为世界空间

世界空间:

  • 定义:这是整个场景的坐标系,所有模型在世界空间中都有唯一的位置和方向,可以视为"全局坐标系"
  • 作用:将所有对象放置在同一个坐标系统中,确保它们相对位置正确
  • 转换:通过应用视图矩阵,将世界空间的坐标转换为视图空间,视图矩阵通常基于摄像机的位置和方向

视图空间:

  • 定义:又称为"摄像机空间",是以摄像机位置为原点的坐标系,此时场景中的所有对象都以摄像机为参考重新定位
  • 作用:使得所有对象相对于摄像机的位置和方向变得更直观,便于确定哪些对象可见、如何投影到屏幕
  • 转换:使用投影矩阵将视图空间转换为裁剪空间,这一步决定了图像的投影类型(如透视投影或正交投影)

裁剪空间:

  • 定义:应用投影变换后的空间,此时3D场景的坐标被转换为一个标准化的3D盒子,所有可见的坐标x,y,z值均被限制在-1到1之间
  • 作用:裁剪空间便于对视锥外部的物体进行裁剪,只保留可见部分。裁剪后的坐标将进行透视除法,映射到2D屏幕上的坐标,即归一化设备坐标
  • 转换:裁剪空间进一步转换为屏幕空间,经过视口变换,使坐标适配屏幕的分辨率和比例

顶点着色器

概述: 顶点着色器(Vertex Shader)是对输入的顶点进行处理,顶点是组成几何体的基本元素,比如三角形的每个角都是一个顶点

作用:

  • 顶点位置变换:顶点着色器通常会将顶点从模型空间(即对象的局部坐标系)转换到世界空间、视图空间,最后转换到裁剪空间,以便在屏幕上正确显示
  • 顶点属性处理:除了位置,顶点着色器还可以处理其他与顶点相关的属性,比如法线、纹理坐标、颜色等
  • 光照计算:在某些情况下,顶点着色器可以进行基础的光照计算,如使用法线来计算顶点的光照效果

输入: 顶点的坐标、法线、纹理坐标等数据

输出: 处理后的顶点坐标,如变换后的顶点位置和其他顶点属性,供后续的图形流水线使用

片段着色器

概述: 片段着色器(Framgment Shader)是屏幕上每个像素的潜在颜色值,在光栅化阶段之后,每个几何体被转换为一系列像素片段,片段着色器负责确定这些片段的最终颜色

作用:

  • 颜色计算:片段着色器通过对纹理、光照、材质等信息的处理,确定每个像素的颜色,它通常会结合插值后的顶点属性(如纹理坐标或颜色)进行复杂的颜色计算
  • 光照效果:片段着色器可以进行精确的光照计算,以便生成更逼真的阴影和高光效果
  • 纹理映射:片段着色器可以从纹理中采样,根据纹理坐标获取纹理颜色,并应用到片段上

输入: 每个片段的插值属性(如纹理坐标、颜色、发现等)

输出: 每个片段的最终颜色值,传递给屏幕或帧缓冲区

VBO

概述: VBO(Vertex Buffer Object)是一个存储在GPU内存中的缓冲区,用于存放顶点数据。通常,顶点数据包含顶点的坐标、颜色、法线、纹理坐标等信息。通过使用VBO,程序可以将这些顶点数据传输到GPU,这样GPU可以在渲染时快速访问它们,而不需要每帧都从CPU传输数据

关键步骤:

  • 创建VBO:使用glGenBuffers()创建一个VBO
  • 绑定VBO:使用glBindBuffer()绑定VBO,指定将要存储的缓冲数据类型(如顶点数据GL_ARRY_BUFFER)
  • 填充VBO:使用glBufferData()将顶点数据传输到VBO中

VAO

概述: VAO(Vertex Array Object)是一个用于保存VBO配置的对象,它记录了与绘制顶点相关的所有状态信息。VAO不仅仅是VBO的一个包装器,它还保存了顶点属性指针和启用状态。例如:每个顶点的布局、数据的解释方式(如步幅、偏移量),以及使用的VBO。使用VAO可以简化渲染过程,因为当VAO被绑定时,所有与其关联的VBO和顶点属性信息都会自动生效。

关键步骤:

  • 创建VAO:使用glGenVertexArrays()创建一个VAO
  • 绑定VAO:使用glBindVertexArray()绑定VAO
  • 设置顶点属性指针:使用glVertexAttribPointer()设置顶点属性指针(告诉OpenGL如何解释顶点数据)
  • 启用顶点属性:使用glEnableVertexAttribArray()启用顶点属性

实战

简介

怎么在vscode上使用cmake构建项目,具体可以看这篇Windows上如何使用CMake构建项目 - 凌云行者的博客

目的: 绘制一个三角形

环境:

  • 编译工具链:使用msys2安装的mingw-gcc
  • 依赖项:glfw3:x64-mingw-static,glad:x64-mingw-static(通过vcpkg安装)

main.cpp

cpp 复制代码
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

using std::cout;
using std::endl;

// 屏幕宽度
const unsigned int SCR_WIDTH = 800;
// 屏幕高度
const unsigned int SCR_HEIGHT = 600;

// 窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    // 确保视口与新窗口尺寸匹配,注意在视网膜显示器上,宽度和高度会显著大于指定值
    glViewport(0, 0, width, height);
}

// 处理输入
void process_input(GLFWwindow* window) {
    // 按下ESC键时进入if块
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        // 关闭窗口
        glfwSetWindowShouldClose(window, true);
}

// 顶点着色器源码
const char *vertexShaderSource = "#version 330 core\n" // 指定了GLSL(OpenGL着色器语言)的版本
                                 "layout (location = 0) in vec3 aPos;\n" // 定义了一个输入变量aPos,它是一个vec3类型的变量, 并且指定了它的位置值为0, 这意味着顶点属性数组的第一个属性将被绑定到这个变量
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" // 将输入的顶点位置aPos转换为一个四维向量,gl_Postion是OpengGL固定功能管线中用于存储顶点位置的变量
                                 "}\0";

// 片段着色器源码
const char *fragmentShaderSource = "#version 330 core\n" // 指定了GLSL(OpenGL着色器语言)的版本
                                   "out vec4 FragColor;\n" // 定义了一个输出变量FragColor,它是一个vec4类型的变量,表示片段颜色,out关键字表示这个变量将输出到渲染管线的下一个阶段
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" // 将输出颜色设置为橙色
                                   "}\n\0";

int main() {
    // 初始化glfw
    glfwInit();
    // 设置opengl版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // 使用核心模式:确保不使用任何被弃用的功能
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	
    // 创建glfw窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "I am window title", NULL, NULL);
    if (window == NULL) {
        cout << "Failed to create glfw window" << endl;
        // 终止GLFW
        glfwTerminate();
        return -1;
    }
    // 设置当前窗口的上下文
    glfwMakeContextCurrent(window);
    // 设置窗口大小改变的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    // 加载opengl函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        cout << "Failed to initialize GLAD" << endl;
        return -1;
    }
    
    // 构建并编译顶点着色程序
    // 创建一个着色器对象,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);
        cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
                  << infoLog << endl;
    }
    // 构建并编译片段着色器
    // 创建一个着色器对象,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);
        cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
                  << infoLog << endl;
    }
    // 创建着色器程序对象
    unsigned int shaderProgram = glCreateProgram();
    // 将着色器对象附加到着色器程序上
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
   	// 链接程序对象
    glLinkProgram(shaderProgram);
    // 检查链接是否成功
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
                  << infoLog << endl; 
    }
    // 删除着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    // 设置三角形的顶点数据
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // 左下角
        0.5f, -0.5f, 0.0f,  // 右下角
        0.0f, 0.5f, 0.0f    // 顶部
    };
    // 生成一个VAO
    unsigned int VAO;
    glGenVertexArrays(1, &VAO);
    // 绑定VAO,使其成为当前操作的VAO
    glBindVertexArray(VAO);
    // 生成一个VBO
    unsigned int VBO;
    glGenBuffers(1, &VBO);
    // 绑定VBO, 使其成为当前操作的VBO,GL_ARRAY_BUFFER表示顶点缓冲区
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    // 为当前绑定的VBO创建并初始化数据存储,GL_STATIC_DRAW表示数据将一次性提供给缓冲区,并且在之后的绘制过程中不会频繁更改
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // 定义顶点属性的布局
    // - index:顶点属性的索引
    // - size:每个顶点属性的数量,每个顶点有三个分享
    // - type:数据类型
    // - normalized:是否将非浮点数值归一化
    // - stride:连续顶点属性之间的间隔
    // - pointer:数据在缓冲区中的偏移量
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
    // 启用顶点属性数组
    glEnableVertexAttribArray(0);
    // 解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // 解绑VAO
    glBindVertexArray(0);
    
    // 循环渲染
    while (!glfwWindowShouldClose(window)) { // 检查是否应该关闭窗口
        // 处理输入
        process_input(window);
        
        // 清空屏幕所用的颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        // 清空颜色缓冲,主要目的是为每一帧的渲染准备一个干净的画布
        glClear(GL_COLOR_BUFFER_BIT);
        
        // 使用着色器程序
        glUseProgram(shaderProgram);
        // 绑定VAO
        glBindVertexArray(VAO);
        // 绘制三角形
        glDrawArrays(GL_TRIANGLES, 0 ,3);
        
        // 交换缓冲区
        glfwSwapBuffers(window);
        // 处理所有待处理事件,去poll所有事件,看看哪个没处理的
        glfwPollEvents();
    }
    
    // 删除VAO
    glDeleteVertexArrays(1, &VAO);
    // 删除VBO
    glDeleteBuffers(1, &VBO);
    // 删除着色器程序
    glDeleteProgram(shaderProgram);
    
    // 终止GLFW,清理GLFW分配的资源
    glfwTerminate();
    return 0;
}

CMakeLists.txt

cmake 复制代码
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(HelloTriangle)

# vcpkg集成, 这里要换成你自己的vcpkg工具链文件和共享库路径
set(VCPKG_ROOT D:/software6/vcpkg/)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_PREFIX_PATH ${VCPKG_ROOT}/installed/x64-mingw-static/share)

# 查找所需的包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)

# 添加可执行文件
add_executable(HelloTriangle main.cpp)

# 链接所需的库
target_link_libraries(HelloTriangle PRIVATE glad::glad glfw)

最终效果

相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos5 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室6 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0016 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我586 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc6 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很6 小时前
C++ 集合 list 使用
c++