• OpenGL入门(五)之Matrix矩阵操作和坐标系统


    本系列文章为Learn OpenGL个人学习总结!
    OpenGL入门(一)之认识OpenGL和创建Window
    OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
    OpenGL入门(三)之着色器Shader
    OpenGL入门(四)之纹理Texture
    OpenGL入门(五)之Matrix矩阵操作和坐标系统
    OpenGL进阶(一)之帧缓冲FrameBuffer
    OpenGL进阶(二)之像素缓冲PixelBuffer

    Matrix矩阵

    我们在前边介绍GLSL中基础变量类型时,还有一个mat没有使用到,这个变量就是矩阵的类型!
    使用(多个)矩阵(Matrix)对象可以更好的变换(Transform)一个物体,来使我们的物体动起来!

    关于矩阵相关的基础数学知识,我们在线性代数的课程里应该都学习过,这里就不再赘述了!

    GLM

    GLM是OpenGL Mathematics的缩写,它是一个只有头文件的库,是专门为OpenGL量身定做的开源数学库。
    仓库地址:https://github.com/g-truc/glm

    GLM库从0.9.9版本起,默认会将矩阵类型初始化为一个零矩阵(所有元素均为0),而不是单位矩阵(对角元素为1,其它元素为0)。如果你使用的是0.9.9或0.9.9以上的版本,你需要将所有的矩阵初始化改为 glm::mat4 mat = glm::mat4(1.0f)。

    应用矩阵变换

        const char *vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 1) in vec3 aColor;\n"
        "layout (location = 2) in vec2 aTexCoord;\n"
        
        "out vec3 vertexColor;\n"
        "out vec2 texCoord;\n"
        "uniform mat4 transform;\n"   //声明一个矩阵变量
        "void main()\n"
        "{\n"
        "   gl_Position = transform * vec4(aPos, 1.0);\n"
        "   vertexColor = aColor;\n"
        "   texCoord = aTexCoord;\n"
        "}\0";
    
    
    
        glm::mat4 trans = glm::mat4(1.0f);//变换矩阵  0.9.9及以上版本需要初始化为单位矩阵
        //沿z轴旋转90度
        trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
        //缩放0.5倍
        trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
    
        glUseProgram(shaderProgram);
        int transLocation = glGetUniformLocation(shaderProgram, "transform");
        /**
         * @brief 给mat4赋值
         * 第1个参数:变量位置
         * 第2个参数:发送几个矩阵
         * 第3个参数:是否对矩阵进行转置(Transpose)
         * 第4个参数:真正的矩阵数据,使用glm::value_ptr将数据转换为OpenGL需要的
         */
        glUniformMatrix4fv(transLocation, 1, GL_FALSE, glm::value_ptr(trans));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    需要注意的一点:代码中写的是先位移再旋转,实际变换则相反,先应用旋转再位移!

    坐标系统

    我们现在代码里的坐标都是[-1,1]范围内的标准化设备坐标,通常,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统(Coordinate System),对我们来说比较重要的总共有5个不同的坐标系统:

    • 局部空间(Local Space,或者称为物体空间(Object Space))
    • 世界空间(World Space)
    • 观察空间(View Space,或者称为视觉空间(Eye Space))
    • 裁剪空间(Clip Space)
    • 屏幕空间(Screen Space)

    概述

    为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。
    在这里插入图片描述
    前边都好理解,我们重点关注一下裁剪坐标。

    裁剪空间

    在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。

    为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix)。

    由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。

    一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;这一步会在每一个顶点着色器运行的最后被自动执行。

    将观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。

    1. 正射投影矩阵
    glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
    
    • 1

    第1,2个参数:平面的宽
    第3,4个参数:平面的高
    第5个参数:近平面的距离
    第6个参数:远平面的距离

    1. 透视投影矩阵
      顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。
    glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
    
    • 1

    第1个参数:fov(Field of View),表示观察空间的大小,通常是45°
    第2个参数:视口的宽高比
    第3个参数:近平面的距离
    第4个参数:远平面的距离

    应用MVP矩阵

    const char *vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 1) in vec3 aColor;\n"
        "layout (location = 2) in vec2 aTexCoord;\n"
        
        "out vec3 vertexColor;\n"
        "out vec2 texCoord;\n"
        "uniform mat4 model;\n"
        "uniform mat4 view;\n"
        "uniform mat4 projection;\n"
        "void main()\n"
        "{\n"
        "   gl_Position = projection * view * model * vec4(aPos, 1.0);\n"  //注意:变换与实际相反,这里要反着乘
        "   vertexColor = aColor;\n"
        "   texCoord = aTexCoord;\n"
        "}\0";
    
    
        glm::mat4 model = glm::mat4(1.0f);//0.9.9及以上版本需要初始化为单位矩阵
        //沿x轴旋转
        model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
        glm::mat4 view = glm::mat4(1.0f);
        // 注意,我们将矩阵向我们要进行移动场景的反方向移动。
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
        glm::mat4 projection = glm::mat4(1.0f);
        projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
    
        glUseProgram(shaderProgram);
        int modelLocation = glGetUniformLocation(shaderProgram, "model");
        glUniformMatrix4fv(modelLocation, 1, GL_FALSE, glm::value_ptr(model));
        int viewLocation = glGetUniformLocation(shaderProgram, "view");
        glUniformMatrix4fv(viewLocation, 1, GL_FALSE, glm::value_ptr(view));
        int projectionLocation = glGetUniformLocation(shaderProgram, "projection");
        glUniformMatrix4fv(projectionLocation, 1, GL_FALSE, glm::value_ptr(projection));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    这样,我们就得到了一个有透视效果的矩形纹理!

    Z缓存

    OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。

    启用:

    glEnable(GL_DEPTH_TEST);  //默认关闭
    
    • 1

    然后在渲染的时候,像清除颜色缓冲那样:

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    • 1
  • 相关阅读:
    【Rust 笔记】17-并发(上)
    Dart语言简介
    【笔记】混淆矩阵和ROC曲线
    从另外一个进程中读取数据
    排序方法总结
    Node.js 语言特定指南
    PG函数中有OUT则忽略 RETURNS SETOF record AS $$
    详解Transformer中的Encoder
    c++多线程学习01 对象生命周期和线程的退出与等待
    Dropout回顾
  • 原文地址:https://blog.csdn.net/aiynmimi/article/details/126707584