s5.GLSL学习之坐标系统变换

本文通过类比相机拍摄过程,详细介绍了OpenGL中的视图变换原理。包括模型变换、视图变换、投影变换等内容,并提供了代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前在OpenGL学习之三维观察中,介绍过视图变换。不过,《OpenGL编程指南(原书第8版)》这本书第五章用相机拍摄来类比
视图变换操作比较形象,这里就做个笔记。

相机模型

   常见的视图变换操作可以类比为使用照相机拍摄照片过程,如图所示,使用相机的主要步骤列举如下:
这里写图片描述

  1. 将相机移动到准备拍摄的位置,将它对准某个方向(视图变换,view transform)。
  2. 将准备拍摄的对象移动到场景中必要的位置上(模型变换, model transform)。
  3. 设置相机焦距,或调整缩放比例(投影变换, projection transform)。
  4. 拍摄照片(应用变换结果)。
  5. 对结果图形进行拉伸或挤压,将它变换到需要的图片大小(视口变换, viewport transform)。对于3D图形来说,这里同样需要对深度信息进行拉伸或挤压(深度范围的缩放)。

注意,我们也可以认为上面的第一步和第二步做的是同一件事情,只不过方向相反而已。我们可以把相机固定在原地,然后把感兴趣的物品拿过来拍摄,或者也可以保持物体的位置不动,然后把相机移动过来。将相机移到左边的操作相当于将物体移动到右边。而沿着顺时针转动相机也相当于沿着逆时针移动物体。因此第一步和第二步各自有多少操作,这完全是由用户自己决定的。正因为如此,通常可以将这两步合并为一个模型-视图变换(model-view transform)。
  OpenGL当中,我们可以直接在着色器中完成上面的第一步到第三步。也就是说,我们传递给OpenGL的坐标应该是已经完成模型视图变换和投影变换的。我们还需要告诉OpenGL如何完成第五步所述的视口变换,当然固定渲染管线中会自动完成这个变换过程。
下图总结了OpenGL的整个处理过程中所用到的坐标系统。
这里写图片描述
  OpenGL得到的最终坐标是归一化之后的其齐次坐标,并且将进行剪切和光栅化的操作。也就是说,最后要绘制的坐标总是在[-1.0, 1.0]的范围内,直到OpenGL对它们进行缩放以匹配视口大小为止。
这里写图片描述

视椎体

  我们操作相机的第三步当中设置了焦距或者缩放的数值。这样其实是在相机拍摄场景的时候,设置取景用的矩形椎体的宽度或窄度。只有落在这个椎体内的几何体才会出现在最终图像中。同时,第三步还计算用于透视投影的“近大远小”效果的相关参数(齐次坐标的第四个坐标值)。
  OpenGL还可以去除过近或是过远的几何体,也就是比近平面更近的,或者在远平面之后的几何体。从相机拍摄的角度来说,并不存在这样一种特性(不过相机焦距之内的物体是可以被清除的),但是这个特性在很多时候是非常有用的。最重要的是,如果一个物体非常靠近视景椎体的顶端点的话,那么它的大小是无限大的,这样会带来很多问题,尤其是物体位于顶端点的时候。而从另一个角度来说,场景中距离观察者非常远的物体最好也不要绘制,这样可以改善渲染的性能以及深度精度,因为深度值不需要再涵盖一个非常大的问题。
  因此,我们需要再增加两个平面,并且与已有的视景椎体的四个平面相交。如下图所示,这个六个平面将会定义出一个平截锥体(frustum)形状的观察范围。
这里写图片描述

OpenGL变换

  如果我们需要通知OpenGL在何处放置近平面和远平面,则可以使用glDepthRange()函数。

void glDepthRange(GLclampd near, GLclampd far);
void glDepthRangef(GLclampd near, GLclampd far);
设置z轴上的近平面位于near,而远平面位于far。这个函数定义了视口变换过程中z坐标的变化范围。近平面和远平面的值也就是深度缓
存中所保存的最小值和最大值。默认情况下它们分别是0.01.0,这对于大部分应用程序都是适用的。这个函数的参数设置范围必须是
[0, 1]之间的数值。

 我们可以使用下面函数来通知OpenGL,在指定的矩形观察区域内显示数据:

