本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。
全文除大片代码外都是手打无复制,如果出现代码的细节错误,敬请谅解,如果可以提醒一下,感激不尽
在之前的学习中,经常出现MatrixStack,本节主要学习一下之前的变换都是怎么发生的。
一、理解变化和矩阵的关系
之前的章节中出现了物体的平移/旋转/投影等操作(包括缩放,只是没写),比如移动四边形,旋转圆环等等。在第一次绘制四边形的时候,我们的做法是选出一个顶点为相对移动点,根据矩形的四个顶点关系,算出相对点的坐标,也就可以算出其他三个点的坐标,使图形发生移动,但是如果顶点的数量非常多的话,那么这种计算的时间成本是很高昂的。而在圆环绘制的章节中,学习到了一种方法,可以解决顶点多的图形发生仿射变化需要耗费的时间长的问题,就是旋转坐标系,不旋转物体。
上述的一系列的变化,其实都是放射变化和矩阵的关系,根本上都是由于使用了某种变化矩阵,使得图形发生了改变。而关于矩阵,这里就要用到大学时候的数学知识,本节不过多赘述,只提一下本节要用的地方。
1.简述关于OpenGL中的数学
OpenGL中已经提供了math3d的库,就在GLTools中,我们主要是理解一下,在向量,矩阵和变化之间是怎样的一个逻辑。
在OpenGL中,我们经常要使用到(x,y,z)坐标来描述一些信息,这三个值其实可以表示图形的位置,而这个位置又带有两个信息,一个是方向,一个是数量。这就和数学中的向量有了交集。
规范化
在开发的时候,我们会经常的听到一个词叫规范化,比如经常听到的把一个矩阵向量做规范化或者标准化处理。规范化或者标准化就是把矩阵或者向量转换为单位矩阵或者单位矩阵。这个很简单理解,比如x轴上的单位向量就是(1,0,0),单位矩阵就是如(1.1)式所示:
而这个矩阵,我们一般可以用一个一维数组来进行存储:float arr[] = {1,0,0,0,1,0,0,0,1};
就可以。
这个单位向量和单位矩阵的用处就是比如我们让图形向某一个方向移动几个单位,就是以单位向量或者单位矩阵为基本单位。
OpenGL中的math3d库
之前在RenderScene中见到过的,M3DVector3f
和M3DVector4f
,这两个前者表示的是定义一个三位向量(x,y,z),后者则是定义一个思维向量(x,y,z,w),在典型情况下呢,w一般设置为1.0,而x、y、z通过除以w来进行缩放。这两个类型其实都是typedef float
的一维数组。比如声明一个三维向量和一个四维向量:
M3DVector3f aVector = {1,2,3};
M3DVector4f bVector = {1,2,3,4};
math3d中的向量/矩阵的点乘和叉乘
点乘:
参数都是两个向量/矩阵,一前一后,就不解释了。
方法1:
返回的是两个向量之间余弦值,是[-1,1]之间的值
float m3dDotProduct3(const M3DVector3f u , const M3DVector3f v)
方法2:
返回的是两个向量之间的弧度值
float m3dGetAngleBetweenVector3(const M3DVector3f u , const M3DVector3f v)
叉乘:
第一个参数是返回的结果要存储到哪个变量里面。后面的两个就是那两个向量/矩阵
void m3dCrossProduct3(M3DVector3f result , const M3DVector3f u , const M3DVector3f v)
矩阵类型:
也是typedef
的。
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
2.理解变化
理解一下之前用到的一些变换,如下图1.2所示:
二、矩阵变换的实际操作
先来代码,然后通过一个简单的代码来学习。
就画一个移动的矩形,但是这次用的是变换矩阵,而不是算数。
//
// main.cpp
// 06矩阵变换图形移动
//
// Created by EasonLi on 2020/8/29.
// Copyright © 2020 EasonLi. All rights reserved.
//
#include <stdio.h>
#pragma mark - 引用类
//包括了管理和创建着色器管理器的方法,还提供了一组存储着色器,可用于一些初级操作。
#include "GLShaderManager.h"
//GLTools工具类。里面包含了大多数GLTool类似于C语言的类、三角形批次类和设置工作区间
#include "GLTools.h"
//math3d数学库,有本节要使用到的,关于矩阵进行仿射变换需要用到的方法
#include "math3d.h"
//根据不同的系统引入不同的GLUT。
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
#pragma mark - 公共变量
//着色器管理器
GLShaderManager shaderManager;
//三角形批次类容器
GLBatch squareBatch;
//矩形边长的一半
GLfloat blockSize = 0.2f;
//每次移动的距离
GLfloat stepSize = 0.025f;
//矩形顶点
GLfloat vVerts[] = {
-blockSize,blockSize,0,
blockSize,blockSize,0,
blockSize,-blockSize,0,
-blockSize,-blockSize,0
};
//矩形的颜色
GLfloat vRed[] = {1.f,0.f,0.f,1.f};
//在x轴移动的距离
GLfloat xPos = 0.f;
//在y轴移动的距离
GLfloat yPos = 0.f;
#pragma mark - 函数
//设置视口
void ChangeSize(int w,int h)
{
glViewport(0, 0, w, h);
}
//设置渲染环境
void SetUpRC ()
{
//设置清屏颜色
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
//初始化着色器管理器
shaderManager.InitializeStockShaders();
//设置三角形批次类
squareBatch.Begin(GL_TRIANGLE_FAN, 4);
squareBatch.CopyVertexData3f(vVerts);
squareBatch.End();
}
//设置渲染
void RenderScene()
{
//清空缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//定义矩阵
//定义一个平移矩阵,一个旋转矩阵,一个综合结果矩阵
M3DMatrix44f mTranslationMatrix,mRotateMatrix,mFinalMatrix;
//用矩阵来做平移
//把x,y轴移动的单位分别放入第2和3个参数,结果会放入第一个参数
m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.f);
//用矩阵来做旋转
//先设置一个每次重绘或者说移动的时候,矩形旋转的角度,绕z轴转
static float zRot = 5.f;
zRot += 5.f;
//用旋转矩阵做旋转,结果放到第一个参数的矩阵中
m3dRotationMatrix44(mRotateMatrix, m3dDegToRad(zRot), 0.f, 0.f, 1.f);
//结合旋转和平移
m3dMatrixMultiply44(mFinalMatrix, mTranslationMatrix, mRotateMatrix);
//选择并设置着色器/固定管线
shaderManager.UseStockShader(GLT_SHADER_FLAT,mFinalMatrix,vRed);
squareBatch.Draw();
glutSwapBuffers();
}
//上下左右特殊键位移动
void SpecialKeys (int key , int x , int y)
{
//设置键位移动
switch (key) {
case GLUT_KEY_UP:
yPos += stepSize;
break;
case GLUT_KEY_DOWN:
yPos -= stepSize;
break;
case GLUT_KEY_RIGHT:
xPos += stepSize;
break;;
case GLUT_KEY_LEFT:
xPos -= stepSize;
}
//边界检测
if (xPos < blockSize - 1) xPos = blockSize - 1;
if (xPos > 1 - blockSize) xPos = 1 - blockSize;
if (yPos < blockSize - 1) yPos = blockSize - 1;
if (yPos > 1 - blockSize) yPos = 1 - blockSize;
glutPostRedisplay();
}
#pragma mark - main
int main (int argc,char *argv[])
{
//设置工作目录和项目目录到同一目录/Resource下
//GLUT的优先级设定已经做过这个操作,手动再做一次保证安全。
gltSetWorkingDirectory(argv[0]);
//初始化GLUT
glutInit(&argc, argv);
//初始化显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//初始化窗口大小
glutInitWindowSize(600, 600);
//创建窗口并命名
glutCreateWindow("矩阵变换——图形移动");
//注册函数
//注册重塑函数
glutReshapeFunc(ChangeSize);
//注册渲染函数
glutDisplayFunc(RenderScene);
//注册特殊键位函数
glutSpecialFunc(SpecialKeys);
//初始化一个Glew库,确保OpenGL的API完全可用
//在做任何的渲染尝试之前,都要确保驱动程序的初始化不出现任何问题
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("glew init error : %s \n",glewGetErrorString(status));
return 1;
}
//设置渲染环境
SetUpRC();
//开启循环。
//本循环会接受并执行来自于系统或者窗口的指令,但是必须是在其之前键入的
glutMainLoop();
return 0;
}
其中,mTranslationMatrix,mRotateMatrix的位置如果发生了互换,结果就会不一样,这是因为矩阵的乘法,又不是特殊的伴随矩阵、单位矩阵和数量矩阵,所以不满足交换律的。
到此,关于矩阵变换和向量的一些知识就学习完成了。