OpenGL(十一)- 变换管线

前言

通过前面的介绍,我们已经了解了如何用模型视图矩阵和投影矩阵在屏幕上显示想

要的东西,下面让我们从整体上了解一下变换管线。

上图展示的是处理过程的流程。

首先,我们的顶点将被视为一个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) = 视图矩阵 × 模型矩阵。

下面详细看看演变过程

  1. 初始状态
    程序启动时,modelViewMatrix 栈是空的,或者默认包含一个单位矩阵。
  2. 进入 RenderScene

• 动作:将当前的栈顶矩阵(单位矩阵)复制一份压入栈。

• 栈状态:单位矩阵, 单位矩阵 (栈顶在右)

• 含义:这是本帧的存档点 A。

  1. 应用相机变换(变成视图矩阵)

• 动作:将 mCamera 矩阵压入栈顶。

• 栈状态:单位矩阵, 单位矩阵, 视图矩阵

• 含义:此时栈顶是视图矩阵。

• 注:GLTools 的 PushMatrix(matrix) 行为通常是:先 Push 当前栈顶,然后将传入的矩阵 Mult 到新的栈顶上,或者直接替换。无论哪种,此刻栈顶代表的是"从世界到相机的变换"。

• 纠正:严格来说,GLTools 的 PushMatrix(m) 等价于 PushMatrix(); MultMatrix(m);。

• 所以栈状态其实是:单位矩阵, 单位矩阵, 视图矩阵

  1. 绘制地面 (Floor)

• 当前 MV 矩阵:视图矩阵。

• 最终 MVP:投影矩阵 × 视图矩阵。

• 效果:地面没有额外的模型变换,直接基于相机视角绘制。

  1. 绘制随机球体 (Spheres) - 循环隔离

• 进入循环第一次迭代:

• 【操作3】:复制栈顶(视图矩阵)。

• 栈状态:..., 视图矩阵, 视图矩阵

• 【操作4】:将第1个球的位置矩阵乘到栈顶。

• 栈状态:..., 视图矩阵, 视图矩阵 × 球1模型矩阵

• 此时栈顶变成了视图模型矩阵 (VM1)。

• Draw:使用 VM1 绘制。

• 【操作5】:弹出栈顶。

• 栈状态恢复为:..., 视图矩阵, 视图矩阵

• 第二次迭代:

• 再次 Push,Mult 球2,Draw,Pop。

• 关键点:每次 Pop 都回到了纯粹的视图矩阵状态,确保下一个球不会受上一个球位置的影响。

  1. 绘制圆环 (Torus) - 层级变换

此时栈顶依然是视图矩阵。

• 动作:将平移矩阵乘到栈顶。

• 栈状态:..., 视图矩阵, 视图矩阵 × 平移矩阵

• 含义:此时栈顶变成了视图模型矩阵 (VM_Translated)。原点被移到了相机前方 2.5 米处。

• 动作:保存当前的 VM_Translated。

• 栈状态:..., 视图矩阵, VM_Translated, VM_Translated

• 动作:将旋转矩阵乘到栈顶。

• 栈状态:..., 视图矩阵, VM_Translated, VM_Translated × 旋转矩阵

• 含义:此时栈顶是最终的圆环 MV 矩阵。圆环在平移后的位置上自转。

• Draw:绘制圆环。

• 动作:弹出栈顶(移除旋转)。

• 栈状态恢复为:..., 视图矩阵, VM_Translated

• 含义:旋转被撤销了,但平移保留了下来。现在的坐标系原点在相机前方 2.5 米,但没有旋转。

  1. 绘制卫星球 (Satellite)

• 动作:继续在 VM_Translated 基础上乘入旋转和平移。

• 栈状态:..., 视图矩阵, VM_Translated × 旋转2 × 平移2

• 含义:这个球体相对于"相机前方2.5米"的那个点进行旋转和偏移。因为它没有 Push/Pop 保护,它是直接修改了上一层的矩阵。

  1. 恢复现场

• 【操作12】:弹出 VM_Translated...。

• 栈状态恢复为:..., 视图矩阵, 视图矩阵 (对应【操作2】后的状态)

• 【操作13】:弹出 视图矩阵。

• 栈状态恢复为:..., 单位矩阵, 单位矩阵 (对应【操作1】后的状态)

• 等等,通常我们会再 Pop 一次回到完全空或初始状态,但在下一帧开始时 RenderScene 会再次 Push,所以保持两层单位矩阵也是安全的,只要层级匹配。

• 修正:看代码开头只 Push 了一次单位矩阵,然后 Push 了 Camera。所以最后两次 Pop 正好抵消了这两次 Push。

• 最终栈顶变回单位矩阵。

小结

  1. 单位矩阵何时变成视图矩阵?
    • 在执行 modelViewMatrix.PushMatrix(mCamera) 时。它将相机矩阵应用到栈顶,此时栈顶代表视图变换。
  2. 视图矩阵何时变成视图模型矩阵?
    • 当你调用 MultMatrix(spheres、Translate 或 Rotate 时。这些函数将模型变换乘到当前的视图矩阵上,结果就是视图模型矩阵 (MV)。
  3. 何时变成视图模型投影矩阵 (MVP)?
    • 永远不会存储在 modelViewMatrix 栈中!
    • 当你调用 transformPipeline.GetModelViewProjectionMatrix() 时,GLTools 会在 CPU 端临时执行: MVP = projectionMatrix栈顶 × modelViewMatrix栈顶
    • 这个计算出的 MVP 矩阵只是作为一个参数传递给 Shader,用完即弃,不会压入任何栈。
  4. 如何恢复到单位矩阵?
    • 通过成对的 PopMatrix()。
    • 每一次 PushMatrix 都会记住之前的状态。
    • 每一次 PopMatrix 都会丢弃当前的变换,回到上一次 Push 时的状态。
    • 只要 Push 和 Pop 的数量相等且逻辑嵌套正确,最终一定会回到最初的单位矩阵。

形象比喻,想象你在画画:

  1. PushMatrix(): 拍一张照(保存当前画布状态)。
  2. Translate/Rotate: 移动或旋转画布。
  3. Draw: 在当前位置画画。
  4. PopMatrix(): 扔掉刚才移动过的画布,拿出之前拍的照片(恢复状态)。

这样,画第二个物体时,就不会受到第一个物体移动的影响。

相关推荐
小小龙学IT1 小时前
Day 26-27 项目实战:从零构建一个高并发聊天室(epoll + 线程池)
linux·服务器·c语言·开发语言·网络
牢姐与蒯1 小时前
c++高级数据结构之图(基本概念,存储结构,BFS,DFS,最小生成树)
数据结构·c++·
c238562 小时前
MySQL 基础用法(下):查询进阶与核心特性
android·c语言·c++·mysql
码匠许师傅2 小时前
【C++ 面试真题】 C++ 中的 static 有什么作用?
c++·面试
郭涤生2 小时前
C++ 零拷贝(Zero-Copy)
linux·开发语言·c++
乐观勇敢坚强的老彭2 小时前
计算机内存中的堆和栈
c++
十月的皮皮2 小时前
STM32从零到量产开发:四路继电器工业控制模块开发 - 上位机操作说明书
c语言·stm32·单片机·stm32cubemx
饼饼学习空间智能3 小时前
遥操作数据能否提升机器人自主化水平?详解模仿学习、仿真放大与Sim2Real闭环
人工智能·算法
间歇性努力持续性发呆的野生快乐选手3 小时前
dfs回溯,bfs枚举,完全背包
c++·算法·深度优先·宽度优先