void glViewport(GLint x, GLint y, GLint width, GLint height);
在程序窗口中定义一个矩形的像素区域,并且将最终渲染的图像映射到其中。这里的x和y参数设置了视口(viewport)的左下角坐标,
而widthheight设置了视口矩形的像素大小。默认情况下,视口初始值设置为(0 ,0, winWidth, winHeight),其中winWidth
和winHeight为窗口的像素尺寸。

示例演示

  我们在上一篇的基础上实现鼠标旋转立方体来说明如何使用视图模型变换。这里我们用到《OpenGL编程指南(原书第8版)》中的vmath.h头文件来方便生产变换矩阵。
  首先,我们先看下在处理鼠标移动事件时如何更新视图模型变换矩阵,代码如下:

void MyQGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    GLfloat dx = GLfloat(event->x() - lastPos.x()) / width();
    GLfloat dy = GLfloat(event->y() - lastPos.y()) / height();
    if (event->buttons() & Qt::LeftButton)
    {
        rotateAngleX_ += 180 * dy;
        rotateAngleY_ += 180 * dx;
        ModelViewMatrix = vmath::rotate(rotateAngleX_, 1.0f, 0.0f, 0.0f) *
                          vmath::rotate(rotateAngleY_, 0.0f, 1.0f, 0.0f) *
                          vmath::rotate(rotateAngleZ_, 0.0f, 0.0f, 1.0f);
        update();
    }
    else if (event->buttons() & Qt::RightButton)
    {
        rotateAngleX_ += 180 * dy;
        rotateAngleZ_ += 180 * dx;
        ModelViewMatrix = vmath::rotate(rotateAngleX_, 1.0f, 0.0f, 0.0f) *
                          vmath::rotate(rotateAngleY_, 0.0f, 1.0f, 0.0f) *
                          vmath::rotate(rotateAngleZ_, 0.0f, 0.0f, 1.0f);
        update();
    }
    lastPos = event->pos();
}
然后我们在顶点着色器中定义视图模型矩阵ModelViewMatrix,代码如下:
const char*  vertexShaderCode =
        "#version 430 \n"
        ""
        "layout(location = 0) in vec3 vPosition;"
        "layout(location = 1) in vec3 color;"
        "uniform mat4 ModelViewMatrix;"
        "out vec3 myColor;"
        ""
        "void main()"
        "{"
        "   gl_Position = ModelViewMatrix * vec4(vPosition, 1.0);"
        "   myColor = color;"
        "}";

  最后我们要使用glUniformMatrix4fv将应用程序中的矩阵和顶点着色器中矩阵mat4关联起来,部分代码如下:

ModelViewMatrixID = glGetUniformLocation(programID, "ModelViewMatrix");
    ModelViewMatrix = vmath::rotate(rotateAngleX_, 1.0f, 0.0f, 0.0f) *
                      vmath::rotate(rotateAngleY_, 0.0f, 1.0f, 0.0f) *
                      vmath::rotate(rotateAngleZ_, 0.0f, 0.0f, 1.0f);
    glUniformMatrix4fv(ModelViewMatrixID, 1, GL_FALSE, ModelViewMatrix);

运行结果:

这里写图片描述

着色语言语法

  GLSL是一种强类型语言,所有变量都必须事先声明,并且要给出变量的类型。变量名称的命名规范与C语言相同:可以使用字母、数字,以及下划线字符(_)来组成变量的名字。但是数字或下划线不能作为变量名称的第一个字符。下表给出了GLSL支持的基本数据类型:
这里写图片描述

  这些类型都是透明的,也就说,它们的内部形式都是暴露出来的,因此着色器代码中可以假设其内部的构成形式。与之对应的一部分类型,称作不透明类型,它们的内部形式没有暴露出来,这些类型包括采样器(sampler)、图像(image)以及原子计算器(atomic counter),以后详解。
  本文用到了mat4 ,这种聚合类型。GLSL的基本类型可以进行合并,从而与核心OpenGL的数据类型相匹配,以及简化计算过程的操作。GLSL支持两个,三个以及四个分量的向量,每个分量都可以用使用bool、int、uint、float和double这些基本 类型。GLSL也支持float和double类型的矩阵。如下表:
这里写图片描述

这里强调矩阵的指定需要遵循列主序的原则,也就说说,传入的数据将首先填充列,然后填充行。例如:

mat3 M =  mat3(1.0, 2.0, 3.0,
        4.0, 5.0, 6.0,
        7.0, 8.0, 9.0);

这矩阵M的数学表达形式如下:
这里写图片描述

代码下载
OpenGL学习系列导航

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值