渲染世界的OpenGL<6>基础变换

这部分的知识涉及到GLTools库当中的一个组件:Math3d,包含大量好用的OpenGL一致的3D数学例程和数据类型。

1.3D图形数学

(1)向量
带箭头的线段可以视为一个向量。包含两种意义:1.方向 2.数量。对于数量的解释是向量的长度。
单位向量:长度为一的向量。
标准化:把非单位向量转化为单位向量的过程。长度缩放为 1。

//math3D当中的数据类型,可以表示三维或者四维向量。
typedef float M3DVector3f[3];
typedef float M3DVector4f[4];

注意第四个分量w,我们必须用一个四分量向量乘以一个4*4矩阵。

//点乘运算,只能够在两个向量之间运行。表示两个向量间的夹角。同时,两个向量必须为单位长度。而且返回的结果将会在-1~1之间。代表的是两个向量之间夹角的余弦值。
float m3dDotProduct3(const M3DVector3f u, const M3DVector3f v);
//为了方便,还有一个函数直接返回一个弧度制。
float m3dGetAngleBetweenVectors3(const M3DVector3f u,const M3DVector3f v );

//叉乘运算,两个向量之间叉乘所得到的结果是另外一个向量。这个新向量与原来的两个向量垂直。
void m3dCrossProduct3(M3DVector3f resault,const M3DVector3f u,const M3DVector3f v );


(2)矩阵
矩阵:功能非常强大的数学工具,简化了求解变量之间有复杂关系的方程组的过程。频繁用于坐标变换。
标量:一个普通的单独数据,用来表示大小和特定量。

//两种维度的矩阵数据类型
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];

2.理解变换
投影:将3D数据压扁成2D数据的处理过程为投影。
视图变换:指定观察者或照相机位置。
模型变换:在场景中移动物体。
模型视图变换:描述视图和模型变换的二元性。
投影变换:改变视景体的大小或者重新设置他的形状。
视口变换:一种伪变换,只是对窗口上的最终输出进行输出。
首先我们需要理解视觉坐标的概念:视觉坐标是相对于观察者而言的,无论可能进行何种变换,他们视为绝对的屏幕坐标。表示一个虚拟的固定坐标系。

(1)视图变换
用于确定场景当中的有利位置。默认情况下透视投影的观察点(0,0,0)并且沿着z轴的负方向进行观察。
在正投影中,观察者被认为是在z轴正方向无穷远点的位置。视图变换允许我们把观察点放在所希望的任何位置。并且允许在任何方向上观察对象。确定视图变换就在场景当中防止照相机并且让他指向某个方向。

(2)模型变换
场景或者对象的最终外观可能在很大程度上取决于应用的模型变换顺序。在平移和旋转上更是如此。每次变换都和最终的变换结果相关。

(3)模型视图的二元性
视图变换和模型变换一样,都应用于整个场景中,在场景中的对象常常在进行视图变换后单独进行模型变换。“模型视图”就是指这两种变换在渲染管线当中进行组合,成为一个单独的矩阵:模型视图矩阵。

(4)投影矩阵
投影变换在模型视图变换之后应用到顶点上。这种有赢实际上定义了视景体并且创建了剪裁平面。
剪裁平面是3D空间当中的平面方程式。投影变换制定了一个完整的场景是如何投影到屏幕上最终图像的。
在正投影中,所有多边形都是精确的按照指定的相对大小赖在屏幕上绘制的。线和多边形使用平行线来直接映射到2D屏幕上。作用:可以用于渲染二维图像。
透视投影:这种投影方式和现实生活更加接近,而不是一张蓝图。透视投影的特点是透视缩短,这种特性使得远距离的物体看起来比近处同样大小的物体更小一些。
注意:在渲染对象的深度与他们到视点的距离非常小的时候,我们也可以用正投影来完成3D渲染。透视投影应用在渲染包含广阔的空间或者需要应用透视缩短的物体的场景时候使用。

(5)视口变换
它将映射到屏幕上某处的窗口上。这种到物理窗口标的映射是我们最后要做的变换,称为视口变换。

3.模型视图矩阵
模型视图矩阵是一个44矩阵,表示一个变换后的坐标系,我们可以用来放置对象和确定对象的方向顶点数据实际上是4个元素。其中包含一个附加值W表示一个缩放因子。默认情况下这个值为1.0。

(1)矩阵构造
空间中任何位置和任意我们想要的方向都可以由一个44矩阵唯一确定。并且如果用一个对象的所有向量乘以这个矩阵。那么我们就将整个对象变换到了空间给定的位置和方向。

GLfloat matrix[16];//当这些数组元素一个接一个的遍历矩阵列中的列时,我们称为这种方式为列优先排序。
GLfloat matrix[4][4];//行优先排序
//以上两种形式是互为转置矩阵。
//注意每一列都代表由四个元素组成的向量。第四列向量为变换后坐标系原点的x,y,z值。前三列时前三个元素的方向向量,表示空间中x,y,z轴的方向。大多数向量互相之间总是成90度。并且通常为单位长度。这种情况的数学术语:标准正交(向量为单位向量)或者正交(非单位向量)。

