OpenGL入门——矩阵变换与坐标系统

发布时间 2023-10-15 21:10:30作者: 一只小瓶子

一、OpenGL的数学库GLM

向量和矩阵的运算就不作说明了,直接介绍OpenGL中如何使用矩阵变换。

GLM(官网:OpenGL Mathematics (g-truc.net))是OpenGL Mathematics的缩写,它是一个只有头文件的库,也就是说只需包含对应的头文件就行了,不用链接和编译。把头文件的根目录复制到项目的includes文件夹,就可以使用这个库了。

 

GLM大多功能都包含在以下四个头文件中

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

 

向量位移示例

//向量平移:向量(1, 0, 0)位移(1, 1, 0)个单位
void vector_translate()
{
    glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);//定义一个向量

    glm::mat4 trans = glm::mat4(1.0f);//定义4x4单位矩阵
    trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));//创建位移矩阵:translate函数将给定矩阵乘以位移向量得到位移变换矩阵
    vec = trans * vec;//变换矩阵与向量相乘得到位移后的向量
    std::cout << vec.x << "," << vec.y << "," << vec.z << std::endl;//输出:2,1,0
}

 

上一节OpenGL入门——多个纹理 - 一只小瓶子 - 博客园 (cnblogs.com)进行旋转缩放平移示例

在顶点着色器中将顶点坐标进行旋转缩放

//vertex shader source
#version 330 core
layout(location = 0) in vec3 position;     //位置X,Y,Z
layout(location = 1) in vec3 color;        //颜色R,G,B
layout(location = 2) in vec2 texture;    //纹理S,T

out vec3 vertexColor;    //顶点颜色
out vec2 textureCoord;    //顶点对应纹理坐标

uniform mat4 transform;    //变换矩阵

void main()
{
    gl_Position = transform * vec4(position, 1.0);    //顶点坐标左乘变换矩阵
    vertexColor = color;                //从顶点数据那里得到的输入颜色
    textureCoord = texture;                //从顶点数据那里得到的对应纹理坐标
}

把变换矩阵传递给着色器

    //定义变换矩阵
    glm::mat4 trans = glm::mat4(1.0f);//定义4x4单位矩阵
    trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));//平移到右下角
    trans = glm::scale(trans, glm::vec3(0.5, 0.5, 1.0));//x,y轴缩小到一半
    trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));//绕着z轴旋转90度(参数为弧度值)
    unsigned int transformLoc = glGetUniformLocation(shader.getShaderID(), "transform");//获得着色器中变换矩阵位置
    glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));//赋值变换矩阵:因为是左乘变换矩阵,所以是先旋转再缩放再平移

注意变换顺序,因为着色器中顶点是左乘变换矩阵的,所以变换顺序是相反的(先旋转再缩放后平移)

示例效果

 

二、坐标系统

物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统,将物体的坐标变换到几个过渡坐标系的优点在于,在这些特定的坐标系统中,一些操作或运算更加方便和容易。

5个重要的坐标系统:

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

这就是一个顶点在最终被转化为片段之前需要经历的所有不同状态

  1. 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  2. 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  3. 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  4. 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。
  5. 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。这一阶段在顶点着色器运行的最后被自动执行。

第4步将观察坐标转换为裁剪坐标的方式称为投影,投影的形式有两种:正射投影(类似立方体的平截头箱)和透视投影(近大远小)。

从局部坐标到裁剪坐标的公式:

 

 三、OpenGL 3D

进行3D绘图时,首先在顶点着色器中进行坐标转换

//vertex shader source
#version 330 core
layout(location = 0) in vec3 position;     //位置X,Y,Z
layout(location = 1) in vec3 color;        //颜色R,G,B
layout(location = 2) in vec2 texture;    //纹理S,T

out vec3 vertexColor;    //顶点颜色
out vec2 textureCoord;    //顶点对应纹理坐标

uniform mat4 model;        //模型矩阵矩阵:局部空间->世界空间
uniform mat4 view;        //观察矩阵:世界空间->观察空间
uniform mat4 projection;//透视投射矩阵:观察空间->裁剪空间

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0);    //顶点坐标变换
    vertexColor = color;                //从顶点数据那里得到的输入颜色
    textureCoord = texture;                //从顶点数据那里得到的对应纹理坐标
}

然后定义变换矩阵传入着色器中

    ///定义变换矩阵
    //模型矩阵矩阵:局部空间->世界空间
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));//绕X轴旋转-55度
    unsigned int transModelLoc = glGetUniformLocation(shader.getShaderID(), "model");//获得变换矩阵位置
    glUniformMatrix4fv(transModelLoc, 1, GL_FALSE, glm::value_ptr(model));//赋值变换矩阵

    //观察矩阵:世界空间->观察空间
    glm::mat4 view = glm::mat4(1.0f);
    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));//向Z轴负方向移动3,相当于把物体往后移动
    unsigned int transViewLoc = glGetUniformLocation(shader.getShaderID(), "view");//获得变换矩阵位置
    glUniformMatrix4fv(transViewLoc, 1, GL_FALSE, glm::value_ptr(view));//赋值变换矩阵

    //透视投射矩阵:观察空间->裁剪空间
    glm::mat4 projection = glm::mat4(1.0f);
    projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);//第2个参数是:窗口宽度 / 窗口高度
    unsigned int transProjectionLoc = glGetUniformLocation(shader.getShaderID(), "projection");//获得变换矩阵位置
    glUniformMatrix4fv(transProjectionLoc, 1, GL_FALSE, glm::value_ptr(projection));//赋值变换矩阵

效果图