1. 顶点变换管线基本流程图
首先初始化顶点数据,然后顶点数据乘以模型视图矩阵,生成变化的视觉坐标,视觉坐标就是经过一系列变换后得到的坐标,然后视觉坐标乘以投影矩阵会生成剪裁坐标,剪裁坐标会将非显示数据踢掉,并且转换到单元立方体坐标中。随后剪裁坐标通过透视除法也就是除以w坐标会转换成设备坐标,w除以坐标的意义在于我们看到渲染物体的深度,对上面的剪裁坐标的点的x、y、z坐标除以它的w分量,除以w的坐标叫做归一化设备坐标。如果w分量大,除以w后的点就接近(0,0,0),而在三维空间中,距离我们较远的坐标如果它的w分量较大,进行透视除法后,就距离原点越近,原点作为远处物体的消失点,反之亦然,就有三维场景的效果。最后将透视得到的三元坐标经过视口变换就映射到2d屏幕上,我们就可以看到渲染之后的效果了。
2. 矩阵堆栈
矩阵存储在堆区,而地址是存储在栈区,在大量进行变换的应用的场景中,就需要顶点与大量的变换矩阵进行相乘,这时候就需要大量的构造矩阵,这时候有一个便利的矩阵构造函数可以进行构造矩阵操作矩阵乘法会方便很多,在math3d的这个类被称为GLMatrixsStack。
使用矩阵堆栈进行矩阵的创建和操作矩阵乘法很方便,但是我们还要方便的管理这些堆栈,就是说我们可以随时方便取到堆栈矩阵的地址,GLGeometryTransform可以设置指针指向我们创建好的堆栈矩阵。
3. 角色针
角色针GLframe可以用来表示一个对象相对于坐标系的位置和方向的。
4. 照相机管理
照相机变换这种方式在OprnGL 中其实是不存在的,只是我们为了形象的形容这种变换。如果给定照相机在坐标系中的一个位置和方向,当我们向前移动照相机就相当于整个场景向后退一样。
照相机也是角色帧的一种,这里是更形象的定义,就好比之前说的视图变换与模型变换。这样做的好处就是可以更方便操作矩阵变换。
下面的例子是对上面提到的知识的应用
#define NUM_SPHERES 50
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);
//创建一个花环将顶点存在torusBatch中
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
//创建一个球体将顶点存在sphereBatch中
gltMakeSphere(sphereBatch, 0.1f, 26, 13);
//创建一个以线段的网状的地面,共324个顶点
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 nWidth, int nHeight)
{
//视口变换,转换成设备坐标
glViewport(0, 0, nWidth, nHeight);
//进行透视投影变换
viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
//堆栈矩阵,将投影变换后的投影矩阵与projectionMatrix堆栈的顶部矩阵相乘得到新的矩阵放入projectionMatrix堆栈顶部
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//管理管线,通过modelViewMatrix和projectionMatrix指针指向堆栈矩阵的顶部
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
void RenderScene(void)
{
// 颜色顶点
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();
// 4x4矩阵
M3DMatrix44f mCamera;
//得到一个照相机角色帧
cameraFrame.GetCameraMatrix(mCamera);
//保存角色帧到堆栈顶部
modelViewMatrix.PushMatrix(mCamera);
// 点光源的顶点
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3DVector4f vLightEyePos;
// 将点光源的顶点和照相机的位置相乘转换成视觉坐标系
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
//通过平面存储着色器将空间坐标转换为平面坐标
//transformPipeline.GetModelViewProjectionMatrix() 管理管线通过transformPipeline得到projectionMatrix堆栈顶部的矩阵
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
//绘制地面
floorBatch.Draw();
//遍历之前在spheres中地面的顶点坐标
for(int i = 0; i < NUM_SPHERES; i++) {
//保存当前矩阵到堆栈
modelViewMatrix.PushMatrix();
//将顶点坐标转换为矩阵并存在modelViewMatrix堆栈顶部
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 main(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;
}