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 }

Demo链接 

三、矩阵堆栈

// 类型
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);

矩阵压栈的过程(图片来自:简书凡几多):

 

posted @ 2020-07-14 12:37  张张_z  阅读(606)  评论(0编辑  收藏  举报