前言
通过前面的介绍,我们已经了解了如何用模型视图矩阵和投影矩阵在屏幕上显示想
要的东西,下面让我们从整体上了解一下变换管线。

上图展示的是处理过程的流程。
首先,我们的顶点将被视为一个1x4矩阵,其中前3个值为x,y和z坐标。
第4个数字是一个缩放因子,如果需要的话我们可以手动进行设置。这就是w坐标,通常在默认情况下为1.0,我们很少会真正去直接修改这个值。
然后,顶点将乘以模型视图矩阵,生成变换的视觉坐标。
随后,视觉坐标再乘以投影矩阵,生成裁剪坐标。
裁剪坐标值位于我们前面提到的+/-1.0单位坐标系内,将有效地将所有位于这个裁剪空间之外的数据消除掉。
裁剪坐标随后再除以w坐标,生成规范化的设备坐标,其中w值可能会被投影矩阵或模型视图矩阵修改,这取决于所发生的变换。
透视除法将作为图元装配过程的一部分进行。
最后,坐标三元组将通过视口变换被映射到2D平面上,这项操作也是由一个矩阵来表示的,但不能直接指定或者修改这个矩阵。
OpenGL将在内部根据指定的glViewport值来设置这个矩阵。
矩阵堆栈
在分层方式中,一个或多个对象会相对于另一个对象进行绘制,在这种方式中经常会应用到变换。
这样,就会需要大量由用户代码进行构造和管理的矩阵在3D空间中建立复杂的场景。
习惯上,我们会使用一个矩阵堆栈来帮助完成这些工作,而GLTools库则会在math3d矩阵函数顶部建立实用类。这个类称为GLMatrixStack,此前的示例中都有用过了。
这个类的构造函数允许指定堆栈的最大深度,默认的堆栈深度为64。
cpp
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);
这个矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。
我们可以通过调用在顶部载入这个单位矩阵。
cpp
void GLMatrixStack::LoadIdentity(void);
或者可以在堆栈的顶部载入任何矩阵。
cpp
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
此外,我们可以用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘得到的结果随后将存储在堆栈的顶部。
cpp
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
最后,只要用GetMatrix函数就可以获得矩阵堆栈顶部的值,这个函数有重载版本,以适应GLShaderManager的使用,或者仅仅是获得顶部矩阵的副本。
cpp
const M3DMatrix44f& GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
压栈与出栈
一个矩阵的真正价值在于通过压栈操作存储一个状态,然后通过出栈操作恢复这个状态。
通过GLMatrixStack类,我们可以使用PushMatrix函数将矩阵压入堆栈来存储当前矩阵值,这样做实际上是复制了当前矩阵值,并将新的值放入了堆栈的顶部。
类似地,PopMatrix将移除顶部矩阵,并恢复它下面的值。以上每种情况都有几次重载。
cpp
void GLMatrixStack::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix);
void PushMatrix(GLFrame& frame);
void GLMatrixStack::PopMatrix(void);
除了将当前矩阵压入堆栈之外,通过M3DMatrix44f数据类型或者GLFrame类,还可以将任意一个矩阵压入到堆栈的顶部。
仿射变换
GLMatrixStack类也内建了对创建旋转、平移和缩放矩阵的支持,相应的函数列出如下。
cpp
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);
示例1
示例程序将进行几次修改,而最开始的示例1只是在绿色网格背景上显示一个旋转线框模式的花托,代码如下:
cpp
#include "sphereworld.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#include <stdio.h>
#ifndef FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif
#include <GL/glut.h>
namespace sphereworld
{
static GLShaderManager shaderManager;
static GLMatrixStack modelViewMatrix; // 模型视图矩阵栈
static GLMatrixStack projectionMatrix; // 投影矩阵栈
static GLFrustum viewFrustum; // 视景体
static GLGeometryTransform transformPipeline; // 几何变换管线
GLTriangleBatch torusBatch;
GLBatch floorBatch;
void SetupRC()
{
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
}
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
// 创建投影矩阵,并将其加载到投影矩阵堆栈上
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 将转换管道设置为使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void RenderScene()
{
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 保存当前模型视图矩阵(单位矩阵)
modelViewMatrix.PushMatrix();
// 绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
// 绘制旋转的圆环
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vTorusColor);
torusBatch.Draw();
// 恢复之前的模型视图矩阵(ID矩阵)
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}
int sphereworld_test(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL SphereWorld");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
}
效果预览:

