OpenGL 五 - 案例解析-绘制、偏移、颜色混合 + 观察者与物体的2种移动方式 + 矩阵堆栈
/* 最新更新时间 2020-07-20
增加:矩阵压栈
*/
/*****************************************************************/
我们视觉上的物体的移动有2种方式:
1、物体移动,观察者(眼睛)不动;
2、物体不动,观察者移动。
案例代码分析 -- 点线、金字塔、六边形、圆环的绘制
一、物体移动
1、物体移动,观察者不动
1 // 绘制
2 void RenderScene(void) {
3
4 // Clear the window with current clearing color
5 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);// 清空
6
7 // 压栈
8 modelViewMatrix.PushMatrix();
9
10 // 观察者矩阵
11 M3DMatrix44f mCamera;
12 cameraFrame.GetCameraMatrix(mCamera);
13 // 单元矩阵 * 观察者矩阵 --> new观察者矩阵
14 // 矩阵 * 矩阵堆栈的顶部矩阵,相乘的结果随后 存储在堆栈的顶部
15 modelViewMatrix.MultMatrix(mCamera);// new观察者矩阵 压栈
16
17 // 物体矩阵
18 M3DMatrix44f mObjectFrame;
19 // 只要使用 GetMatrix 函数就可以获取矩阵堆栈顶部的值,这个函数可以进行2次重载。用来使用 GLShaderManager 的使用,或者是获取顶部矩阵的顶点副本数据
20 objectFrame.GetMatrix(mObjectFrame);
21 // new观察者矩阵 * 物体矩阵 --> 模型视图矩阵,相乘的结果随后 存储在堆栈的顶部
22 modelViewMatrix.MultMatrix(mObjectFrame);// 模型视图矩阵 压栈
23
24 /*** GLShaderManager 中的 Uniform 值——平面着色器
25 参数1:平面着色器
26 参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
27 --transformPipeline.GetModelViewProjectionMatrix() 获取的
28 GetMatrix函数就可以获得矩阵堆栈顶部的值
29 参数3:颜色值(黑色)
30 */
31 shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
32
33 switch(nStep) {
34 case 0:
35 // 设置点的大小
36 glPointSize(4.0f);
37 pointBatch.Draw();
38 glPointSize(1.0f);
39 break;
40 case 1:
41 // 设置线的宽度
42 glLineWidth(2.0f);
43 lineBatch.Draw();
44 glLineWidth(1.0f);
45 break;
46 case 2:
47 glLineWidth(2.0f);
48 lineStripBatch.Draw();
49 glLineWidth(1.0f);
50 break;
51 case 3:
52 glLineWidth(2.0f);
53 lineLoopBatch.Draw();
54 glLineWidth(1.0f);
55 break;
56 case 4:
57 DrawWireFramedBatch(&triangleBatch);
58 break;
59 case 5:
60 DrawWireFramedBatch(&triangleStripBatch);
61 break;
62 case 6:
63 DrawWireFramedBatch(&triangleFanBatch);
64 break;
65 }
66
67 // 出栈 还原到初始的模型视图矩阵(单位矩阵)
68 modelViewMatrix.PopMatrix();
69
70 // 进行缓冲区交换
71 glutSwapBuffers();
72 }
上面代码中的 2 次压栈,单元矩阵 -- 观察者矩阵 相乘 --> new观察者矩阵 -- 物体矩阵 相乘 --> 模型视图矩阵,平面着色器绘制。
物体的展示做了如下的流程转换: 物体坐标(object space) --> 世界坐标 --> 观察者坐标 --> 裁剪坐标(clip space),再经过OpenGL转化 --> 规范化设备坐标(DNC space) --> 显示在屏幕上。
2、物体不动,观察者动
主要代码
1 // 绘制
2 void RenderScene(void) {// 观察者移动
3
4 // Clear the window with current clearing color
5 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
6
7 // 压栈
8 modelViewMatrix.PushMatrix(objectFrame);// 将观察者矩阵压入视图模型矩阵中
9 /* GLShaderManager 中的Uniform 值——平面着色器
10 参数1:平面着色器
11 参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
12 --transformPipeline.GetModelViewProjectionMatrix() 获取的
13 GetMatrix函数就可以获得矩阵堆栈顶部的值
14 参数3:颜色值(黑色)
15 */
16 shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
17
18 switch(nStep) {
19 case 0:
20 //设置点的大小
21 glPointSize(4.0f);
22 pointBatch.Draw();
23 glPointSize(1.0f);
24 break;
25 case 1:
26 //设置线的宽度
27 glLineWidth(2.0f);
28 lineBatch.Draw();
29 glLineWidth(1.0f);
30 break;
31 case 2:
32 glLineWidth(2.0f);
33 lineStripBatch.Draw();
34 glLineWidth(1.0f);
35 break;
36 case 3:
37 glLineWidth(2.0f);
38 lineLoopBatch.Draw();
39 glLineWidth(1.0f);
40 break;
41 case 4:
42 DrawWireFramedBatch(&triangleBatch);
43 break;
44 case 5:
45 DrawWireFramedBatch(&triangleFanBatch);
46 break;
47 case 6:
48 DrawWireFramedBatch(&triangleStripBatch);
49 break;
50 }
51
52 //还原到以前的模型视图矩阵(单位矩阵)
53 modelViewMatrix.PopMatrix();
54
55 // 进行缓冲区交换
56 glutSwapBuffers();
57 }
疑问:为什么 观察者移动时只把一个观察者矩阵压栈,而没有像物体移动时进行矩阵混合计算呢???
个人的理解如下(如有理解偏差,请大家帮忙指正,谢谢):
1、当观察者固定,物体移动时,物体的每个点位都是需要做相应的移动的,矩阵计算即:移动物体时我们每次都要计算的每个点的位置并使之移动。(例子:我们移动一个平面的正方形,每移动一步,正方形的4个点我们都要对其位置进行计算并移动)。
2、当物体固定,观察者移动时,相当于我们的眼睛在动,此时的移动就只有眼睛位置这一个移动,我们要做的就是表明眼睛的所在位置即可。
简单可归结为:物体移动,则物体本身上的每个位置都要进行移动;观察者移动,移动的只是眼睛的位置。
二、图形的绘制
点 线 面、金字塔、圆环、扇形
1 // 初始化 2 void SetupRC() { 3 4 // 灰色的背景 5 glClearColor(0.7f, 0.7f, 0.7f, 1.0f ); 6 shaderManager.InitializeStockShaders(); 7 glEnable(GL_DEPTH_TEST); 8 //设置变换管线以使用两个矩阵堆栈 9 transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix); 10 cameraFrame.MoveForward(-15.0f); 11 12 /* 13 常见函数: 14 void GLBatch::Begin(GLenum primitive,GLuint nVerts,GLuint nTextureUnits = 0); 15 参数1:表示使用的图元 16 参数2:顶点数 17 参数3:纹理坐标(可选) 18 19 //负责顶点坐标 20 void GLBatch::CopyVertexData3f(GLFloat *vNorms); 21 22 //结束,表示已经完成数据复制工作 23 void GLBatch::End(void); 24 */ 25 // 定义三角形形状 的点 26 GLfloat vCoast[9] = { 27 3,3,0,0,3,0,3,0,0 28 }; 29 30 // 不同方式 三角 31 // 1、用点的形式 32 pointBatch.Begin(GL_POINTS, 3); 33 pointBatch.CopyVertexData3f(vCoast); 34 pointBatch.End(); 35 36 // 2、通过线的形式 37 lineBatch.Begin(GL_LINES, 3); 38 lineBatch.CopyVertexData3f(vCoast); 39 lineBatch.End(); 40 41 // 3、通过线段的形式 42 lineStripBatch.Begin(GL_LINE_STRIP, 3); 43 lineStripBatch.CopyVertexData3f(vCoast); 44 lineStripBatch.End(); 45 46 // 4、通过线环的形式 47 lineLoopBatch.Begin(GL_LINE_LOOP, 3); 48 lineLoopBatch.CopyVertexData3f(vCoast); 49 lineLoopBatch.End(); 50 51 52 // 5、通过三角形创建金字塔 53 GLfloat vPyramid[12][3] = { 54 -2.0f, 0.0f, -2.0f, 55 2.0f, 0.0f, -2.0f, 56 0.0f, 4.0f, 0.0f, 57 58 2.0f, 0.0f, -2.0f, 59 2.0f, 0.0f, 2.0f, 60 0.0f, 4.0f, 0.0f, 61 62 2.0f, 0.0f, 2.0f, 63 -2.0f, 0.0f, 2.0f, 64 0.0f, 4.0f, 0.0f, 65 66 -2.0f, 0.0f, 2.0f, 67 -2.0f, 0.0f, -2.0f, 68 0.0f, 4.0f, 0.0f 69 70 }; 71 // GLfloat vPyramid[12][3] = { 72 // -4.0f, 0.0f, -4.0f, 73 // 4.0f, 0.0f, -4.0f, 74 // 0.0f, 8.0f, 0.0f, 75 // 76 // 4.0f, 0.0f, -4.0f, 77 // 4.0f, 0.0f, 4.0f, 78 // 0.0f, 8.0f, 0.0f, 79 // 80 // 4.0f, 0.0f, 4.0f, 81 // -4.0f, 0.0f, 4.0f, 82 // 0.0f, 8.0f, 0.0f, 83 // 84 // -4.0f, 0.0f, 4.0f, 85 // -4.0f, 0.0f, -4.0f, 86 // 0.0f, 8.0f, 0.0f}; 87 88 // GL_TRIANGLES 每3个顶点定义一个新的三角形 89 triangleBatch.Begin(GL_TRIANGLES, 12); 90 triangleBatch.CopyVertexData3f(vPyramid); 91 triangleBatch.End(); 92 93 94 // 6、圆环 三角形条带,一个小环或圆柱段 95 // 顶点下标 96 int iCounter = 0; 97 // 半径 98 GLfloat radius = 3.0f; 99 //从0度~360度,以0.3弧度为步长 100 for(GLfloat angle = 0.0f; angle <= (2.0f*M3D_PI); angle += 0.3f) { 101 //或许圆形的顶点的X,Y 102 GLfloat x = radius * sin(angle); 103 GLfloat y = radius * cos(angle); 104 105 //绘制2个三角形(他们的x,y顶点一样,只是z点不一样) 106 vPoints[iCounter][0] = x; 107 vPoints[iCounter][1] = y; 108 vPoints[iCounter][2] = -0.5; 109 iCounter++; 110 111 vPoints[iCounter][0] = x; 112 vPoints[iCounter][1] = y; 113 vPoints[iCounter][2] = 0.5; 114 iCounter++; 115 } 116 // 关闭循环 117 printf("三角形带的顶点数:%d\n",iCounter); 118 // 结束循环,在循环位置生成2个三角形 119 vPoints[iCounter][0] = vPoints[0][0]; 120 vPoints[iCounter][1] = vPoints[0][1]; 121 vPoints[iCounter][2] = -0.5; 122 iCounter++; 123 124 vPoints[iCounter][0] = vPoints[1][0]; 125 vPoints[iCounter][1] = vPoints[1][1]; 126 vPoints[iCounter][2] = 0.5; 127 iCounter++; 128 129 // GL_TRIANGLE_STRIP 共用一个条带(strip)上的顶点的一组三角形 130 triangleStripBatch.Begin(GL_TRIANGLE_STRIP, iCounter); 131 triangleStripBatch.CopyVertexData3f(vPoints); 132 triangleStripBatch.End(); 133 134 135 // 7、三角形扇形 -- 六边形 136 GLfloat vPoints[100][3]; 137 int nVerts = 0; 138 // 半径 139 GLfloat r = 3.0f; 140 // 原点(x,y,z) = (0,0,0); 141 vPoints[nVerts][0] = 0.0f; 142 vPoints[nVerts][1] = 0.0f; 143 vPoints[nVerts][2] = 0.0f; 144 145 // M3D_2PI 就是2Pi 的意思,就一个圆的意思。 绘制圆形 146 for(GLfloat angle = 0; angle < M3D_2PI; angle += M3D_2PI / 6.0f) { 147 148 // 数组下标自增(每自增1次就表示一个顶点) 149 nVerts++; 150 /* 151 弧长=半径*角度,这里的角度是弧度制,不是平时的角度制 152 既然知道了cos值,那么角度=arccos,求一个反三角函数就行了 153 */ 154 // x点坐标 cos(angle) * 半径 155 vPoints[nVerts][0] = float(cos(angle)) * r; 156 // y点坐标 sin(angle) * 半径 157 vPoints[nVerts][1] = float(sin(angle)) * r; 158 // z点的坐标 159 vPoints[nVerts][2] = -0.5f; 160 } 161 162 // 结束扇形 前面一共绘制7个顶点(包括圆心) 163 // 添加闭合的终点 164 nVerts++; 165 vPoints[nVerts][0] = r; 166 vPoints[nVerts][1] = 0; 167 vPoints[nVerts][2] = 0.0f; 168 169 // 加载 170 // GL_TRIANGLE_FAN 以一个圆心为中心呈扇形排列,共用相邻顶点的一组三角形 171 triangleFanBatch.Begin(GL_TRIANGLE_FAN, 8);// 8个顶点?7的话扇形是无法闭合的 172 triangleFanBatch.CopyVertexData3f(vPoints); 173 triangleFanBatch.End(); 174 }
绘制:void RenderScene();
下图,我们是看不出立体效果的
设置偏移:
glPolygonOffset(-1.0f, -1.0f);// 偏移深度,在同一位置要绘制填充和边线,会产生z-Flighting 冲突,所以要偏移
glEnable(GL_POLYGON_OFFSET_LINE);
开启颜色混合:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1 void DrawWireFramedBatch(GLBatch* pBatch) { 2 3 /*------------画绿色部分----------------*/ 4 /* GLShaderManager 中的Uniform 值 —— 平面着色器 5 参数1:平面着色器 6 参数2:运行为几何图形变换指定一个 4 * 4变换矩阵 7 --transformPipeline 变换管线(指定了2个矩阵堆栈) 8 参数3:颜色值 9 */ 10 shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen); 11 pBatch->Draw();// 第 1 次绘制 12 13 /*-----------边框部分-------------------*/ 14 /* 15 glEnable(GLenum mode); 用于启用各种功能。功能由参数决定 16 参数列表:https://www.cnblogs.com/zhangying-domy/p/13276529.html 17 注意:glEnable() 不能写在glBegin() 和 glEnd()中间 18 GL_POLYGON_OFFSET_LINE 根据函数glPolygonOffset的设置,启用线的深度偏移 19 GL_LINE_SMOOTH 执行后,过虑线点的锯齿 20 GL_BLEND 启用颜色混合。例如实现半透明效果 21 GL_DEPTH_TEST 启用深度测试 根据坐标的远近自动隐藏被遮住的图形(材料 22 23 glDisable(GLenum mode); 用于关闭指定的功能 功能由参数决定 24 */ 25 // 设置偏移 画黑色边框 26 glPolygonOffset(-1.0f, -1.0f);// 偏移深度,在同一位置要绘制填充和边线,会产生z冲突,所以要偏移 27 glEnable(GL_POLYGON_OFFSET_LINE); 28 29 // 画反锯齿,使平滑 让黑边好看些 30 glEnable(GL_LINE_SMOOTH); 31 32 // 颜色混合 33 glEnable(GL_BLEND); 34 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 35 36 //绘制线框几何黑色版 三种模式,实心,边框,点,可以作用在正面,背面,或者两面 37 //通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染 38 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 线的形式填充 39 //设置线条宽度 40 glLineWidth(2.5f); 41 42 /* GLShaderManager 中的Uniform 值 —— 平面着色器 43 参数1:平面着色器 44 参数2:运行为几何图形变换指定一个 4 * 4变换矩阵 45 --transformPipeline.GetModelViewProjectionMatrix() 获取的 46 GetMatrix函数就可以获得矩阵堆栈顶部的值 47 参数3:颜色值(黑色) 48 */ 49 shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack); 50 pBatch->Draw();// 第 2 次绘制 51 52 // 恢复初始状态 复原原本的设置 53 // 通过调用glPolygonMode将多边形正面或者背面设为全部填充模式 54 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 55 glDisable(GL_POLYGON_OFFSET_LINE); 56 glLineWidth(1.0f); 57 glDisable(GL_BLEND); 58 glDisable(GL_LINE_SMOOTH); 59 }
三、矩阵堆栈
// 类型 GLMatrixStack::GLMatrixStack(int iStackDepth = 64); // 在堆栈顶部载⼊一个单元矩阵 void GLMatrixStack::LoadIdentity(void); // 在堆栈顶部载⼊任何矩阵 // 参数:4*4矩阵 void GLMatrixStack::LoadMatrix(const M3DMatrix44f m); // 矩阵 乘以 矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 void GLMatrixStack::MultMatrix(const M3DMatrix44f); // 获取矩阵堆栈顶部的值 GetMatrix 函数 // 为了适应GLShaderMananger的使用,或者获取顶部矩阵的副本 const M3DMatrix44f & GLMatrixStack::GetMatrix(void); void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix); // 将当前矩阵压⼊堆栈 (栈顶矩阵copy 一份到栈顶) void GLMatrixStack::PushMatrix(void); // 将M3DMatrix44f 矩阵对象压⼊当前矩阵堆栈 void PushMatrix(const M3DMatrix44f mMatrix); // 将GLFame 对象压⼊矩阵对象 void PushMatrix(GLFame &frame); // 出栈(移除顶部的矩阵对象) void GLMatrixStack::PopMatrix(void);
矩阵压栈的过程(图片来自:简书凡几多):