单位矩阵
一个向量乘以一个单位矩阵,不会有任何改变。

void m3dLoadIdentity44(M3DMatrix44f m);

这个函数初始化一个空的单位矩阵。

综上:创建数组可以使用多种方法,利用GLfloat数组、使用M3D类当中特殊的数据结构,其次可以使用M3D类当中特殊的函数对其进行设置。

平移
一个平移矩阵仅仅是将我们的顶点沿着3个坐标轴中的一个或者多个进行平移。

void m3dTranslationMatrix44(M3DMatrix44f m,float x, float y, float z);

旋转
一个对象沿着3个坐标轴中的一个或者任意向量进行旋转。旋转的角度是沿着逆时针方向按照弧度计算的。由变量angle决定。

void m3dRotationMatrix44(M3DMatrix44f m,float angle, float x, float y, float z);

注意:

m3dDegToRad的使用,这个宏代替内联函数的一个好处,如果该值是一个硬编码,转换将会发生在编译的时候,这样角度和弧度之间的转换就不会产生运行时的损失。

缩放
math3d创建一个缩放矩阵与创建平移和旋转的方法一致。

void m3dScaleMatrix44(M3DMatrix44f m,float xScale, float yScale, float zScale);

综合变换
连接:表示两种变换以相乘形式结合。注意运算的顺序是有非常大影响的。

void m3dMatrixMultiply44(M3DMatrix44f m,M3DMatrix44f a, M3DMatrix44f b);

(2)运用模型视图矩阵

在之前的程序当中,我们使得一个红色方块随着按动方向键而在窗口中移动。这是一种强制性方法。
更好的解决方法:一次性创建批次(以原点为中心)然后在渲染这个批次时对顶点应用一个矩阵。(模型视图矩阵)在之前的程序中,我们使用了单位着色器,对顶点不做任何变换。而平面着色器,将变换矩阵作为其参数之一。

4.更多对象
GLTriangleBatch类专门作为三角形容器的。每个顶点都可以有一个表面法线。从而进行光照计算和纹理坐标。实际上,将多边形存储在图形卡,使用顶点缓冲区中。

(1)使用三角形批次类步骤

//为对象创建一个事件
GLTriangleBatch myCoolObject;
//通知容器使用的顶点数
myCoolObject.BeginMesh(200);
//添加三角形,AddTriangle成员函数接受一个包含3个顶点的数组,一个包含3个法线的数组,以及一个包含3个纹理坐标的数组。
void GLTriangleBatch::AddTriangle(M3DVector3f verts[3], M3DVector3f vNorms[3],M3DVector3f vTexCoord[3] );
//添加完三角形时
myCoolObject.End();
//最后调用draw
myCoolObject.Draw();

(2)创建多种不同的形体

球体

void glMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);
//iStacks是从球体底部堆叠到顶部的三角形带的数量。iSlices参数是围绕着球体排列的对数。
//正常情况下,一个球体的iSlices=2*iStacks

花托管状体

void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
//外边缘半径,内边缘半径,主半径和从半径的细分单元数量。

圆柱和圆锥

void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);
//圆柱体从0开始向z轴正向眼神。既可以指定底部半径,又可以指定顶部半径。numSlices是围绕z轴对三角形对的数量,numStacks代表从圆柱体底部堆叠到顶部圆环的数量。

圆盘

void gltMakeDisk(GLTriangleBatch& diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);

5.投影矩阵

使用不同坐标系的技巧是,将我们想要的坐标系变换到这个单位立方体当中。我们使用一个新的矩阵来完成这项工作,投影矩阵。
(1)正投影
正投影所有面都是正方形的。各个面的逻辑宽度都是相等的,这样就产生了一个平行投影,这种投影在绘制从远处观察不产生任何透视缩短的特定物体时非常有用。
注意之前涉及到的设置透视投影的函数,见基础渲染章节。

(2)透视投影
注意,视景体是一个从窄端看向宽端的金字塔中截取的一部分。
具体函数见基础渲染章节。

(3)模型视图投影矩阵
ModelviewProjection:模型视图投影。

6.变换管线
变换管线流程步骤简述:
首先一个顶点为一个1*4矩阵。该顶点乘以模型视图矩阵,生成变换的视觉坐标。随后视觉坐标再乘以投影矩阵,生成裁剪坐标。
裁剪坐标位于我们前面提到的,+-1坐标系内部。将有效地把所有位于这个裁剪空间之外的数据消除掉。裁剪坐标随后除以w坐标。生成规范化的设备坐标。

(1)使用矩阵堆栈
习惯上我们会使用一个矩阵堆栈来帮助我们完成工作。GLTools库则会在math3d矩阵函数顶部建立实用类。这个类成为GLMatrixStack。熟悉兼容版本中现在已经不推荐的OpenGL矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。

