矩阵堆栈管理
我们在使用OpenGL渲染时,通常需要展现出一些动画效果,每种动画都离不开基本的形变方式:平移、旋转、缩放。一个4x4的矩阵可以描述3D坐标上的一个位置和方向。(没接触过矩阵的可以简单了解下)一个顶点乘以一个形变矩阵后,得到的是这个顶点形变后的位置。一个对象的所有顶点都乘以这个形变矩阵就会得到这个对象形变后的位置。A坐标系下的向量乘以一个包含B坐标系的矩阵,得到的是B坐标系下的新向量。
而变换管线中从顶点数据开始到最终在窗口呈现,中间有多层变换。其中我们要处理的只有模型视图矩阵和投影矩阵,但复杂的需求场景也会降低开发效率。我们可以使用math3d库中的矩阵堆栈类GLMatrixStack
来完成这部分工作。
矩阵堆栈类GLMatrixStack
常用函数
//构造函数:可以指定堆栈的深度,默认64。初始化时已经使用m3dLoadIdentity44()加载了一个单元矩阵。
GLMatrixStack::GLMatrixStack(int istackDepth = 64);
//在栈顶部载入一个单元矩阵
void GLMat rixStack::LoadIdentity(void);
//在栈顶载一个矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
//矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部
void GLMatrixStack::MultMatrix(const M3DMatrix44f mMatrix);
//GetMatrix():获取矩阵堆栈顶部的值
//可以进行两次重载,为了适应GLShaderMananger的使用,或者获取顶部矩阵的副本
const M3DMatrix44f & GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
压栈出栈
矩阵堆栈的真正价值在于通过压栈存储一个状态,出栈恢复原来的状态。
//将当前矩阵压入堆栈(栈顶矩阵copy一份后,再压入栈顶)
void GLMatrixStack::PushMatrix(void);
//将mMatrix矩阵对象压入当前矩阵堆栈
void PushMatrix(const M3DMatrix44f mMatrix);
//将GLFame对象压入矩阵对象
void PushMatrix(GLFame &frame);
//出栈(出栈指的是移除顶部的矩阵对象)
void GLMatrixStack::PopMatrix(void);
仿射变换
//旋转,angle为角度值
void MatrixStack::Rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
//平移
void MatrixStack::Translate(GLfloat X, GLfloat y, GLfloat z);
//缩放
void MatrixStack::Scale(GLfloat X, GLfloat y, GLfloat z);
示例代码
下面是个球体世界demo代码,其中大球自转,小球公转的变换矩阵都是用矩阵堆栈管理对象 GLMatrixStack
来操作的。
//
// main.cpp
// OpenGLDemo
//
// Created by silas on 6/4/19.
// Copyright © 2019 guokai. All rights reserved.
//
#define kDebug 1
#if kDebug
#define SSPrintf(fmt, args...) (printf("\n%s--%d\n" fmt, __FUNCTION__, __LINE__, ##args))
#else
#define SSPrintf(fmt, ...)
#endif
#include <GLTools.h>
#include <GLUT/GLUT.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
#include <GLShaderManager.h>
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"
#include "GLFrustum.h"
#include "StopWatch.h"
///MARK:- 全局变量
GLShaderManager shaderManager;
GLGeometryTransform transformPipeline;
GLFrustum viewFrustum;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectMatrix;
GLBatch floorBatch;
GLTriangleBatch sphereBigBatch;
GLTriangleBatch sphereSmallBatch;
#define kSphereSmakkNum (50)
GLFrame sphereSmallFrame[kSphereSmakkNum];
GLFrame cameraFrame;//观察者矩阵
//MARK:- 函数
void setupRC() {
glClearColor(0.74f, 0.62f, 0.88f, 1.0f);
glEnable(GL_DEPTH_TEST);
shaderManager.InitializeStockShaders();
floorBatch.Begin(GL_LINES, 324);
for (GLfloat i = -20.0f; i <= 20.0f; i += 0.5f) {
floorBatch.Vertex3f(i, -0.6f, 20.0f);
floorBatch.Vertex3f(i, -0.6f, -20.0f);
floorBatch.Vertex3f(-20.0f, -0.6f, i);
floorBatch.Vertex3f(20.0f, -0.6f, i);
}
floorBatch.End();
gltMakeSphere(sphereBigBatch, 0.5f, 20, 30);
gltMakeSphere(sphereSmallBatch, 0.2, 22, 10);
for (GLint i = 0; i < kSphereSmakkNum; i++) {
//y轴不变,X,Z产生随机值
GLfloat x = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
sphereSmallFrame[i].SetOrigin(x, 0.0f, z);
}
}
void changeSize(int w, int h) {
// SSPrintf("w = %d, h = %d", w, h);
glViewport(0, 0, w, h);
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1, 100);
projectMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
modelViewMatrix.LoadIdentity();
transformPipeline.SetMatrixStacks(modelViewMatrix, projectMatrix);
}
void drawWireFramedWithBatch(GLBatch *batch) {
}
void drawWireFramedWithTriangleBatch(GLTriangleBatch *triangleBatch) {
GLfloat black[] = {0.0f, 0.0f, 0.0f, 1.0f};
glEnable(GL_POLYGON_OFFSET_LINE);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glPolygonOffset(-1.0f, -1.0f);
glLineWidth(2.0f);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
black);
sphereBigBatch.Draw();
glDisable(GL_POLYGON_OFFSET_LINE);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glPolygonOffset(0.0f, 0.0f);
glLineWidth(1.0f);
glDisable(GL_LINE_SMOOTH);
glDisable(GL_BLEND);
}
void renderScene() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
GLfloat floorColor[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat sphereBigColor[] = {1.0f, 0.0f, 0.0f, 1.0f};
GLfloat sphereSmallColor[] = {1.0f, 0.0f, 1.0f, 1.0f};
//加入观察者
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
//画地板
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
floorColor);
floorBatch.Draw();
//画大球
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
static CStopWatch timer;
GLfloat angle = timer.GetElapsedSeconds() * 60;
modelViewMatrix.PushMatrix();
modelViewMatrix.Rotate(m3dDegToRad(angle), 0.0f, 1.0f, 0.0f);
M3DMatrix44f lightPosition = {0.0f, 10.0f, 5.0f, 1.0f};
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereBigColor);
sphereBigBatch.Draw();
if (1) {
drawWireFramedWithTriangleBatch(&sphereBigBatch);
}
modelViewMatrix.PopMatrix();
//画小球
for (int i = 0; i < kSphereSmakkNum; i++) {
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(sphereSmallFrame[I]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereSmallColor);
sphereSmallBatch.Draw();
modelViewMatrix.PopMatrix();
}
//使小球公转
modelViewMatrix.Rotate(angle * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
// shaderManager.UseStockShader(GLT_SHADER_FLAT,
// transformPipeline.GetModelViewProjectionMatrix(),
// sphereSmallColor);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereSmallColor);
sphereSmallBatch.Draw();
modelViewMatrix.PopMatrix();//pop观察者
glutSwapBuffers();
glutPostRedisplay();
}
void specialKey(int key, int x, int y) {
if (key == GLUT_KEY_UP) {
// cameraFrame.TranslateWorld(0.0f, 0.0f, -0.25f);
cameraFrame.MoveForward(0.25f);
}
if (key == GLUT_KEY_DOWN) {
// cameraFrame.TranslateWorld(0.0f, 0.0f, 0.5f);
cameraFrame.MoveForward(-0.25f);
}
if (key == GLUT_KEY_LEFT) {
cameraFrame.RotateWorld(m3dDegToRad(2.5f), 0.0f, 1.0f, 0.0f);
}
if (key == GLUT_KEY_RIGHT) {
cameraFrame.RotateWorld(m3dDegToRad(-2.5f), 0.0f, 1.0f, 0.0f);
}
}
void keyboardPress(unsigned char key, int x, int y) {
}
//MARK:- main函数
int main(int argc, char *argv[]) {
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("sphere world");
glutReshapeFunc(changeSize);
glutDisplayFunc(renderScene);
glutSpecialFunc(specialKey);
GLenum status = glewInit();
if (status != GLEW_OK) {
SSPrintf("glew error:%s", glewGetErrorString(status));
return 1;
}
setupRC();
glutMainLoop();
return 0;
}
如果需要调试,可以把代码放在OpenGL环境下的项目中。
球体世界.gif示例解析
demo中声明了两个矩阵堆栈对象
GLMatrixStack modelViewMatrix;//模型视图矩阵堆栈
GLMatrixStack projectMatrix;//透视投影矩阵堆栈
在changeSize
函数中设置了这两个矩阵堆栈的初值。
//设置投影视图
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1, 100);
//在透视投影矩阵堆栈中 压入 投影矩阵
projectMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//在模型矩阵堆栈中 压入 单元矩阵
modelViewMatrix.LoadIdentity();//这句可有可无
矩阵的压栈出栈主要是在renderScene
中,这里摘关键代码,完整示例代码可以用来调试。
//1.观察者矩阵入栈
//模型视图矩阵堆栈栈顶(单元矩阵0) copy后压入 一个单元矩阵
modelViewMatrix.PushMatrix();
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1)
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//模型视图矩阵堆栈栈顶(单元矩阵1) 乘以 观察者矩阵
modelViewMatrix.MultMatrix(mCamera);
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵)
//画地板
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
floorColor);
floorBatch.Draw();
//2.大球矩阵管理
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵) 乘以 大球平移矩阵
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵)
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵 * 大球平移矩阵) copy后压入 (单元矩阵2 * 观察者矩阵 * 大球平移矩阵)
modelViewMatrix.PushMatrix();
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵, 单元矩阵2 * 观察者矩阵 * 大球平移矩阵)
//模型视图矩阵堆栈栈顶(单元矩阵2 * 观察者矩阵 * 大球平移矩阵) * 大球旋转矩阵
modelViewMatrix.Rotate(m3dDegToRad(angle), 0.0f, 1.0f, 0.0f);
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵, 单元矩阵2 * 观察者矩阵 * 大球平移矩阵 * 大球旋转矩阵)
M3DMatrix44f lightPosition = {0.0f, 10.0f, 5.0f, 1.0f};
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereBigColor);
sphereBigBatch.Draw();
//模型视图矩阵堆栈栈顶(单元矩阵2 * 观察者矩阵 * 大球平移矩阵 * 大球旋转矩阵) 出栈
modelViewMatrix.PopMatrix();
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵)
//3.小球矩阵管理
for (int i = 0; i < kSphereSmakkNum; i++) {
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵 * 大球平移矩阵) copy后压入 (单元矩阵2 * 观察者矩阵 * 大球平移矩阵)
modelViewMatrix.PushMatrix();
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵, 单元矩阵2 * 观察者矩阵 * 大球平移矩阵)
//模型视图矩阵堆栈栈顶(单元矩阵2 * 观察者矩阵 * 大球平移矩阵) 乘以 小球矩阵
modelViewMatrix.MultMatrix(sphereSmallFrame[I]);
//结果://结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵, 单元矩阵2 * 观察者矩阵 * 大球平移矩阵 * 小球矩阵)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereSmallColor);
sphereSmallBatch.Draw();
//模型视图矩阵堆栈栈顶(单元矩阵2 * 观察者矩阵 * 大球平移矩阵 * 小球矩阵) 出栈
modelViewMatrix.PopMatrix();
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵)
}
//4.公转小球矩阵管理
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵 * 大球平移矩阵) 乘以 公转小球旋转矩阵
modelViewMatrix.Rotate(angle * -2.0f, 0.0f, 1.0f, 0.0f);
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵 * 公转小球旋转矩阵)
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵 * 大球平移矩阵 * 公转小球旋转矩阵) 乘以 公转小球平移矩阵
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
//结果:模型视图矩阵堆栈(单元矩阵0,单元矩阵1 * 观察者矩阵 * 大球平移矩阵 * 公转小球旋转矩阵 * 公转小球平移矩阵)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
lightPosition,
sphereSmallColor);
sphereSmallBatch.Draw();
//1.观察矩阵出栈
//模型视图矩阵堆栈栈顶(单元矩阵1 * 观察者矩阵 * 大球平移矩阵 * 公转小球旋转矩阵 * 公转小球平移矩阵) 出栈
modelViewMatrix.PopMatrix();//pop观察者
//结果:模型视图矩阵堆栈(单元矩阵0)
注释看起来有点啰嗦,还是画张简陋的图来说明吧。最后公转小球的部分就不画了,没那么大的纸。
矩阵的堆栈管理
网友评论