几何图形变换测试源码实例:
// GeoTest.cpp
// OpenGL SuperBible
// Demonstrates OpenGL Backface culling, depth testing, and polygon mode
// Program by Richard S. Wright Jr.
#include
// OpenGL toolkit
#include
#include
#include
#include
#include
#ifdef __APPLE__ #include
#else #define FREEGLUT_STATIC #include
#endif GLFrame viewFrame; GLFrustum viewFrustum; GLTriangleBatch torusBatch; GLMatrixStack modelViewMatix; GLMatrixStack projectionMatrix; GLGeometryTransform transformPipeline; GLShaderManager shaderManager; // Flags for effects int iCull = 0; int iDepth = 0; /// // Reset flags as appropriate in response to menu selections void ProcessMenu(int value) { switch(value) { case 1: iDepth = !iDepth; break; case 2: iCull = !iCull; break; case 3: glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); break; case 4: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); break; case 5: glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); break; } glutPostRedisplay(); } // Called to draw scene void RenderScene(void) { // Clear the window and the depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Turn culling on if flag is set if(iCull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); // Enable depth testing if flag is set if(iDepth) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); modelViewMatix.PushMatrix(viewFrame); GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f }; //shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vRed); shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed); torusBatch.Draw(); modelViewMatix.PopMatrix(); glutSwapBuffers(); } // This function does any needed initialization on the rendering // context. void SetupRC() { // Black background glClearColor(0.3f, 0.3f, 0.3f, 1.0f ); shaderManager.InitializeStockShaders(); viewFrame.MoveForward(7.0f); // Make the torus gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26); glPointSize(4.0f); } void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_DOWN) viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0f); if(key == GLUT_KEY_LEFT) viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f); if(key == GLUT_KEY_RIGHT) viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f); // Refresh the Window glutPostRedisplay(); } void ChangeSize(int w, int h) { // Prevent a divide by zero if(h == 0) h = 1; // Set Viewport to window dimensions glViewport(0, 0, w, h); viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f); projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix()); transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix); } /// // Main entry point for GLUT based programs 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("Geometry Test Program"); glutReshapeFunc(ChangeSize); glutSpecialFunc(SpecialKeys); glutDisplayFunc(RenderScene); // Create the Menu glutCreateMenu(ProcessMenu); glutAddMenuEntry("Toggle depth test",1); glutAddMenuEntry("Toggle cull backface",2); glutAddMenuEntry("Set Fill Mode", 3); glutAddMenuEntry("Set Line Mode", 4); glutAddMenuEntry("Set Point Mode", 5); glutAttachMenu(GLUT_RIGHT_BUTTON); GLenum err = glewInit(); if (GLEW_OK != err) { fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err)); return 1; } SetupRC(); glutMainLoop(); return 0; }
这段源码包含了许多后面章节(第四章)的知识点。这里主要是对源码的学习,所以会根据源码列出一些库函数的说明、概念的理解及简单的3D图形数学知识。
概念概览:
模型:在场景中移动物体(静止或动态)。
视图(视点):指定观察者或者相机的位置。
模型视图:描述视图和模型之间的二元性。
模型视图矩阵:是一个4x4矩阵,表示变换后的坐系,可以用来放置对象和确定对象的方向。实际上是在视觉坐标中移动几何图形。
投影矩阵:可更改物体在窗口显示的形状,有正投影和透视投影,例如在Unity开发中切换2D和3D场景的时候,会显示2D和3D不同的形状。
模型视图投影矩阵(ModelviewProjection):将模型的顶点坐标从局部坐标系转换到规范立方坐标系,也可理解为把模型从三维世界到二维屏幕中的一个重要变换。
投影:从3D到2D。分为正投影和透视投影。应用:改变视景体的大小或重新设置它的形状。
窗口:以像素为单位进行度量,可以理解为Windows窗口。
视口:把绘图坐标映射到窗口坐标,就是窗口内部用于绘制裁剪区域的客户区域。这是一种伪变换,只是对窗口上的最终输出进行缩放。
可以结合ModelViewProjection.cpp理解 http://blog.youkuaiyun.com/perseverancep/article/details/72793705
一、类型学习
1、GLFrame
为了在3D场景中表示任何对象的位置和方向,可以用一个4x4矩阵表示它的变换。但是,直接操纵矩阵显然有点笨拙,所以设法用更简洁的方式表示空间中的坐标和方向。固定的物体通常不进行变换,它的定点通常准确地指定几何图形在空间中应该怎样进行绘制。但是,在场景中移动的物体通常称为“角色”,“角色”有自己的变换,而且“角色”的变换常常不仅与全局坐标系(视觉坐标系)有关,也与其他“角色”有关。每个有自己的变换角色都被称为有自己的参考帧,或者本地对象坐标系。在本地和全局坐标系之间的变换常常是非常有用的。那么可以用一种简单的方式表示参考帧,就是使用一个数据结构,这个类中包含空间中的一个位置、一个指向前方的向量和一个指向上方的向量。使用这些向量,就可以在空间中唯一确定一个给定的位置和方向。所以也可以使用GLFrame类来实现“角色”的一些变换等功能。在后面的学习过程中介绍GLFrame一些函数的使用。
2、GLFrustum(平截头体)
此类作为投影矩阵的容器。
1)在正投影中,沿着视景体的边界进行剪裁,所有范围内的所有东西都会被显示在屏幕上,不存在照相机或视点坐标系的概念。调用下面方法完成上述工作:
GLFrustum::SetOrthographic(GLfloat xMin,GLfloat xMax,GLfloat yMin,
GLfloat xMax, GLfloat zMin,GLfloat zMax, )
2)透视投影会进行透视除法对距离观察者很远的对象进行缩短和收缩。视景体的前面比绘制在视景体的背面显得更大。下图所示:
GLFrustum类通过调用SetPerspective方法创建一个平截头体:
GLFrustum::SetPerspective(float fFov,float fAspect,float fNear,float fFar);
参数:垂直方向上的视场角度;窗口的宽度与高度的纵横比:宽度/高度;到近、远剪裁面之间的距离。
在后面的学习过程中介绍GLFrame一些函数的使用。
3、GLTriangleBatch
GLBatch类只是一个便利类,可作为图元简单批次的容器使用,虽然它提供了一种包含和提交几何图形的快捷方式,但这并不代表它拥有OpenGL在图形处理方面的全部范围和能力,GLTriangleBatch同样也是一个图元批次的容器类,可以查看一下GLTriangleBatch.cpp的源码参考下。
4、GLMatrixStack
矩阵堆栈,便于在建立复杂的场景中,处理大量的用户构造的矩阵和有效的管理代码。此类允许指定堆栈的最大深度,默认的堆栈深度是64。矩阵堆栈在初始化时已经在堆栈中包含了单位矩阵。
GLMatrixStack::GLMatrixStack(int iStackDepth = 64);//初始化堆栈
void GLMatrixStack::LoadIdentity(void)
通过调用在栈的顶部载入这个单位矩阵
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m) //参数:4x4浮点型矩阵
通过调用在栈的顶部载入任何矩阵
void GLMatrixStack::LoadIdentity(void)
通过调用在栈的顶部载入这个单位矩阵
用一个矩阵乘以矩阵堆栈的顶部矩阵,相乘结果将存储在堆栈的顶部
const M3DMatrix44f &GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
获得矩阵堆栈顶部的值。可进行两次重载,以适应GLShaderManager的使用,或者仅仅是获得顶部矩阵的副本。
压栈:
void GLMatrixStatck::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix); //复制了当前矩阵并将新的值放入了堆栈的顶部
出栈:
void GLMatrixStack::PopMatrix(void);
仿射变换:
void MatrixStatck::Rotate(GLfloat angle,GLfloat x,GLfloat y, GLfloat z); //旋转,注意接受角度值不是弧度值
void MatrixStatck::Translate(GLfloat angle,GLfloat x,GLfloat y, GLfloat z);//平移
void MatrixStatck::Scale(GLfloat angle,GLfloat x,GLfloat y, GLfloat z); //缩放
5、GLGeometryTransform
管线管理。很多时候,我们需检索模型视图矩阵和投影矩阵将其相乘得到模型视图投影矩阵。还有一种矩阵是正规矩阵,用来进行光照计算,也可以从模型视图矩阵推导出来。那么GLGeometryTransform用来为我们跟踪记录这两种矩阵堆栈,并快速检索模型视图投影矩阵的顶部或正规矩阵堆栈的顶部。
二、源码分析
1、 void ChangeSize(int w ,int h)
回顾一下,glutReshapeFunc(ChangeSize) 是窗口改变大小设置回调函数,以便能够设置视点。
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 100.0f);
创建头投影矩阵并设置视场角度是35度;窗口的宽度与高度的纵横比:宽度/高度;到近、远剪裁面之间的距离设置为1和100。
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
将得到的投影矩阵加载到投影矩阵的堆栈中。
transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix);
设置变换管线管理,以使用两个矩阵堆栈。
2、 glutSpecialFunc(SpecialKeys);
回顾一下,glutSpecialFunc(SpecialKeys) 是设置按键的回调函数。
void SpecialKeys(int key, int x, int y);
if(key == GLUT_KEY_UP)
viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
当按下上键,沿x轴以-5的角度旋转“角色”的世界坐标
viewFrame.RotateWorld(m3dDegToRad(-5.0), 1.0f, 0.0f, 0.0f);
if(key == GLUT_KEY_DOWN)
viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0);
当按下下键,沿x轴以+5的角度旋转“角色”的世界坐标
viewFrame.RotateWorld(m3dDegToRad(5.0), 1.0f, 0.0f, 0.0);
当按下下键,沿x轴以+5的角度旋转“角色”的世界坐标
if(key == GLUT_KEY_LEFT)
viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
viewFrame.RotateWorld(m3dDegToRad(-5.0), 0.0f, 1.0f, 0.0f);
当按下左键,沿y轴以-5的角度旋转“角色”的世界坐标
if(key == GLUT_KEY_RIGHT)
viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
viewFrame.RotateWorld(m3dDegToRad(5.0), 0.0f, 1.0f, 0.0f);
当按下右键,沿y轴以+5的角度旋转“角色”的世界坐标
glutPostRedisplay();
刷新窗口,重新渲染(RenderScene)
注意:在刷新窗口之前,只是更改“角色”的世界坐标,并未设置投影矩阵。投影矩阵是在ChangeSize方法中设置,当刷新窗口后,可以看到几何图形的动画效果。
3、 glutDisplayFunc(RenderScene);
回顾一下,glutDisplayFunc(RenderScene)是注册渲染的回调函数。
void RenderScene(void)
if(iCull)
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
剔除表面侧测试:右键鼠标单击菜单栏中的选项可以设置是否开启剔除表面。
if(iDepth)
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
glEnable(GL_DEPTH_TEST);
else
glDisable(GL_DEPTH_TEST);
深度测试:右键鼠标单击菜单栏中的后两个选项可以设置是否开启深度测试。
注意:这里只是在设置是否开启的标志位。在后面的源码中设置了一个右键鼠标的显示的菜单栏,选择后才是真正的设置开启与关闭的切换。
modelViewMatix.PushMatrix(viewFrame);
将“角色”的参考帧坐标系压栈到模型视图矩阵堆栈中
注意:在ChangeSize函数中,transformPipeline.SetMatrixStacks(modelViewMatix, projectionMatrix)函数已经将模型视图矩阵和投影矩阵设置管线管理。
GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
shaderManager.UseStockShader(GLT_SHADER_DEFAULT_LIGHT, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vRed);
选择默认光源着色器,并提供这个着色器的Uniform值是vRed(红色),其中第2、3个参数,是从管线管理的堆栈中获取到的模型视图矩阵和投影矩阵。
torusBatch.Draw();
将几何图形提交到着色器。
modelViewMatix.PopMatrix();
将模型视图矩阵出栈。因为在开始渲染时保存矩阵状态,在结束时使用PopMatrix恢复它,是一种很好的做法,这样就不必在每一次渲染时都重载戴文矩阵了。
注意:modelViewMatrix在初始化时已经默认设置为单位矩阵,所以在做压栈和出栈的操作之后,modelViewMatrix栈顶还是单位矩阵。
4、菜单栏的定义和操作
glutCreateMenu(ProcessMenu);
创建一个菜单栏,传入一个回调函数,类型是函数指针。
glutAddMenuEntry("Toggle depth test",1);
glutAddMenuEntry("Toggle cull backface",2);
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
glutAddMenuEntry("Toggle cull backface",2);
glutAddMenuEntry("Set Fill Mode", 3);
glutAddMenuEntry("Set Line Mode", 4);
glutAddMenuEntry("Set Point Mode", 5);
给菜单里添加条目,参数1是添加的菜单条目的名称;参数2是返回给ProcessMenu回调函数的参数,void ProcessMenu在下面。
glutAttachMenu(GLUT_RIGHT_BUTTON);
将菜单栏和鼠标右键关联起来
void ProcessMenu(int value)
{
{
switch(value){
case 1://当再次右键菜单栏的1条目时候,切换开启和关闭深度测试
iDepth = !iDepth;
break;
case 1://当再次右键菜单栏的1条目时候,切换开启和关闭深度测试
iDepth = !iDepth;
break;
case 2://当再次右键菜单栏的2条目时候,切换开启和关闭剔除表面测试
iCull = !iCull;
break;
iCull = !iCull;
break;
case 3:
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//控制多边形的显示方式,适用于物体的所有表面,采用填充显示方式。
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//控制多边形的显示方式,适用于物体的所有表面,采用填充显示方式。
break;
case 4:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//控制多边形的显示方式,适用于物体的所有表面,采用轮廓显示方式。
break;
case 5:
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); //控制多边形的显示方式,适用于物体的所有表面,采用点显示方式。
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); //控制多边形的显示方式,适用于物体的所有表面,采用点显示方式。
break;
}
glutPostRedisplay(); //刷新窗口,会重新调用回调函数RenderScene函数
}
5、void SetupRC()
glClearColor(0.3f, 0.3f, 0.3f, 1.0f );
设置背景颜色,在RenderScene函数中,设置了清除缓冲后,该方法就会执行,用当前清除颜色设置窗口背景。
viewFrame.MoveForward(7.0f);
设置“角色”的参考帧坐标系沿着z轴(向前)7个像素值。
注意:可以看下计算机图形学中的坐标系的知识。
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
绘制花托形状的几何图形,参数是:批次图元;外边缘半径;内边缘半径;沿着主半径的细分单元的最大和最小数目。
glPointSize(4.0f);
指定栅格化点的直径,即这是点的像素大小。
注意:点是最简单的图元,每个特定的顶点在屏幕上都仅仅是一个单独的点。
三、小结
这次源码示例的学习,或多或少囊括了蓝宝书的前面4章的知识,同样也需要我们知道一点基础的线性代数和数学的知识。在学习源码的时候,可以查阅glut和gltools的源码
文件进行查阅理解:GLTools.h、GLMatrixStack.h、GLFrame.h、glut/glut.h等等。我在学习时,先浏览了一下第三章的内容,但是是一知半解,所以就一遍看源码示例再结合前
4章的整体知识进行理解,希望对大家有帮助。