OPenGL学习(1)显示一个三角形

主要内容

  • 图形渲染管线
  • 安装OpenGL
  • OpenGL基本用法

图形渲染管线

图形渲染管线是渲染的核心,是通过给定虚拟相机、3D场景物体 以及光源等场景要素来产生或者渲染一副2D的图像。

渲染有很多个步骤,其中某些是可以并行的,因此叫流水管线。

图形渲染管线的步骤基本上是确定的,但是不同的书上分类的规则不同。这里按照实时渲染这本书上来分类。一共有3个阶段

  1. 应用程序阶段
  2. 几何阶段
  3. 光栅化阶段

应用阶段

应用阶段的任务:将需要绘制到屏幕的图元数据输入到几何阶段。图元指的是绘制图形的基本单位。在OpenGL中,一共有7种图元:点,线,三角形,线带,线环,三角形带,三角形环。

应用还可以处理下面这些:

碰撞检测、加速算法、输入检测,动画,力反馈以及纹理动画,变换仿真、几何变形,层次视锥裁剪

应用阶段的输出是:摄像机的位置,光照,模型的图元。

几何阶段:

几何阶段的任务是:将图元从3维空间映射到2维屏幕上

主要包括下面几个步骤:

  • 模型视图变换(MV),模型变换是将图元的坐标从local坐标转换成世界坐标,视图变换是将
  • 顶点着色
  • 投影 (P)
  • 裁剪,投影之后得到了
  • 屏幕映射。

其中,MVP和顶点着色是写在顶点着色器中的。屏幕映射是用硬件实现的,属于完全不可编程。

光栅化阶段

三角形设定,三角形遍历,像素着色,合并阶段

安装OpenGL

现在安装OpenGL我们只安装GLAD和GLFW就行了。

  • GLAD是用来管理OpenGL的函数指针的。OpenGL是一套接口,通常我们使用函数指针来调用这些,但是因为不同的显卡驱动获取OpenGL函数地址不同,程序就需要针对不同的驱动单独编写适应性代码。GLAD提供了一套封装,可以方便的获取不同驱动下的OpenGL的函数地址
  • GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户 创建OpenGL上下文,定义窗口参数以及处理用户输入。

glfw可以在官网页面下载,可以直接下载用vs编译好的dll和lib。里面有vs2022以及之前的很多版本,基本够用了。

glad也可以在官网下载,但是要配置下;

  • API选最新的
  • Profile设置为Core(核心模式)

然后点击生成得到一个glad.c文件和一堆头文件。不过这个属于第三方的代码,我喜欢打包成一个库使用。所以先创建一个vs项目,然后导入glad.c文件,并且配置头文件目录,把设置生成exe改成静态库lib(这个在项目设置里面)。然后ctr+f5生成得到glad.lib。

最后新建一个项目,这个项目就是我们写opengl的地方。首先设置头文件目录,把刚刚glad的目录包括进来,然后添加静态库目录和文件(在项目配置里)

到此,应该就没什么问题了。如果对静态库,动态库这些理解不深的话,可以先复习一下。再就是vs这个IDE的配置和使用,可以好好学学,Windows下我个人还是更喜欢vs。

OpenGL基本用法

  • VAO,
  • VBO,GPU中的一块显存区域
  • EBO

在GPU显存中,用glGenBuffer()创建一块缓冲区,用来存放顶点数据。然后将这个缓冲区绑定到GL_ARRAY_BUFFER。调用glBindBuffer()这个函数。具体的参数什么的放到最后的代码里面说,这里先看流程。

在OpenGL中有很多个缓冲区,这里我们用到的是GL_ARRAY_BUFFER这个缓冲区来存放顶点。我们说把创建的VBO(也就是显存中的一块空间)绑定到这个缓冲区,意思是,以后我们并不操作VBO,而是操作缓冲区GL_ARRAY_BUFFER。

OpenGL是面向过程的C语言程序设计方法,所以不能像面向对象那样去思考。

接着往下,在绑定了缓冲区后,需要调用glBufferData()加载数据了。

数据加载完了以后需要写Shader,Shader(着色器)是一个代码块,需要编译链接然后使用。不过这些OpenGL都帮我们封装好了,只需要用GLSL语言写就行了(很简单的语言),GLSL是openGL Shader Language,除此以外还有别的什么SL,不过都大同小异。

在OpenGL中,我们写两个着色器,顶点着色器和片段着色器。

编写的步骤是:

  • 用一个字符串保存着色器代码
  • 创建Shader对象 glCreateShader(),如果是顶点着色器,就带个参数告知(后面会谈到)
  • 将着色器代码加载到shader对象中,glShaderSource()
  • 编译glCompileShader()
  • 创建一个程序对象 glCreateProgram()
  • 附加并链接两个着色器,glAttachShader(),glLinkProgram()
  • 使用着色器程序 glUseProgram()

Shader写好以后,GPU就可以进行渲染了。下一步是要告知GPU如何理解传进去的数据。这里需要用到VAO,顶点数组对象。

下面是代码和我的注释

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

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// 屏幕设置
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

// 顶点着色器的代码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

// 片段着色器的代码
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";

int main()
{

    // glfw初始化
    glfwInit();

    // 这个函数是设置一些参数的
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // 创建一个窗口
    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,glad是用来管理函数指针的。
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // 编译,链接,生成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;
    }
    
    // 片段着色器
    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(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;
    }

    // 程序生成了及时删除shader
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    // 顶点数据
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // left  
         0.5f, -0.5f, 0.0f, // right 
         0.0f,  0.5f, 0.0f  // top   
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
   
    // 将VAO绑定到Vertex Array上
    glBindVertexArray(VAO);

    // 将VBO绑定到GL_ARRAY_BUFFER上
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 告知如何处理Vertex Array,(VAO)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // 数据传完了,也告知了如何解读,就需要解绑了
    // 解绑GL_ARRYA_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    
    // 解绑Vertex Array
    glBindVertexArray(0);


    // 主事件循环
    while (!glfwWindowShouldClose(window))
    {
        // 处理输入
        processInput(window);

        // 设置颜色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 显示三角形
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);     // 这里其实不需要每次都绑定,我们只显示一个三角形,绑定一次,一直用就行了。
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // 窗口有关的,不需要管
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 释放内存
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // 结束程序
    glfwTerminate();
    return 0;
}


// 处理输入
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// 每次窗口大小修改后会调用这个
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // 视口是我们看到的屏幕的大小
    glViewport(0, 0, width, height);
}

窗口和事件处理

posted @   Destiny233  阅读(124)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示