使用照相机和角色进行移动
// SphereWorld.cpp
// OpenGL SuperBible
// New and improved (performance) sphere world
// Program by Richard S. Wright Jr.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__ #include
#else #define FREEGLUT_STATIC #include
#endif #define NUM_SPHERES 50 GLFrame spheres[NUM_SPHERES]; GLShaderManager shaderManager; // Shader Manager GLMatrixStack modelViewMatrix; // Modelview Matrix GLMatrixStack projectionMatrix; // Projection Matrix GLFrustum viewFrustum; // View Frustum GLGeometryTransform transformPipeline; // Geometry Transform Pipeline GLTriangleBatch torusBatch; GLBatch floorBatch; GLTriangleBatch sphereBatch; GLFrame cameraFrame; // // This function does any needed initialization on the rendering // context. void SetupRC() { // Initialze Shader Manager shaderManager.InitializeStockShaders(); glEnable(GL_DEPTH_TEST); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // This makes a torus gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30); // This make a sphere 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); } } /// // Screen changes size or is initialized void ChangeSize(int nWidth, int nHeight) { glViewport(0, 0, nWidth, nHeight); // Create the projection matrix, and load it on the projection matrix stack viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f); projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); // Set the transformation pipeline to use the two matrix stacks transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix); } // Called to draw scene void RenderScene(void) { // 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 }; // 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); // Save the current modelview matrix (the identity matrix) modelViewMatrix.PushMatrix(); M3DMatrix44f mCamera; cameraFrame.GetCameraMatrix(mCamera); modelViewMatrix.PushMatrix(mCamera); // Transform the light position into eye coordinates M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f }; M3DVector4f vLightEyePos; m3dTransformVector4(vLightEyePos, vLightPos, mCamera); // Draw the ground 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(); } // Draw the spinning Torus modelViewMatrix.Translate(0.0f, 0.0f, -2.5f); // Save the Translation modelViewMatrix.PushMatrix(); // Apply a rotation and draw the torus 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(); // "Erase" the Rotation from before // Apply another rotation, followed by a translation, then draw the sphere 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(); // Restore the previous modleview matrix (the identity matrix) modelViewMatrix.PopMatrix(); modelViewMatrix.PopMatrix(); // Do the buffer Swap glutSwapBuffers(); // Tell GLUT to do it again glutPostRedisplay(); } // Respond to arrow keys by moving the camera frame of reference 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; }
一、GLFrame深入
GLFrame是一个类,它取自GLTools 库,它利用了math3d库,并将所有信息一起进行了存储。使用这样一个像这样的帧来表示一个对象的位置和方向是一种强大的机制。
class GLFrame
{
protected:M3DVector3f vOrigin;// Where am I?M3DVector3f vForward;// Where am I going?M3DVector3f vUp;// Which way is up?public:......};类包含3次重用,甚至允许我们使用GLFrame来代替一个完整的矩阵。void GLMatrixStack::LoadMatrix(GLFrame& frame);void GLMatrixStack::MultMatrix(GLFrame& frame);
void GLMatrixStack::PushMatrix(GLFrame& frame);
二、照相机管理
OpenGL中其实并不存在像照相机变换这样的东西。我们使用照相机作为一种有用的比喻,帮助我们在某些类型的3D环境中管理观察点。如果把照相机想象成种物体,它在空间中具有一些位置和一些特定的方向,就会发现当前的参考帧系统在3D环境中既可以用角色表示,也可以用照相机表示。
为了应用照相机变换,我们使用照相机的角色变换并对它进行反转,这样向后移动照相机就相当于向前移动整个场景。类似地,向左旋转相当于把整个场景向右旋转。为了渲染一个特定的场景,通常使用下图表示的方法:
void GetCameraMatrix(M3DMatrix44f m,bool bRotationOnly =false);
这个函数用来检索条件合适的照相机矩阵。在这里做了一些变通,所以只能获得照相机的旋转变换。C++默认参数允许忽略这一点。但是例如天空盒子的应用,只应用照相机的旋转分量,而场景中其他所有东西都应该通过完整的照相机进行变换。
三、光源简单学习
光源位置也需要转换视觉坐标,但是传递到着色器的矩阵变换是几何图形,而不是光线。整个世界实际上相对于照相机移动的,其中也包括光源。在此示例中应用的是一个点光源,将一个固定光源位置变换到视觉坐标相对简单,而在每个场景中只需进行一次。在后面几章,会深入学习光源与着色器的结合应用。
光源位置的全局坐标存储在vLightPos变量中,其中包括了光源位置的x坐标,y坐标、z坐标和w坐标。使用m3dTransformVector4将光源坐标系乘以照相机矩阵,对光源位置进行变换。例:M3DMatrix vLightPos = {0.0f,10.0f,5.0f,1.0f};
M3DMatrix vLightEyePos;
m3dTransformVector4(vLightPos,vLight,mCamera);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightEyePos, vSphereColor);完成渲染。
四、源码解析
1、变量初始化
//声明50个漂浮的随机球体,允许使用GLFrame类来代替一个完整的矩阵
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];
GLShaderManagershaderManager;// 着色器
GLMatrixStackmodelViewMatrix;// 模型视图矩阵堆栈
GLMatrixStackprojectionMatrix;// 投影矩阵堆栈
GLFrustumviewFrustum;// 平截头体声明
GLGeometryTransformtransformPipeline;// 管线管理声明
GLTriangleBatchtorusBatch; //花托批次图元
GLBatchfloorBatch; //地面批次图元
GLTriangleBatch sphereBatch; //大球的批次图元
GLFrame cameraFrame; //照相机角色
2、void SetupRC() 一次性设置
// 初始化着色器
shaderManager.InitializeStockShaders();
//开启深度测试
glEnable(GL_DEPTH_TEST);
//设置清除窗口的清除颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//绘制一个花托图形,参数:1:花托批次图元;2:外圈半径;3:内圈半径; 4和5是沿着主半径细分数目最大和最小数目。
gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
//绘制球形,参数:1:球形批次图元;2:细分数量(切片)3:突包络数量
gltMakeSphere(sphereBatch, 0.1f,26, 13);
//开始地面批次图元的设置,依次把顶点坐标添加到图元数组的末尾。注意这里y坐标不变,因为只是地面的高度不变。
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);
}//随机生成小球,按照角色的数量循环一次设置角色(小球)的初始坐标,SetOrigin有两个重写方法,可在GLFrame.h中查看
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);
}}
3、void ChangeSize(int nWidth, int nHeight) 窗口改变的回调函数
//根据窗口的大小设置视口
glViewport(0, 0, nWidth, nHeight);
//设置透视投影并将投影矩阵放入到投影矩阵堆栈中
viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//将模型视图矩阵堆栈和投影矩阵堆栈设置管线管理
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
4、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 };//根据运行时间加载的动画变量声明,花托和球形会跟随yRot的改变而沿y轴旋转
static CStopWatchrotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;//清除颜色和深度缓冲区,会触发glClearColor函数
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);
//选择使用平面着色器,为其提供颜色值和地面批次图元,并渲染绘绘制。
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//根据随机球形的数量,循环处理。
/*
1)保存当前的模型视图矩阵,由于上面已经把照相机矩阵压栈了,这时保存的是仍然是照相机矩阵,这里只是在下面循环渲染时不改变栈顶矩阵。
2)与随机球形(GLFrame类型)相乘,把结果放在栈顶,这时栈顶矩阵是随机小球的坐标 。
3)选择使用点光源着色器,设置管线的模型视图矩阵和投影矩阵,设置在视觉坐标系中的光源坐标矩阵,设置随机小球的颜色值
4)渲染绘制图元
5)最后把随机小球顶点坐标出栈,这时栈顶仍是最初的mCamera照相机的矩阵,循环执行。
*/
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();
//创建平移矩阵,注意这是模型视图堆栈的栈顶矩阵是mCamera,即照相机矩阵,也就是根据照相机矩阵设置来进行平移
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
//保存当前矩阵,即平移的mCamera照相机矩阵
modelViewMatrix.PushMatrix();
/*
1)设置模型视图矩阵堆栈栈顶矩阵(平移的照相机矩阵),按照yRot的弧度绕y轴进行旋转。yRot是根据运行时间改变,所以呈现一直旋转的花托
2)选择使用点光源着色器,为其提供模型视图矩阵和投影矩阵(通过管线从获取相应栈顶的矩阵)
3)提交批次图元,绘制渲染图元
4)把之前的栈顶的旋转矩阵出栈,擦除旋转的效果,这是栈顶仍然是mCamera照相机矩阵
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();
//清空模型视图矩阵堆栈,先后把mCamear照相机矩阵和单位矩阵出栈
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
//交换缓冲区
glutSwapBuffers();
// 重新刷新绘制渲染
glutPostRedisplay();5、void SpecialKeys(int key, int x, int y) 方向键的回调函数,控制图形效果
//设置线性移动的参数值和旋转弧度
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
//当按下不同方向键的不同效果,上键是照相机矩阵向前移动(沿z轴变大),下键进行相反操作
if(key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if(key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
//当按下左键照相机矩阵沿着y轴,按照上面定义的弧度值进行旋转,右键进行相反操作
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);
五、小结
本次的源码示例,更深入的学习到了:
1、透视投影的应用
2、照相机的使用和对角色进行移动和旋转。
3、使用GlFrame创建随机小球的坐标,再使用模式视图矩阵堆栈压栈和出栈的方法for循环绘制渲染随机小球。注意三次方法的重用:
void GLMatrixStack::LoadMatrix(GLFrame& frame);void GLMatrixStack::MultMatrix(GLFrame& frame);
void GLMatrixStack::PushMatrix(GLFrame& frame);
4、对模型视图矩阵堆栈的多次重复使用,要理解堆栈的压栈和出栈机制。PushMatrix保存当前矩阵,第一次使用时默认是单位矩阵。PopMatrix把栈顶矩阵出栈。
MultMatrix传入一个矩阵与栈顶矩阵相乘,再把得到的结果压入到栈顶。
5、遇到不理解的,可以寻根究底,查找使用的库的源码理解。
学习到此,前四章的知识也就完全过了一遍,后面会补上第三章的Primitives.cpp源码示例的学习及源码解析。接下来开始进行第五章的学习,纹理贴图的知识。