示例2
在示例1的基础上,添加蓝色球体的变换及绘制处理。
代码如下:
cpp
#include "sphereworld2.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#include <stdio.h>
#ifndef FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif
#include <GL/glut.h>
namespace sphereworld2
{
static GLShaderManager shaderManager;
static GLMatrixStack modelViewMatrix; // 视图模型矩阵栈
static GLMatrixStack projectionMatrix; // 投影矩阵栈
static GLFrustum viewFrustum; // 视景体
static GLGeometryTransform transformPipeline; // 几何变换管线
GLTriangleBatch torusBatch;
GLBatch floorBatch;
GLTriangleBatch sphereBatch;
GLFrame cameraFrame;
void SetupRC()
{
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
}
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
// 创建投影矩阵,并将其加载到投影矩阵堆栈上
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 将转换管道设置为使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void SpecialKeys(int key, int x, int y)
{
// 移动摄像机
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
void RenderScene()
{
// Color values
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
// 基于时间的动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// 清除颜色和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 保存当前模型视图矩阵(单位矩阵)
modelViewMatrix.PushMatrix();
// 将摄像机矩阵加载到模型视图矩阵栈中
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
// 绘制旋转的圆环
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 报错变化矩阵
modelViewMatrix.PushMatrix();
{
// 旋转并绘制圆环
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vTorusColor);
torusBatch.Draw();
}
modelViewMatrix.PopMatrix();
// 再次旋转,然后平移,然后绘制球体
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vSphereColor);
sphereBatch.Draw();
// 恢复之前的模型视图矩阵(单位矩阵)
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
// 进行缓冲区交换
glutSwapBuffers();
// 重新绘制
glutPostRedisplay();
}
int sphereworld2_test(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL SphereWorld");
glutSpecialFunc(SpecialKeys);
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
}
效果预览:

示例3
在示例2的基础上,在不同位置添加小球体,并添加观察视角移动的处理。
代码如下:
cpp
#include "sphereworld3.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#include <stdio.h>
#ifndef FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif
#include <GL/glut.h>
#define NUM_SPHERES 50
namespace sphereworld3
{
static GLFrame spheres[NUM_SPHERES];
static GLShaderManager shaderManager;
static GLMatrixStack modelViewMatrix;
static GLMatrixStack projectionMatrix;
static GLFrustum viewFrustum;
static GLGeometryTransform transformPipeline;
static GLTriangleBatch torusBatch;
static GLBatch floorBatch;
static GLTriangleBatch sphereBatch;
static GLFrame cameraFrame;
void SetupRC()
{
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
// 随机放置球体
for (int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
spheres[i].SetOrigin(x, 0.0f, z);
}
}
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
// 创建投影矩阵,并将其加载到投影矩阵堆栈上
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 将转换管道设置为使用两个矩阵堆栈
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void RenderScene()
{
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
// Time Based animation
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// Clear the color and depth buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 保存当前模型视图矩阵(单位矩阵)
modelViewMatrix.PushMatrix();
// 视图变换,变换后为视图矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
// 绘制随机球体
for (int i = 0; i < NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(spheres[i]);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
// 绘制旋转的圆环
// 视图矩阵变换
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// 保存变换,因为没有乘以模型矩阵,因此此时还是视图矩阵
modelViewMatrix.PushMatrix();
{
// 应用旋转并绘制圆环
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vTorusColor);
torusBatch.Draw();
}
modelViewMatrix.PopMatrix();
// 再次旋转,然后平移,然后绘制球体
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vSphereColor);
sphereBatch.Draw();
// 恢复之前的模型视图矩阵(单位矩阵)
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
// Do the buffer Swap
glutSwapBuffers();
// Tell GLUT to do it again
glutPostRedisplay();
}
void SpecialKeys(int key, int x, int y)
{
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
int sphereworld3_test(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL SphereWorld");
glutSpecialFunc(SpecialKeys);
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
}
效果预览:

示例4
示例4在示例3的基础上添加一个单独的点光源。
代码如下:
cpp
#include "sphereworld4.h"
#include <GLTools.h>
#include <GLShaderManager.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLFrame.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#include <StopWatch.h>
#include <math.h>
#include <stdio.h>
#ifndef FREEGLUT_STATIC
#define FREEGLUT_STATIC
#endif
#include <GL/glut.h>
#define NUM_SPHERES 50
namespace sphereworld4 {
static GLFrame spheres[NUM_SPHERES];
GLShaderManager shaderManager;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrustum viewFrustum;
GLGeometryTransform transformPipeline;
GLTriangleBatch torusBatch;
GLBatch floorBatch;
GLTriangleBatch sphereBatch;
GLFrame cameraFrame;
void SetupRC()
{
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0; x <= 20.0f; x += 0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
// Randomly place the spheres
for (int i = 0; i < NUM_SPHERES; i++) {
GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1f);
GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1f);
spheres[i].SetOrigin(x, 0.0f, z);
}
}
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void RenderScene()
{
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
for (int i = 0; i < NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(spheres[i]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
modelViewMatrix.PushMatrix();
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vTorusColor);
torusBatch.Draw();
modelViewMatrix.PopMatrix();
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}
void SpecialKeys(int key, int x, int y)
{
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
int sphereworld4_test(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL SphereWorld");
glutSpecialFunc(SpecialKeys);
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
}
效果预览:

将一个固定光源位置变换到视觉坐标相对简单,而在每个场景中只需进行一次。
cpp
//将光源位置传变换到视觉坐标系
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
光源位置的全局坐标存储在vLightPos变量中,其中包含了光源位置的x坐标、y坐标、z坐标和w坐标。我们必须保留w坐标(而且它必须为1.0),因为无法用一个只有3个分量的向量去乘以一个4×4矩阵。
使用原来获得的照相机矩阵mCamera,可以使用math3d库函数m3dTransformVector4对光源位置进行变换。
此外,要渲染蓝色球体,需要用到点光源存储着色器。
cpp
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos, vSphereColor);
思考
每次PushMatrix时的矩阵状态是如何变化的?
在过完上面4个示例后,不妨思考下每次PushMatrix时的矩阵状态是如何变化的?
要思考这个问题,需要了解此前介绍过的几个核心概念:
• 单位矩阵 (Identity):相当于"无变换",数值上是对角线为1的矩阵。
• 视图矩阵 (View):由相机位置决定,把世界坐标转换到相机坐标。
• 模型矩阵 (Model):由物体位置/旋转决定,把物体局部坐标转换到世界坐标。
• 模型视图矩阵 (MV) = 视图矩阵 × 模型矩阵。
下面详细看看演变过程
- 初始状态
程序启动时,modelViewMatrix 栈是空的,或者默认包含一个单位矩阵。 - 进入 RenderScene
• 动作:将当前的栈顶矩阵(单位矩阵)复制一份压入栈。
• 栈状态:单位矩阵, 单位矩阵 (栈顶在右)
• 含义:这是本帧的存档点 A。
- 应用相机变换(变成视图矩阵)
• 动作:将 mCamera 矩阵压入栈顶。
• 栈状态:单位矩阵, 单位矩阵, 视图矩阵
• 含义:此时栈顶是视图矩阵。
• 注:GLTools 的 PushMatrix(matrix) 行为通常是:先 Push 当前栈顶,然后将传入的矩阵 Mult 到新的栈顶上,或者直接替换。无论哪种,此刻栈顶代表的是"从世界到相机的变换"。
• 纠正:严格来说,GLTools 的 PushMatrix(m) 等价于 PushMatrix(); MultMatrix(m);。
• 所以栈状态其实是:单位矩阵, 单位矩阵, 视图矩阵。
- 绘制地面 (Floor)
• 当前 MV 矩阵:视图矩阵。
• 最终 MVP:投影矩阵 × 视图矩阵。
• 效果:地面没有额外的模型变换,直接基于相机视角绘制。
- 绘制随机球体 (Spheres) - 循环隔离
• 进入循环第一次迭代:
• 【操作3】:复制栈顶(视图矩阵)。
• 栈状态:..., 视图矩阵, 视图矩阵
• 【操作4】:将第1个球的位置矩阵乘到栈顶。
• 栈状态:..., 视图矩阵, 视图矩阵 × 球1模型矩阵
• 此时栈顶变成了视图模型矩阵 (VM1)。
• Draw:使用 VM1 绘制。
• 【操作5】:弹出栈顶。
• 栈状态恢复为:..., 视图矩阵, 视图矩阵
• 第二次迭代:
• 再次 Push,Mult 球2,Draw,Pop。
• 关键点:每次 Pop 都回到了纯粹的视图矩阵状态,确保下一个球不会受上一个球位置的影响。
- 绘制圆环 (Torus) - 层级变换
此时栈顶依然是视图矩阵。
• 动作:将平移矩阵乘到栈顶。
• 栈状态:..., 视图矩阵, 视图矩阵 × 平移矩阵
• 含义:此时栈顶变成了视图模型矩阵 (VM_Translated)。原点被移到了相机前方 2.5 米处。
• 动作:保存当前的 VM_Translated。
• 栈状态:..., 视图矩阵, VM_Translated, VM_Translated
• 动作:将旋转矩阵乘到栈顶。
• 栈状态:..., 视图矩阵, VM_Translated, VM_Translated × 旋转矩阵
• 含义:此时栈顶是最终的圆环 MV 矩阵。圆环在平移后的位置上自转。
• Draw:绘制圆环。
• 动作:弹出栈顶(移除旋转)。
• 栈状态恢复为:..., 视图矩阵, VM_Translated
• 含义:旋转被撤销了,但平移保留了下来。现在的坐标系原点在相机前方 2.5 米,但没有旋转。
- 绘制卫星球 (Satellite)
• 动作:继续在 VM_Translated 基础上乘入旋转和平移。
• 栈状态:..., 视图矩阵, VM_Translated × 旋转2 × 平移2
• 含义:这个球体相对于"相机前方2.5米"的那个点进行旋转和偏移。因为它没有 Push/Pop 保护,它是直接修改了上一层的矩阵。
- 恢复现场
• 【操作12】:弹出 VM_Translated...。
• 栈状态恢复为:..., 视图矩阵, 视图矩阵 (对应【操作2】后的状态)
• 【操作13】:弹出 视图矩阵。
• 栈状态恢复为:..., 单位矩阵, 单位矩阵 (对应【操作1】后的状态)
• 等等,通常我们会再 Pop 一次回到完全空或初始状态,但在下一帧开始时 RenderScene 会再次 Push,所以保持两层单位矩阵也是安全的,只要层级匹配。
• 修正:看代码开头只 Push 了一次单位矩阵,然后 Push 了 Camera。所以最后两次 Pop 正好抵消了这两次 Push。
• 最终栈顶变回单位矩阵。
小结
- 单位矩阵何时变成视图矩阵?
• 在执行 modelViewMatrix.PushMatrix(mCamera) 时。它将相机矩阵应用到栈顶,此时栈顶代表视图变换。 - 视图矩阵何时变成视图模型矩阵?
• 当你调用 MultMatrix(spheres、Translate 或 Rotate 时。这些函数将模型变换乘到当前的视图矩阵上,结果就是视图模型矩阵 (MV)。 - 何时变成视图模型投影矩阵 (MVP)?
• 永远不会存储在 modelViewMatrix 栈中!
• 当你调用 transformPipeline.GetModelViewProjectionMatrix() 时,GLTools 会在 CPU 端临时执行: MVP = projectionMatrix栈顶 × modelViewMatrix栈顶
• 这个计算出的 MVP 矩阵只是作为一个参数传递给 Shader,用完即弃,不会压入任何栈。 - 如何恢复到单位矩阵?
• 通过成对的 PopMatrix()。
• 每一次 PushMatrix 都会记住之前的状态。
• 每一次 PopMatrix 都会丢弃当前的变换,回到上一次 Push 时的状态。
• 只要 Push 和 Pop 的数量相等且逻辑嵌套正确,最终一定会回到最初的单位矩阵。
形象比喻,想象你在画画:
- PushMatrix(): 拍一张照(保存当前画布状态)。
- Translate/Rotate: 移动或旋转画布。
- Draw: 在当前位置画画。
- PopMatrix(): 扔掉刚才移动过的画布,拿出之前拍的照片(恢复状态)。
这样,画第二个物体时,就不会受到第一个物体移动的影响。