【OpenGL】渲染概览

相关库

  • OpenGL 渲染管线(规范的接口文档+各平台实现不同)
  • GLAD 确定当前显卡驱动版本下OpenGL函数的位置(由于OpenGL驱动版本众多,它大多数函数的位置都无法在编译时确定下来,需要在运行时查询)用来管理OpenGL的函数指针。
  • GLFW 窗口操作、定义OpenGL上下文、处理用户输入(区分OpenGL概念下的视口)
    创建、初始化与释放
    对窗口注册回调函数(创建后 循环前)
    void func_callback(){}                   // 自定义处理操作
    glfwSetxxx(GLFWwindows*, func_callback); // 绑定某事件触发时的处理操作
    
    渲染循环 Render Loop
    while(!glfwWindowShouldClose(window)) {  // 每循环的开始前检查一次GLFW是否被要求退出
    	// ...
    	// 放入所有渲染操作
       	glfwSwapBuffers(window);  // 绘制GLFW窗口各像素颜色值的缓冲结果
        glfwPollEvents();         // 检查是否触发事件并调用相应callback函数
    }
    

    单缓冲渲染生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实,时可能出现图像闪烁。双缓冲(Double Buffer)机制下,前缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在后缓冲上绘制。当所有的渲染指令执行完毕后,我们Swap前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。

渲染管线 Graphics Pipeline


抽象各阶段
着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行
着色器之间不能相互通信,沟通只有通过阶段性的输入和输出
必须定义至少一个顶点着色器和一个片段着色器(GPU中没有默认的顶点/片段着色器)

  • 顶点着色器
    3D坐标转换为 标准化设备坐标 Normalized Device Coordinates, NDC(x、y和z范围在-1.0到1.0之间,且y轴向上)
  • 图元装配
    图元类型有 GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP
  • 几何着色器
    把图元形式的一系列点集作为输入,通过产生新顶点构造出新的(或是其它的)图元来生成其他形状
  • 光栅化阶段
    还会进行视图剪裁
  • 片段着色器
    计算一个像素的最终颜色,包含3D场景的数据(比如光照、阴影、光的颜色等等),会进行Fragment Interpolation(利用所有输入属性进行插值)
  • Alpha测试和混合(Blending)阶段
    遮挡效果和色彩融合

渲染数据准备

  • 定义顶点缓冲对象 Vertex Buffer Object VBO
    开辟存储顶点数据的显存,当数据从内存发送至显存后,顶点着色器几乎能立即访问顶点

    float vertices[] = {
    	// 位置              // 颜色
     	0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  
    	-0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,   
     	0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    
    };
    
    unsigned int VBO;
    glGenBuffers(1, &VBO);                 // 创建对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);    // 绑定对象至上下文, 同时指定对象的具体类型
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);   // 一些操作或设置
    // 允许程序存在多个VBO, 根据当前状态绑定的VBO来获取顶点数据
    
  • 定义顶点数组对象 Vertex Array Object VAO
    如何解析顶点缓冲数据(顶点属性)

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);      // 链接位置属性
    // glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float)));   // 颜色属性
    
    // 首个参数:要配置哪个顶点属性,即属性对应的location值
    // 倒数第二个:步长
    // 最后参数:该属性的数据在缓冲中起始位置的偏移量
    
    glEnableVertexAttribArray(0);  // 启用
    

    当有多个顶点属性需要配置时使用 VAO

    unsigned int VAO;
    glGenVertexArrays(1, &VAO);  // 创建顶点数组对象
    glBindVertexArray(VAO);      // 绑定
    
    // 创建VBO
    unsigned int VBO;      
    glGenBuffers(1, &VBO);  
    
    glBindBuffer(GL_ARRAY_BUFFER, VBO);           // 绑定顶点数据缓冲
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);     // 复制
    	
    // 创建EBO Element Buffer Object 元素缓冲对象
    unsigned int EBO;
    glGenBuffers(1, &EBO);   
    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);                // 激活
    
    // 配置完所有 VAO(数据缓存在哪, 如何解释)
    // .. 绘制代码(渲染循环中)..
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    

可编程着色器 Shader

  • 编程语言 GLSL opengl shading language
    除了支持C的基本数据类型外,还定义了向量与矩阵
    对于顶点着色器,每个输入变量又叫做顶点属性,有内存上限,一般为16个(4分量)

    如果打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)。

    Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式(程序和着色器间的数据交互)

    但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

    #version version_number
    in type in_var;
    in type in_var;
    
    out type out_var;
    
    uniform type uniform_name;
    
    int main()	{
      	...
     	out_var = xxx;
    }
    
  • OpenGL 代码运行时动态编译着色器的源代码

    // GLSL 暂时放入文件顶部的c风格字符串中
    // in 关键字声明所有的输入顶点属性(可以有多个)同时设定输入变量的 location
    // 解析数据, 绑定顶点属性指针时需要指定 location 参数,以指明绑定到哪个顶点属性上
    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";
    
    // openGL渲染部分
    /* 创建可编程着色器对象 */
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);    // 创建着色器对象
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);  // 附加源码
    glCompileShader(vertexShader);  // 编译
    
    /* 多个着色器合并链接,创建着色器程序对象 */
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);    // 激活
    
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    int location = 	glGetUniformLocation(shaderProgram, "某个Uniform变量名");   // 查询Uniform变量地址, 不要求着色器程序激活
    glUniformNX(location, 0.0f, greenValue, 0.0f, 1.0f);  // 更新Uniform变量值, 要求着色器激活 
    
  • GLSL定义自己的着色器,并编写相关接口方便复用

    // GLSL将自定义着色器写入某个文本文件,存入硬盘
    // 1. for example, shader.vs/shader.fs
    
    // c++ 类的头文件
    // 2. shader.h
    #ifndef SHADER_H
    #define SHADER_H
    
    #include <glad/glad.h>; // 包含glad来获取所有的必须OpenGL头文件
    
    #include <string>
    #include <fstream>
    #include <sstream>
    #include <iostream>
    
    
    class Shader {
    public:
      	unsigned int ID;    // 程序ID
    
    	Shader(const char* vertexPath, const char* fragmentPath);  // 从文件流构造    
    	
    	void use();         // 激活着色器
    	
    	// uniform工具函数
    	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;
    };
    // 3. 当然还要定义实现 shader.cpp
    
    // 4. 某个项目 myProgram.cpp 使用自定义着色器
    Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs");
    ...
    while(...)	{
     	ourShader.use();
    	ourShader.setFloat("someUniform", 1.0f);
    	DrawStuff();
    }
    

坐标系统及转换

  • glm 库
    OpenGL不自带任何的矩阵和向量操作定义。使用 glm库(OpenGL Mathematics)完成相关数学运算(它只有头文件)

  • 坐标系统

    Local/Object Space 物体

    World Space 多个物体+相机系统

    View/Camera/Eye Space 相机空间

    Clip Space 裁剪+缩放+各分量归一化后的相机空间,决定最终显示的内容范围、有无变形等等

    投影矩阵创建平截头体 Frustum 进行剪裁和缩放(此处投影相比多视几何的投影概念范围更小,仅指相机坐标系到像平面的映射),然后通过 透视除法 Perspective Division 将齐次坐标变为 3D 标准化设备坐标

    Screen Space 2维屏幕
    Viewport Transform 视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值