//这个类的构造函数允许指定堆栈的最大深度,默认为64。
GLMatrixStack::GLMatrixStack(int iStackDepth=64);
//可以通过调用在顶部载入单位矩阵
void GLMatrixStack::LoadIdentity(void);
//或者可以在堆栈的顶部载入任何矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
//此外我们可以用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘得到的矩阵结果随后存储在堆栈的顶部。
void GLMatrixStack::MultiMatrix(const M3DMatrix44f );
//GetMatrix函数可以获得矩阵堆栈顶部的值。
const M3DMatrix44f& GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);

压栈和出栈
一个矩阵的真正价值在于压栈操作存储一个状态,然后通过出栈操作恢复这个状态。可以使用压栈函数将矩阵压入堆栈来存储当前矩阵值。实际上是复制当前矩阵并且将新的值放入了堆栈的顶部。类似的,弹栈会恢复他下面的值。

void GLMatrixStack::PushMatrix();
void GLMatrixStack::PushMatrix(const M3DMatrix44f mMatrix);
void GLMatrixStack::PushMatrix(GLFrame& frame);

void GLMatrixStack::PopMatrix(void);

仿射变换
以下三个函数都可以创建恰当的矩阵,然后用这个矩阵乘以矩阵堆栈顶部的元素。实际上是对当前矩阵添加变换。

void MatrixStack::Rotate(GLfloat angle, GLfloat x,GLfloat y, GLfloat z);
void MatrixStack::Translate(Glfloat x,GLlfoat y,GLfloat z);
void MatrixStack::Scale(GLfloat x, GLfloat y,GLfloat z);

(2)管理管线
正规矩阵:用来进行光照计算,并且可以从模型视图矩阵推导出来。
实用类GLGeometryTransform为我们跟踪记录两种矩阵堆栈,并且快速的检索模型视图矩阵的顶部或者正规矩阵堆栈的顶部。几何变换管线。使用方法在之后的代码分析当中。

7.使用照相机和角色进行移动
固定的物体通常不进行变换,他们的顶点通常准确地指定几何图形在空间中应该怎样进行绘制。
移动的物体通常称为角色。
角色有自己的变换,角色变换不仅仅与全局坐标系有关,和其他角色有关,每个有自己的变换角色都被称为有自己的参考帧,本地对象坐标系。在本地和全局坐标系当中进行转换常常非常有用。

(1)角色帧
参考帧类包含空间中的一个位置、一个指向前方的向量,以及一个指向上方的向量。根据两个向量可以叉乘计算得到另外一个方向的向量。

class GLFrame
{
protected:
M3DVector3f vLocation;
M3DVector3f vUp;
M3DVector3f vForward;

};

//从一个GLFrame到一个矩阵的变换~
void GLFrameText::GetMatrix(M3DMatrix44f mMatrix, bool bRotationOnly = false)
{
    M3DVector3f vXAxis;
    m3dCrossProduct3(vXAxis, vUp, vForward);

    m3dSetMatrixColumn44(mMatrix, vXAxis, 0);
    mMatrix[3] = 0.0f;

    m3dSetMatrixColumn44(mMatrix, vUp, 1);
    mMatrix[7] = 0.0f;

    m3dSetMatrixColumn44(mMatrix, vForward, 2);
    mMatrix[11] = 0.0f;

    if (bRotationOnly == true)
    {
        mMatrix[12] = 0.0f;
        mMatrix[13] = 0.0f;
        mMatrix[14] = 0.0f;
    }
    else
        m3dSetMatrixColumn44(mMatrix, vLocation, 3);

    mMatrix[15] = 1.0f;
}

(2)欧拉角

注意欧拉角,更加简单的机制来存储一个对象的位置和方向。

struct EULER
{
M3DVector3f vPosition;
GLfloat fRoll;//z
GLfloat fPitch;//y
GLfloat fYaw;//x
};

欧拉角所需要的存储空间更少,只需要存储一个物体的位置以及三个角度就可以。

(3)照相机管理

为了应用照相机变换,我们使用照相机的角色变换并且对它进行反转,这样向后移动照相机就相当于向前移动整个场景。类似的,向左旋转,相当于把整个场景向右旋转。
具体步骤:
保存单位矩阵
应用照相机变换
绘制不会移动的物体
绘制移动的物体
应用照相机变换
应用角色变换
绘制角色几何图形
恢复照相机变换
恢复单位矩阵
注意:上面所说的顺序,无限循环。
类当中包含一个GLFrame函数,这个函数用来检索条件适合的照相机矩阵。

void GetCameraMatrix(M3DMatrix44f m,bool bRotationOnly=false);
//这里只能够获得照相机的旋转变换。C++默认参数允许忽略这一点。

(4)添加更多角色
我们会有很多物体分散在各处,我们要分别在空间中管理每个对象的位置和方向。GLFrame类为这些位置和方向提供了一个非常好的容器。

(5)光线研究
对于几何图形变换来说,典型情况下我们会设置变换矩阵,将他们传递到着色器,然后让硬件完成所有的顶点变换。
光源位置也需要转换到视觉坐标系。整个世界是相对于照相机移动的。

在上述理论研究之后,我会对涉及上述知识的代码详细分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值