OpenGL

006---OpenGL基础变化

2020-07-18  本文已影响0人  清风烈酒2157

[toc]

image.png
image.png

OpenGL 矩阵向量

向量

一个顶点同时也是一个向量.

  1. 方向
  2. 数量

长度或模为1的 当前这个顶点为 单位向量

math3d库,有2个数据类型,能够表示⼀一个三维或者四维向量量。M3DVector3f可以表示⼀一个三 维向量量(x,y,z),⽽而M3DVector4f则可以表示⼀一个四维向量量(x,y,z,w).在典型情况下,w坐标设为1.0x,y,z值通过除以w,来进⾏行行缩放。⽽而除以1.0则本质上不不改变x,y,z值.


typedef float M3DVector3f[3];
typedef float M3DVector4f[4];

//声明⼀一个三分量量向量量操作:
M3DVector3f vVector;

//类似,声明⼀一个四分量量的操作:
M3DVector4f vVectro= {0.0f,0.0f,1.0f,1.0f};

//声明⼀一个三分量量顶点数组,例例如⽣生成⼀一个三⻆角形
M3DVector3f vVerts[] = {
};

-0.5f,0.0f,0.0f, 0.5f,0.0f,0.0f, 0.0f,0.5f,0.0f


如图:

image.png

点乘

向量可以进行加减运算,也可以简单的通过加减法进行缩放.
点乘这个操作是在两个单位向量之前进行.

返回的是-1,1之间的值。它代表这个2个向量量的余弦值。


float m3dDotProduct3(const M3DVector3f u,const
M3DVector3f v);

返回2个向量量之间的弧度值。


float m3dGetAngleBetweenVector3(const M3DVector3f
u,const M3DVector3f v);

如图:

image.png

叉乘

两个向量之前的叉乘结果是另外一个向量.垂直于两个向量.

V1V2叉乘得到V3,如果V1V2调换顺序,V3将指向与原来相反的方向.


void m3dCrossProduct3(M3DVector3f result,const M3DVector3f
u ,const M3DVector3f v);

如图:

image.png

矩阵

矩阵(matrix),在数学上,矩阵只不过是一组排列在统一的行列中的数字而已.

矩阵之前可以进行乘法和加法,也可以与向量和标量相乘.用一个点(向量)乘以一个矩阵(一次变换)结果得到一个新的变换点(向量);

math3d库中也有这两种维度的矩阵数据.

image.png

许多矩阵都定义了一个二维矩阵作为c语言的二维数组.

OpenGL种通用使用一位数组.因为OpenGL使用一种column-Major(一列为主)矩阵排序的矩阵约定.


typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];

如图:

image.png

