OpenGL 03案例 ----点、线、面、金字塔、伞、手环(一)
前面我们了解如何绘制点和线,甚至用GL_LINE_LOOP来绘制一些封闭的多边形。但这些终归只是线条而已,而不能对它进行填充颜色。这时候我们需要一个封闭的图形,用来填充颜色或纹理,构建实体对象。
三角形
这是OpenGL支持的最简单的实体多边形了。其他多边形都是由三角形构成。下面通过三角形构建一个金字塔。
金字塔
// 通过三角形创建金字塔
GLfloat vPyramid[12][3] = {
-2.0f, 0.0f, -2.0f,
2.0f, 0.0f, -2.0f,
0.0, 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 每三个顶点定义一个新的三角形
triangleBatch.Begin(GL_TRIANGLES, 12);
triangleBatch.CopyVertexData3f(vPyramid);
triangleBatch.End();
在 SetupRC
函数中 定义4个三角形顶点,并将之保存到GCD缓存区中。下面我们需要使用着色器对三角形填充颜色。为了看得更加清晰,这里我们还需要将它的边框设置成黑色线条。这里,抽出一个公用函数,用来填充颜色,设置边框颜色。
void DrawWireFramedBatch(GLBatch *pBatch) {
/*--------- 画绿色部分 -----------*/
/* GLShaderManager 中Uniform值————平面着色器
参数1:平面着色器
参数2:运行为几何图形变换指定一个4 * 4变换矩阵
————transformPipeline 变换管线(指定了2个矩阵堆栈)
参数3:颜色值
*/
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);
glEnable(GL_POLYGON_OFFSET_LINE);
// 画反锯齿,让黑边好看些
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 绘制线框几何黑色版 三种模式,实心,边框,点,可以作用的正面,背面,或两者
// 通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 设置线条宽度
glLineWidth(2.5f);
/* GLShaderManager 中的Uniform值————平面着色器
参数1:平面着色器
参数2:运行为几何图形变换指定一个4 * 4变换矩阵
--transformPipeline.GetModelViewProjectionMatrix() 获取的GetMatrix函数就可以获得矩阵堆栈顶部的值
参数3:颜色值(黑色)
*/
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
// 复原原本的设置
// 通过调用glPolygonMode将多边形正面或背面设置为全部填充模式
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);
}
主要的绘制代码已经写好了,接下来的案例(如手环)也会用到这部分代码,我就不会再重复写了。
这里采用了深度测试解决正背面隐藏的问题,后面会详细的讲到
旋转图形
为了更好的展示金字塔(以及下面的其他图形),我们需要对金字塔进行旋转展示。
接下来需要用到变换管道、模型矩阵、投影矩阵的知识。首先我们定义几个全局变量。
GLMatrixStack modelViewMatrix; // 模型矩阵堆栈
GLMatrixStack projectionMatrix; // 投影矩阵堆栈
GLFrame cameraFrame;
GLFrame objectFrame;
GLFrustum viewFrustum;
// 几何变换管道
GLGeometryTransform transformPipeline;
接下来需要在SetupRC
函数中,利用变换管道将模型矩阵和投影矩阵关联起来。设置视角,使得图形看起来更加有立体感。
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
cameraFrame.MoveForward(-30.0f);
ChangeSize
函数也需要变化
// 创建投影矩阵,并将它载入投影矩阵堆栈中
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
// 调用顶部载入单元矩阵
modelViewMatrix.LoadIdentity();
接下来就是重点,在绘制函数RenderScene
中将变化了的矩阵传入到着色器中
// 压栈
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
//矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后存储在堆栈的顶部
modelViewMatrix.MultMatrix(mCamera);
M3DMatrix44f mObjectFrame;
// 只要使用 GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。用来使用GLShaderManager的使用。或者是获取顶部矩阵的顶点副本数据
objectFrame.GetMatrix(mObjectFrame);
// 矩阵乘以矩阵堆栈的顶部矩阵,相乘的结果随后存储在堆栈的顶部
modelViewMatrix.MultMatrix(mObjectFrame);
/*
GLShaderManager 中的Uniform值--平面着色器
参数1:平面着色器
参数2:运行为集合图形变换指定一个4*4变换矩阵
--transformPipeline.GetModelViewProjectionMatrix()获取的
GetMatrix函数就可以获得矩阵堆栈顶部的值
参数3:颜色值(黑色)
*/
//2.设置一组浮点数来表示红色
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
别忘了最后需要调用Draw
函数后,推出栈顶数据。
modelViewMatrix.PopMatrix();
最后在特殊函数SpecialKeys
中,添加如下代码:
// 特殊键位处理(上、下、左、右)
void SpecialKeys(int key, int x, int y) {
if (key == GLUT_KEY_UP) {
// 围绕一个指定的x、y、z轴选旋转
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_DOWN) {
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
}
if (key == GLUT_KEY_LEFT) {
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
}
if (key == GLUT_KEY_RIGHT) {
objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
}
glutPostRedisplay();
}
使用RotateWorld
传入弧度。这边弧度需要自己去计算,所以使用glTools中封装好的宏m3dDegToRad
进行转化。后面三个参数代表的x,y,z,作用就是在哪个轴上进行旋转。如上代码,我们会根据键盘的上下左右来围绕x,y轴进行旋转。
伞
在SetupRC
函数中定义伞的各个顶点。
// 三角形扇形————六边形
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) {
// 数组下标自增 (每自增一次就表示一个顶点)
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个顶点 (包括圆心)
// 添加闭合的重点
// 课程添加演示:屏蔽148-150行代码,并把绘制节点改为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();
- 将360度分为六等分,也就是说要构建6个三角形。以原点(0,0,0)为圆点,使用for循环创建6个三角形对应的顶点。
- 最后使用
GL_TRIANGLE_FAN
这个扇形排列的规则来进行图元装配。 - 着色这块还是用上面的使用到的
DrawWireFramedBatch
函数就行绘制。
DrawWireFramedBatch(&triangleFanBatch);
手环
在SetupRC
函数中定义手环的顶点
// 三角形条带,一个小环或圆柱段
// 顶点喜爱宝
int iCounter = 0;
// 半径
GLfloat radius = 3.0f;
// 从0度~360度,以0.e弧度为步长
for(GLfloat angle = 0.0f;angle <= (2.0f * M3D_PI); angle += 0.3) {
// 或许圆形的顶点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.5f;
iCounter++;
vPoints[iCounter][0] = x;
vPoints[iCounter][1] = y;
vPoints[iCounter][2] = 0.5f;
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();
- 使用for循环,根据弧度值的变化,求出对应顶点的 x,y,z值
- 为了让手环闭合 设置顶点数组倒数第一、第二的值和第一、第二一样。
- 使用GL_TRIANGLE_STRIP来进行图元装配
- 最后在
RenderScene
进行绘制。
这边还有几个技术点,比如深度测试,后面会着重讲解到。
这样金字塔、伞、手环在这就制作完成了。如果想查看完整代码点击这里下载。