一、案例
基本的图元装配方式 在之前的文章介绍过。
这个案例是对这些基本的图元进行操作 以及用这些图元完成一些简单的图形。
流程:
main 函数:程序入口
int main(int argc,char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
//申请一个颜色缓存区,深度缓存区,双缓存区,模板缓存区
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//设置windo的名称
glutCreateWindow("GL_POINTS");
//注册回调函数
glutReshapeFunc(changeSize);
//点击空格 调用的函数
glutKeyboardFunc(keyPressFunc);
//上下左右调用的函数
glutSpecialFunc(SpecialKeys);
//显示函数
glutDisplayFunc(RenderScene);
//判断一下是否能初始化glew库,确保项目能正常使用OpenGL 框架
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
setupRC();
glutMainLoop();
return 0;
}
changeSize():用于设置视口和投影方式
void changeSize(int w, int h)
{
glViewport(0, 0, w, h);
//创建投影矩阵
viewFrustum.SetPerspective(35.0f, float(w)/float(h), 1.0f, 500.0f);
//将它载入投影矩阵堆栈中。
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//初始化模型视图矩阵堆栈,压入单元矩阵
modelViewMatrix.LoadIdentity();
}
setupRC():初始化图形数据配置(顶点数据,图元链接方式,矩阵观察者,相应的矩阵设置)
void setupRC()
{
//清屏
glClearColor(0.9, 0.9, 0.9, 1.0);
//初始化着色管理器
shaderManager.InitializeStockShaders();
//开启深度测试:这个功能在后面讲解。
glEnable(GL_DEPTH_TEST);
//将模型视图矩阵和投影矩阵放到变换管道中,能帮助快速进行矩阵相乘
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
//观察者视角往前移动 -15
cameraFrame.MoveForward(-15.0f);
//定义一些点,三角形形状。
GLfloat vCoast[9] = {
3,3,0,0,3,0,3,0,0
};
pointBatch.Begin(GL_POINTS, 3);
//顶点数据放入到顶点着色器中
pointBatch.CopyVertexData3f(vCoast);
pointBatch.End();
lineBatch.Begin(GL_LINES, 3);
lineBatch.CopyVertexData3f(vCoast);
lineBatch.End();
lineStripBatch.Begin(GL_LINE_STRIP, 3);
lineStripBatch.CopyVertexData3f(vCoast);
lineStripBatch.End();
lineLoopBatch.Begin(GL_LINE_LOOP, 3);
lineLoopBatch.CopyVertexData3f(vCoast);
lineLoopBatch.End();
// 通过三角形创建金字塔
GLfloat vPyramid[12][3] = {
-2.0f, 0.0f, -2.0f,
2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, -2.0f,
2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, 2.0f,
0.0f, 4.0f, 0.0f,
-2.0f, 0.0f, 2.0f,
-2.0f, 0.0f, -2.0f,
0.0f, 4.0f, 0.0f
};
//GL_TRIANGLES 每3个顶点定义一个新的三角形
triangleBatch.Begin(GL_TRIANGLES, 12);
triangleBatch.CopyVertexData3f(vPyramid);
triangleBatch.End();
// 三角形扇形--六边形
GLfloat vPoints[100][3];
int nVerts = 0;
//半径
GLfloat r = 3.0f;
//原点(x,y,z) = (0,0,0);
vPoints[nVerts][0] = 0.0f;
vPoints[nVerts][1] = 0.0f;
vPoints[nVerts][2] = 0.0f;
//M3D_2PI 就是2Pi 的意思,就一个圆的意思。 绘制圆形
for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) {
//数组下标自增(每自增1次就表示一个顶点)
nVerts++;
/*
弧长=半径*角度,这里的角度是弧度制,不是平时的角度制
既然知道了cos值,那么角度=arccos,求一个反三角函数就行了
*/
//x点坐标 cos(angle) * 半径
vPoints[nVerts][0] = float(cos(angle)) * r;
//y点坐标 sin(angle) * 半径
vPoints[nVerts][1] = float(sin(angle)) * r;
//z点的坐标
vPoints[nVerts][2] = -0.5f;
}
// 结束扇形 前面一共绘制7个顶点(包括圆心)
//添加闭合的终点
nVerts++;
vPoints[nVerts][0] = r;
vPoints[nVerts][1] = 0;
vPoints[nVerts][2] = 0.0f;
// 加载!
//GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形
triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);
triangleFanBatch.CopyVertexData3f(vPoints);
triangleFanBatch.End();
//三角形条带,一个小环或圆柱段
//顶点下标
int iCounter = 0;
//半径
GLfloat radius = 3.0f;
//从0度~360度,以0.3弧度为步长
for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f)
{
//或许圆形的顶点的X,Y
GLfloat x = radius * sin(angle);
GLfloat y = radius * cos(angle);
//绘制2个三角形(他们的x,y顶点一样,只是z点不一样)
vPoints[iCounter][0] = x;
vPoints[iCounter][1] = y;
vPoints[iCounter][2] = -0.5;
iCounter++;
vPoints[iCounter][0] = x;
vPoints[iCounter][1] = y;
vPoints[iCounter][2] = 0.5;
iCounter++;
}
// 关闭循环
printf("三角形带的顶点数:%d\n",iCounter);
//结束循环,在循环位置生成2个三角形
vPoints[iCounter][0] = vPoints[0][0];
vPoints[iCounter][1] = vPoints[0][1];
vPoints[iCounter][2] = -0.5;
iCounter++;
vPoints[iCounter][0] = vPoints[1][0];
vPoints[iCounter][1] = vPoints[1][1];
vPoints[iCounter][2] = 0.5;
iCounter++;
// GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形
triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter);
triangleStripBatch.CopyVertexData3f(vPoints);
triangleStripBatch.End();
}
RenderScene():处理图形渲染,系统和手动触发,每次渲染前需要清除缓存。
void RenderScene(void)
{
//清除缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//压栈
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
// 矩阵乘以矩阵堆栈的顶部矩阵, 相乘的结果随后结果存储在堆栈的顶部.
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
//只要使用GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载, 用来使用GLShaderManager的使用,或者是获取顶部矩阵的顶点副本数据.
objectFrame.GetMatrix(mObjectFrame);
//矩阵乘以矩阵.
modelViewMatrix.MultMatrix(mObjectFrame);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
switch (nStep) {
case 0:
glPointSize(4.0f);
pointBatch.Draw();
break;
case 1:
glLineWidth(4.0f);
lineBatch.Draw();
break;
case 2:
lineStripBatch.Draw();
glLineWidth(1.0f);
break;
case 3:
lineLoopBatch.Draw();
glLineWidth(1.0);
break;
case 4:
DrawWireFramedBatch(&triangleBatch);
break;
case 5:
DrawWireFramedBatch(&triangleStripBatch);
break;
case 6:
DrawWireFramedBatch(&triangleFanBatch);
break;
}
//还原到以前的模型视图矩阵(单位矩阵)
modelViewMatrix.PopMatrix();
//缓冲区交换
glutSwapBuffers();
}
//处理图形的填充和边框的处理。
void DrawWireFramedBatch(GLBatch *pBatch)
{
//绿色
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
pBatch -> Draw();
/*-----------边框部分-------------------*/
/*
glEnable(GLenum mode); 用于启用各种功能。功能由参数决定
参数列表:http://blog.csdn.net/augusdi/article/details/23747081
注意:glEnable() 不能写在glBegin() 和 glEnd()中间
GL_POLYGON_OFFSET_LINE 根据函数glPolygonOffset的设置,启用线的深度偏移
GL_LINE_SMOOTH 执行后,过虑线点的锯齿
GL_BLEND 启用颜色混合。例如实现半透明效果
GL_DEPTH_TEST 启用深度测试 根据坐标的远近自动隐藏被遮住的图形(材料
glDisable(GLenum mode); 用于关闭指定的功能 功能由参数决定
*/
//黑色边框
glPolygonOffset(-1.0f, -1.0f); //偏移深度, 在同一位置要绘制填充和边线. 会产生z冲突, 所以要偏移
glEnable(GL_POLYGON_OFFSET_LINE);
// 画反锯齿,让黑边好看些
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(2.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
SpecialKeys(): 监听键盘上下左右
void SpecialKeys(int key, int x, int y)
{
if (key == GLUT_KEY_UP) {
//绕x轴转
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0, 0.0, 0.0);
}
if (key == GLUT_KEY_DOWN) {
//绕x轴转
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0, 0.0, 0.0);
}
if (key == GLUT_KEY_LEFT) {
//绕x轴转
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0, 1.0, 0.0);
}
if (key == GLUT_KEY_RIGHT) {
//绕x轴转
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0, 1.0, 0.0);
}
glutPostRedisplay();
}
keyPressFunc():监听空格键
void keyPressFunc(unsigned char key, int x, int y)
{
if (key == 32) {
nStep++;
if (nStep > 6) {
nStep = 0;
}
}
switch (nStep) {
case 0:
glutSetWindowTitle("GL_POINTS");
break;
case 1:
glutSetWindowTitle("GL_LINES");
break;
case 2:
glutSetWindowTitle("GL_LINE_STRIP");
break;
case 3:
glutSetWindowTitle("GL_LINE_LOOP");
break;
case 4:
glutSetWindowTitle("GL_TRIANGLES");
break;
case 5:
glutSetWindowTitle("GL_TRIANGLE_STRIP");
break;
case 6:
glutSetWindowTitle("GL_TRIANGLE_FAN");
break;
default:
break;
}
glutPostRedisplay();
}
二、关于矩阵的堆栈
这个案例相比于之前的案例多出了一个新的知识点:矩阵的压栈与出栈。
2.1:初始化一个单元矩阵,矩阵堆栈中默认是存在一个单元矩阵的,但是也可以自己初始化一个。此时堆栈中存在一个矩阵。
modelViewMatrix.LoadIdentity();
2.2:压栈:把栈顶的矩阵复制一份后再压到栈顶。此时堆栈中有两个单元矩阵
modelViewMatrix.PushMatrix();
2.3:矩阵相乘:
- 获取一个观察者矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
- 让观察者矩阵乘以矩阵堆栈栈顶矩阵, 相乘得到一个新的矩阵,再压入栈顶:此时堆栈中为一个原单元矩阵,一个观察者矩阵。
//矩阵乘以矩阵.
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
- 还是矩阵相乘的过程,取出刚刚入栈的观察者矩阵乘以物体矩阵,得到一个新的模型视图矩阵,再压入栈顶。
/*只要使用GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载,
用来使用GLShaderManager的使用,或者是获取顶部矩阵的顶点副本数据.*/
objectFrame.GetMatrix(mObjectFrame);
//矩阵乘以矩阵.
modelViewMatrix.MultMatrix(mObjectFrame);
2.4:出栈:将视图模型矩阵pop出栈
//还原到以前的模型视图矩阵(单位矩阵,就是恢复成原来还没有处理矩阵前的状态)
modelViewMatrix.PopMatrix();
图1 by Style_月月