`

理解变换

3D数据被压扁2D数据的处理过程叫做投影;

在指定顶点和这些顶点出现在屏幕上直之间这段时间,可能会发生3种类型的几何变换:视图变换,模型变换和投影变换.

视图,模型,模型视图,投影,视口.

变换 应用
视图 指定观察者或相机位置
模型 在场景中移动物体
模型视图 描述视图和模型变换的二元性
投影 改变视景体的大小或重新设置它的形状
视口 这种一种伪变换,只是对窗口上最终输出进行缩放

如图:

视觉坐标

视觉坐标是相对于观察者的视角而言,无论可能进行何种变换,我们都可以将它们视为绝对的屏幕坐标.

视觉坐标表示一个虚拟的固定坐标系,它通常作为参考系使用.

如图:

image.png

a:观察者在z轴方向

视图变换

视图变换是应用到场景中的第一变换,它用来确定场景中的有利位置.

在默认情况下:

视图变换运行我们把观察者放在所希望的位置,并运行在任何方向上观察场景.确定视图变换就像在场景中放置照相机让它指向某个方向.

全局考虑,在应用任何其他模型变换之前,必须先应用视图变换.

模型变换

模型变换用于操作模型和其他特定对象.这些变换将对对象移动到需要的位置,然后再对它们进行旋转和缩放.

image.png

场景或对象的最终外观很大程度上取决于应用模型变换顺序

image.png image.png

模型视图的二元性

实际上,视图和模型变换按照他们的内部效果和场景的最终外观来说是一样的.两者分开纯粹为了程序员方便.

将对象后移动和将参考坐标系向前移动在视觉上没有区别.

如图:

image.png

投影变换

投影变换将在模型视图变换之后应用到顶点上.这种投影实际上定义视景并创建裁剪平面.

在正投影中,线和多边形使用平行线直接映射到2D屏幕上,这就意味着,无论物体有多远,他都会按照同样大小进行绘制,仅仅是平贴屏幕上.

透视投影中,知道模型视图变换场景,然后应用到透视投影矩阵.

image.png

视口变换

当以上都完成之后,得到一个场景的二维投影,它将被映射到屏幕上某处的窗口,这种到物理窗口表的映射就是最后的变换,视口变换.

总结

我们生活在一个三维的世界——如果要观察一个物体,我们可以:

模型视图矩阵

模型视图矩阵是一个 4x4 矩阵,它表示一个变换后的坐标系,我们可以用放置对象确定对象的方向.我们为图元提供一个顶点将作为一个单列矩阵(也就是一个向量)的形式来使用.并乘以一个模型视图矩阵来获得一个相对于视觉坐标系的经过变换的新坐标.

image.png

矩阵构造

OpenGL矩阵通常是一个由16个顶点组成的单个数组.

image.png

第二种也可以,但是第一种是一种更加有效地方式.

image.png

行优先矩阵和列优先矩阵互为 转置矩阵

奥秘之处,在于这 16 个值表示空间中⼀一个特定的位置; 这4列列中,每⼀一列列都是有4个元素组成的向量量;

image.png
单位矩阵

将一个向量乘以一个单位矩阵,相当于乘以1,不会发生任何改变.

image.png image.png
平移

将顶点沿着3个坐标轴中的一个或多个进行平移.

image.png

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

旋转

我们围绕有x,y,z变量指定来进行旋转.旋转角度沿逆时针方向按照弧度计算.

image.png
m3dRotationMatrix44(m3dDegToRad(45.0), floata x, float y, float z);

缩放

缩放矩阵沿着3个坐标轴的方向按照指定因子放大或缩小.

image.png

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

模型矩阵
视图矩阵
综合变换

将两个变换加在一起,只需将矩阵相乘,


void m3dMatrixMultiply44(M3DMatrix44f product, const M3DMatrix44f a, const M3DMatrix44f b);

运用模型视图矩阵


void RenderScene(void)
{

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    
    M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;
    
    //平移 xPos,yPos
    m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);
    
    
    // 每次重绘时,旋转5度
    static float yRot = 0.0f;
    yRot += 5.0f;
    m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);
    
    //将旋转和移动的结果合并到mFinalTransform 中
    m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);
    
    //将矩阵结果提交到固定着色器(平面着色器)中。
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
    squareBatch.Draw();
    
    // 执行缓冲区交换
    glutSwapBuffers();
}

  1. 创建一个平移矩阵 mTranslationMatrix
  2. 创建一个旋转矩阵 mRotationMatrix
  3. 把平移矩阵和选择矩阵相乘得到mFinalTransform
  4. 传入平面着色器中

平面着色器只接受一个矩阵变量,然后他会利用这些顶点乘以这个矩阵.

更多对象

一个批次类容器代表一种图形.


GLTriangleBatch     CC_Triangle;


CC_Triangle.BeginMesh(200);

  1. verts:顶点数
  2. vNorms3个法线数组
  3. vTexCoords三个纹理数组

sphereBatch.AddTriangle(<#M3DVector3f *verts#>, <#M3DVector3f *vNorms#>, <#M3DVector2f *vTexCoords#>)

不用担心重复的顶点数据,GLTriangleBatch会帮我们优化.


CC_Triangle.end();


CC_Triangle.Draw();

创建一个球体

  1. sphereBatch:三角形批次类对象
  2. fRadius:球体半径
  3. iSlices:从球体底部堆叠到顶部的三角形带的数量;其实球体是一圈一圈三角形带组成
  4. iStacks:围绕球体一圈排列的三角形对数

void gltMakeSphere(GLTriangleBatch& sphereBatch, GLfloat fRadius, GLint iSlices, GLint iStacks);

image.png

创建一个花托

  1. torusBatch,三角形批次类对象
  2. majorRadius,甜甜圈中心到外边缘的半径
  3. minorRadius,甜甜圈中心到内边缘的半径
  4. numMajor,沿着主半径的三角形数量
  5. numMinor,沿着内部较小半径的三角形数量

void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);

image.png

创建一个圆柱或圆锥

  1. cylinderBatch,三角形批次类对象
  2. baseRadius,底部半径
  3. topRadius,头部半径
  4. fLength,圆形长度
  5. numSlices,围绕Z轴的三角形对的数量
  6. numStacks,圆柱底部堆叠到顶部圆环的三角形数量

void gltMakeCylinder(GLTriangleBatch& cylinderBatch, GLfloat baseRadius, GLfloat topRadius, GLfloat fLength, GLint numSlices, GLint numStacks);

image.png image.png image.png

创建一个圆盘

  1. diskBatch,三角形批次类对象
  2. innerRadius,内圆半径
  3. outerRadius,外圆半径
  4. nSlices,圆盘围绕Z轴的三角形对的数量
  5. nStacks,圆盘外网到内围的三角形数量

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

image.png

投影矩阵

** 模型视图投影矩阵实际上是在视觉坐标系中移动图形.**

正投影

2D


void SetOrthographic(GLfloat xMin, GLfloat xMax, GLfloat yMin, GLfloat yMax, GLfloat zMin, GLfloat zMax)

透视投影

3D = 2D + 透视

 viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);

模型视图投影矩阵

ModelviewProjection(模型视图投影矩阵)

ModelviewProjection = 模型视图矩阵 * 投影矩阵

在不使用管线的情况下

  1. GLFrustum构造投影矩阵

  2. mTranslate: 平移,mRotate: 旋转, mModelview: 模型视图
    mModelViewProjection: 模型视图投影MVP

  3. 构造旋转矩阵

 m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);
  1. 构造旋转矩阵

m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);

  1. 通过矩阵旋转矩阵、移动矩阵相乘,将结果添加到mModerView上
m3dMatrixMultiply44(mModelview, mTranslate, mRotate);

  1. 将投影矩阵乘以模型视图矩阵,将变化结果通过矩阵乘法应用到mModelViewProjection矩阵上

注意顺序: 投影 * 模型 != 模型 * 投影


 m3dMatrixMultiply44(mModelViewProjection, viewFrustum.GetProjectionMatrix(),mModelview);

7.完整代码:

void RenderScene(void)
{
    //清除屏幕、深度缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //1.建立基于时间变化的动画
    static CStopWatch rotTimer;
    //当前时间 * 60s
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    
    //2.矩阵变量
    /*
     mTranslate: 平移
     
     mRotate: 旋转
     mModelview: 模型视图
     mModelViewProjection: 模型视图投影MVP
     */
    M3DMatrix44f mTranslate, mRotate, mModelview, mModelViewProjection;
    
    //创建一个4*4矩阵变量,将花托沿着Z轴负方向移动2.5个单位长度
    m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);
    
    //创建一个4*4矩阵变量,将花托在Y轴上渲染yRot度,yRot根据经过时间设置动画帧率
     m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);
    
    //为mModerView 通过矩阵旋转矩阵、移动矩阵相乘,将结果添加到mModerView上
    m3dMatrixMultiply44(mModelview, mTranslate, mRotate);
    
    // 将投影矩阵乘以模型视图矩阵,将变化结果通过矩阵乘法应用到mModelViewProjection矩阵上
    //注意顺序: 投影 * 模型 != 模型 * 投影
     m3dMatrixMultiply44(mModelViewProjection, viewFrustum.GetProjectionMatrix(),mModelview);
  
    //绘图颜色
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    
    //通过平面着色器提交矩阵,和颜色。
    shaderManager.UseStockShader(GLT_SHADER_FLAT, mModelViewProjection, vBlack);
    //开始绘图
    torusBatch.Draw();
    
    
    // 交换缓冲区,并立即刷新
    glutSwapBuffers();
    glutPostRedisplay();
}

变换管线

image.png
  1. 首先初始化顶点数据,然后顶点数据乘以模型视图矩阵,生成变化的视觉坐标,视觉坐标就是经过一系列变换后得到的坐标

  2. 然后视觉坐标乘以投影矩阵会生成剪裁坐标,剪裁坐标会将非显示数据踢掉,并且转换到单元立方体坐标中

  3. 随后剪裁坐标通过透视除法也就是除以w坐标会转换成设备坐标,w除以坐标的意义在于我们看到渲染物体的深度,对上面的剪裁坐标的点的x、y、z坐标除以它的w分量,除以w的坐标叫做归一化设备坐标。如果w分量大,除以w后的点就接近(0,0,0)

  4. 而在三维空间中,距离我们较远的坐标如果它的w分量较大,进行透视除法后,就距离原点越近,原点作为远处物体的消失点 反之亦然,就有三维场景的效果。

  5. 最后将透视得到的三元坐标经过视口变换就映射到2d屏幕上,我们就可以看到渲染之后的效果了

使用矩阵堆栈

这个类的构造函数允许我们制定堆栈的最大的深度,默认堆栈深度为64.在初始化已经包含了单位矩阵 .


GLMatrixStack(int iStackDepth = 64) {
            stackDepth = iStackDepth;
            pStack = new M3DMatrix44f[iStackDepth];
            stackPointer = 0;
            m3dLoadIdentity44(pStack[0]);
            lastError = GLT_STACK_NOERROR;
            }


void LoadIdentity(void);


void LoadMatrix(const M3DMatrix44f mMatrix)


void MultMatrix(const M3DMatrix44f mMatrix)


void GetMatrix(M3DMatrix44f matrix, bool bRotationOnly = false)

压栈和出栈

一个矩阵的真正价值在于通过压栈操作存储一个状态,然后通过出栈恢复这个状态.


void PushMatrix(void)

void PushMatrix(const M3DMatrix44f mMatrix)

内部转为矩阵后压栈

void PushMatrix(GLFrame& frame) {
            M3DMatrix44f m;
            frame.GetMatrix(m);
            PushMatrix(m);
      }

仿射变化

GLMatrixStack类也内建了对床架旋转,平移和缩放矩阵的支持.


void Translate(GLfloat x, GLfloat y, GLfloat z) {
            M3DMatrix44f mTemp, mScale;
            m3dTranslationMatrix44(mScale, x, y, z);
            m3dCopyMatrix44(mTemp, pStack[stackPointer]);
            m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);           
            }

  1. 拿到平移矩阵
  2. 把栈顶部copy一份
  3. 平移矩阵和单元矩阵相乘放在栈顶

void Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) {
            M3DMatrix44f mTemp, mRotate;
            m3dRotationMatrix44(mRotate, float(m3dDegToRad(angle)), x, y, z);
            m3dCopyMatrix44(mTemp, pStack[stackPointer]);
            m3dMatrixMultiply44(pStack[stackPointer], mTemp, mRotate);
            }


void Scalev(const M3DVector3f vScale) {
            M3DMatrix44f mTemp, mScale;
            m3dScaleMatrix44(mScale, vScale);
            m3dCopyMatrix44(mTemp, pStack[stackPointer]);
            m3dMatrixMultiply44(pStack[stackPointer], mTemp, mScale);
            }

管理管线

GLGeometryTransform跟踪记录着模型视图矩阵和投影矩阵堆栈,并快速检索模型视图投影矩阵的顶部和正规矩阵堆栈的顶部.

GLShaderManager shaderManager; //着色管理类
GLMatrixStack modelViewMatrix;//模型视图矩阵
GLMatrixStack projectionMatrix; //投影矩阵
GLFrame cameraFrame; //观察者位置
GLFrame objectFrame;//世界坐标
GLFrustum viewFrustum; //投影体
GLTriangleBatch CC_Triangle; // 批次类
GLGeometryTransform transformPipeline; //几何变换关系

  1. 模型视图矩阵加一个单元矩阵(可以忽略这一步)
  2. 初始化 GLGeometryTransform,管道管理模型视图矩阵堆栈 和 投影矩阵堆栈

viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
  projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
 modelViewMatrix.LoadIdentity();
 transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);


PopMatrix()

使用相机和角色进行移动

在3D场景中表示任意对象位置和方向,可以使用4x4矩阵,但是这样有点笨拙,所以有了一种更简洁的方式表示空间的坐标和方向.

角色有自己的变换,角色的变换不仅和全局坐标系(视觉坐标系)有关,也与其他角色有关,每个有自己的变换决战都贝晨伟自己的参考帧.或者本地对象坐标系.

角色帧

角色帧也叫做观察者,通常移动的物体称为角色,只有角色才会有自己的变化,所以其实GLFrame是拿来做变化用的。可以用来产生模型视图矩阵。来产生位置的移动

GLframe可以用来表示一个对象相对于坐标系的位置方向

无论是相机还是模型,都可以使用GLFrame来表示。对任意一个使用GLFrame来表示的物体而言,涉及到的坐标系有两个:永远不变的世界坐标系,针对于自身的物体坐标系(即绘图坐标系)。


class GLFrame
    {
    protected:
        M3DVector3f vOrigin;    // Where am I?
        M3DVector3f vForward;   // Where am I going?
        M3DVector3f vUp;        // Which way is up?

    public:
    
    ...
   }

  1. vOrigin空间中的位置
  2. vForward指向前方的向量
  3. M3DVector3f指向上方的向量

照相机管理

照相机变换这种方式在OprnGL 中其实是不存在的,只是我们为了形象的形容这种变换。如果给定照相机在坐标系中的一个位置和方向,当我们向前移动照相机就相当于整个场景向后退一样
照相机也是角色帧的一种,这里是更形象的定义,就好比之前说的视图变换与模型变换。这样做的好处就是可以更方便操作矩阵变换。


GLFrame             cameraFrame;

表示离屏幕之间的距离 负数,是往屏幕后面移动;正数,往屏幕前面移动


cameraFrame.MoveForward(-15.0f);


 modelViewMatrix.PushMatrix();
    
    //3.获取摄像头矩阵
    M3DMatrix44f mCamera;
    //从camereaFrame中获取矩阵到mCamera
    cameraFrame.GetCameraMatrix(mCamera);
    //模型视图堆栈的 矩阵与mCamera矩阵 相乘之后,存储到modelViewMatrix矩阵堆栈中
    modelViewMatrix.MultMatrix(mCamera);


void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP)
        cameraFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        cameraFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        cameraFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        cameraFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
}

只有方向键按需,机会调用SpecialKeys函数,在照相机对象cameraFrame上调用GLFrame类成员函数,向上向下向左向右移动.

上一篇 下一篇

猜你喜欢

热点阅读