SharpGL学习笔记(八) 矩阵堆栈和变换的综合例子: 机器人

 

我们先引入关于"矩阵堆栈"的官方说法:

OpenGL的矩阵堆栈指的就是内存中专门用来存放矩阵数据的某块特殊区域。
实际上,在创建、装入、相乘模型变换和投影变换矩阵时,都已用到堆栈操作。一般说来,矩阵堆栈常用于构造具有继承性的模型,即由一些简单目标构成的复杂模型。例如,一辆自行车就是由两个轮子、一个三角架及其它一些零部件构成的。它的继承性表现在当自行车往前走时,首先是前轮旋转,然后整个车身向前平移,接着是后轮旋转,然后整个车身向前平移,如此进行下去,这样自行车就往前走了。
矩阵堆栈对复杂模型运动过程中的多个变换操作之间的联系与独立十分有利。因为所有矩阵操作函数如LoadMatrix()、MultMatrix()、LoadIdentity()等只处理当前矩阵或堆栈顶部矩阵,这样堆栈中下面的其它矩阵就不受影响。堆栈操作函数有以下两个:
PushMatrix(void);
PopMatrix(void);
第一个函数表示将所有矩阵依次压入堆栈中,顶部矩阵是第二个矩阵的备份;压入的矩阵数不能太多,否则出错。

第二个函数表示弹出堆栈顶部的矩阵,令原第二个矩阵成为顶部矩阵,接受当前操作,故原顶部矩阵被破坏;当堆栈中仅存一个矩阵时,不能进行弹出操作,否则出错。

由此看出,矩阵堆栈操作与压入矩阵的顺序刚好相反,编程时要特别注意矩阵操作的顺序。
为了更好地理解这两个函数,我们可以形象地认为glPushMatrix()就是“记住自己在哪”,glPopMatrix()就是“返回自己原来所在地”。

我特意在"机器人"的代码里加入这段演示矩阵堆栈用法的例子, 让朋友们能看得更明白清楚一些.

 

下面这段测试代码是想让第一个长方体缩放为2*1*1, 沿X轴转45, 第二个长方体缩放为1*1*1,也就是不变形, 置于第一个长方体的左边贴着.

 1 public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
 2         {
 3             gl.PushMatrix();
 4             {
 5                 gl.Color(1f, 0f, 0f);
 6                 gl.Translate(xPos, yPos, zPos);
 7                 gl.Scale(2f, 1f, 1f);
 8                 gl.Rotate(45, 1f, 0f, 0f);
 9                 DrawCube(ref gl, 0, 0, 0, true);
10             }
11             gl.PopMatrix();
12 
13             gl.PushMatrix();
14             {
15                 gl.Color(0f, 1f, 0f);
16                 gl.Translate(xPos - 2f, yPos, zPos);
17                 DrawCube(ref gl, 0, 0, 0, true);
18             }
19             gl.PopMatrix();
20         }

看到的效果就是我想真正想要的.

 

我们修改下代码, 把PushMatrix()和popMatrix()都注释了, 就像下面这样:

 1   public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
 2         {
 3             //gl.PushMatrix();
 4             //{
 5                 gl.Color(1f, 0f, 0f);
 6                 gl.Translate(xPos, yPos, zPos);
 7                 gl.Scale(2f, 1f, 1f);
 8                 gl.Rotate(45, 1f, 0f, 0f);
 9                 DrawCube(ref gl, 0, 0, 0, true);
10             //}
11             //gl.PopMatrix();
12 
13             //gl.PushMatrix();
14             //{
15                 gl.Color(0f, 1f, 0f);
16                 gl.Translate(xPos - 2f, yPos, zPos);
17                 DrawCube(ref gl, 0, 0, 0, true);
18             //}
19             //gl.PopMatrix();
20         }

然后结果是这样的:

显示, 第二个DrawCube()受到了上面那些变换操作的影响, 像是继承了上面的那个长方体的旋转与缩放一样.

因此, 在这里如果两个DrawCube()操作在其首尾各加入栈出栈功能括起来, 那么这两次DrawCube()就相互独立了起来, 不会相互影响. 

 

最后我们给出一个运用"变换"的综合性的例子, 它画了一个手脚能动的机器人, 场景做360度旋转, 可以观察到机器人每个面.

这个例子改编自 徐明亮的《OpenGL游戏编程》这本书里的一个例子, 原书例子是C++的代码.

先贴出源代码:

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 using SharpGL;
 10 
 11 namespace SharpGLWinformsApplication1
 12 {
 13     public partial class SharpGLForm : Form
 14     {
 15         public float angle;                  // 机器人绕视点旋转的角度
 16         float[] legAngle = new float[2];     // 腿的当前旋转角度
 17         float[] armAngle = new float[2];     // 胳膊的当前旋转角度
 18 
 19         bool leg1 = true;                    // 机器人腿的状态,true向前,flase向后
 20         bool leg2 = false;
 21         bool arm1 = true;   
 22         bool arm2 = false;
 23 
 24         private float rotation = 0.0f;
 25         public SharpGLForm()
 26         {
 27             InitializeComponent();
 28             angle = 0.0f;                    // 设置初始角度为0
 29             legAngle[0] = legAngle[1] = 0.0f;
 30             armAngle[0] = armAngle[1] = 0.0f;
 31         }
 32 
 33         private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e)
 34         {
 35             OpenGL gl = openGLControl.OpenGL;
 36             gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
 37 
 38             gl.LoadIdentity();
 39             gl.Rotate(rotation, 0.0f, 1.0f, 0.0f);
 40 
 41             drawGrid(gl);
 42             DrawRobot(ref gl, 0, 0, 0);
 43             //rtest(ref gl, 0, 0, 0);
 44             rotation += 1.0f;
 45         }
 46 
 47 
 48 
 49 
 50         private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
 51         {
 52             OpenGL gl = openGLControl.OpenGL;
 53             gl.ClearColor(0, 0, 0, 0);
 54         }
 55 
 56         private void openGLControl_Resized(object sender, EventArgs e)
 57         {
 58             OpenGL gl = openGLControl.OpenGL;
 59 
 60             gl.MatrixMode(OpenGL.GL_PROJECTION);
 61 
 62             gl.LoadIdentity();
 63             gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);
 64             gl.LookAt(-5, 5, 15, 0, 0, 0, 0, 1, 0);
 65             gl.MatrixMode(OpenGL.GL_MODELVIEW);
 66         }
 67 
 68         //测试例子
 69         public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
 70         {
 71             gl.PushMatrix();
 72             {
 73                 gl.Color(1f, 0f, 0f);
 74                 gl.Translate(xPos, yPos, zPos);
 75                 gl.Scale(2f, 1f, 1f);
 76                 gl.Rotate(45, 1f, 0f, 0f);
 77                 DrawCube(ref gl, 0, 0, 0, true);
 78             }
 79             gl.PopMatrix();
 80 
 81             gl.PushMatrix();
 82             {
 83                 gl.Color(0f, 1f, 0f);
 84                 gl.Translate(xPos - 2f, yPos, zPos);
 85                 DrawCube(ref gl, 0, 0, 0, true);
 86             }
 87             gl.PopMatrix();
 88         }
 89 
 90 
 91         public void DrawRobot(ref OpenGL Gl, float xPos, float yPos, float zPos)
 92         {
 93             Gl.PushMatrix();
 94             {
 95                 Gl.Translate(xPos, yPos, zPos);
 96 
 97                 ///绘制各个部分
 98                 //Gl.LoadIdentity();
 99                 DrawHead(ref Gl, 1f, 2f, 0f);     // 绘制头部  2*2*2
100                 DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干,  3*5*2
101 
102                 Gl.PushMatrix();
103                 {
104                     //如果胳膊正在向前运动,则递增角度,否则递减角度 
105                     if (arm1)
106                         armAngle[0] = armAngle[0] + 1f;
107                     else
108                         armAngle[0] = armAngle[0] - 1f;
109 
110                     ///如果胳膊达到其最大角度则改变其状态
111                     if (armAngle[0] >= 15.0f)
112                         arm1 = false;
113                     if (armAngle[0] <= -15.0f)
114                         arm1 = true;
115 
116                     //平移并旋转后绘制胳膊
117                     Gl.Translate(0.0f, -0.5f, 0.0f);
118                     Gl.Rotate(armAngle[0], 1.0f, 0.0f, 0.0f);
119                     DrawArm(ref Gl, 2.5f, 0.0f, -0.5f);  //胳膊1, 1*4*1  
120                 }
121                 Gl.PopMatrix();
122 
123                 Gl.PushMatrix();
124                 {
125                     if (arm2)
126                         armAngle[1] = armAngle[1] + 1f;
127                     else
128                         armAngle[1] = armAngle[1] - 1f;
129 
130 
131                     if (armAngle[1] >= 15.0f)
132                         arm2 = false;
133                     if (armAngle[1] <= -15.0f)
134                         arm2 = true;
135 
136 
137                     Gl.Translate(0.0f, -0.5f, 0.0f);
138                     Gl.Rotate(armAngle[1], 1.0f, 0.0f, 0.0f);
139                     DrawArm(ref Gl, -1.5f, 0.0f, -0.5f); //胳膊2, 1*4*1
140                 }
141                 Gl.PopMatrix();
142 
143                 Gl.PushMatrix();
144                 {
145                     ///如果腿正在向前运动,则递增角度,否则递减角度 
146                     if (leg1)
147                         legAngle[0] = legAngle[0] + 1f;
148                     else
149                         legAngle[0] = legAngle[0] - 1f;
150 
151                     ///如果腿达到其最大角度则改变其状态
152                     if (legAngle[0] >= 15.0f)
153                         leg1 = false;
154                     if (legAngle[0] <= -15.0f)
155                         leg1 = true;
156 
157                     ///平移并旋转后绘制胳膊
158                     Gl.Translate(0.0f, -0.5f, 0.0f);
159                     Gl.Rotate(legAngle[0], 1.0f, 0.0f, 0.0f);
160                     DrawLeg(ref Gl, -0.5f, -5.0f, -0.5f); //腿部1,1*5*1 
161                 }
162                 Gl.PopMatrix();
163 
164                 Gl.PushMatrix();
165                 {
166                     if (leg2)
167                         legAngle[1] = legAngle[1] + 1f;
168                     else
169                         legAngle[1] = legAngle[1] - 1f;
170 
171                     if (legAngle[1] >= 15.0f)
172                         leg2 = false;
173                     if (legAngle[1] <= -15.0f)
174                         leg2 = true;
175 
176                     Gl.Translate(0.0f, -0.5f, 0.0f);
177                     Gl.Rotate(legAngle[1], 1.0f, 0.0f, 0.0f);
178                     DrawLeg(ref Gl, 1.5f, -5.0f, -0.5f); //腿部2, 1*5*1
179                 }
180                 Gl.PopMatrix();
181             }
182             Gl.PopMatrix();
183         }
184 
185         // 绘制一个手臂 
186         void DrawArm(ref OpenGL Gl, float xPos, float yPos, float zPos)
187         {
188             Gl.PushMatrix();
189             Gl.Color(1.0f, 0.0f, 0.0f);    // 红色 
190             Gl.Translate(xPos, yPos, zPos);
191             Gl.Scale(1.0f, 4.0f, 1.0f);        // 手臂是1x4x1的立方体 
192             DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
193             Gl.PopMatrix();
194         }
195 
196         // 绘制一条腿 
197         void DrawLeg(ref OpenGL Gl, float xPos, float yPos, float zPos)
198         {
199             Gl.PushMatrix();
200             Gl.Color(1.0f, 1.0f, 0.0f);    // 黄色 
201             Gl.Translate(xPos, yPos, zPos);
202             Gl.Scale(1.0f, 5.0f, 1.0f);        // 腿是1x5x1长方体 
203             DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
204             Gl.PopMatrix();
205         }
206 
207         // 绘制头部
208         void DrawHead(ref OpenGL Gl, float xPos, float yPos, float zPos)
209         {
210             Gl.PushMatrix();
211             Gl.Color(1.0f, 1.0f, 1.0f);    // 白色 
212             Gl.Translate(xPos, yPos, zPos);
213             Gl.Scale(2.0f, 2.0f, 2.0f);        //头部是 2x2x2长方体
214             DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
215             Gl.PopMatrix();
216         }
217 
218         // 绘制机器人的躯干 
219         void DrawTorso(ref OpenGL Gl, float xPos, float yPos, float zPos)
220         {
221             Gl.PushMatrix();
222             Gl.Color(0.0f, 0.0f, 1.0f);     // 蓝色
223             Gl.Translate(xPos, yPos, zPos);
224             Gl.Scale(3.0f, 5.0f, 2.0f);         // 躯干是3x5x2的长方体 
225             DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
226             Gl.PopMatrix();
227         }
228 
229         void drawGrid(OpenGL gl)
230         {
231             //绘制过程
232             gl.PushAttrib(OpenGL.GL_CURRENT_BIT);  //保存当前属性
233             gl.PushMatrix();                        //压入堆栈
234             gl.Translate(0f, -20f, 0f);
235             gl.Color(0f, 0f, 1f);
236 
237             //在X,Z平面上绘制网格
238             for (float i = -50; i <= 50; i += 1)
239             {
240                 //绘制线
241                 gl.Begin(OpenGL.GL_LINES);
242                 {
243                     if (i == 0)
244                         gl.Color(0f, 1f, 0f);
245                     else
246                         gl.Color(0f, 0f, 1f);
247 
248                     //X轴方向
249                     gl.Vertex(-50f, 0f, i);
250                     gl.Vertex(50f, 0f, i);
251                     //Z轴方向 
252                     gl.Vertex(i, 0f, -50f);
253                     gl.Vertex(i, 0f, 50f);
254       
255                 }
256                 gl.End();
257             }
258             gl.PopMatrix();
259             gl.PopAttrib();
260         }
261 
262         internal void DrawCube(ref OpenGL Gl, float xPos, float yPos, float zPos,bool isLine)
263         {
264             Gl.PushMatrix();
265             Gl.Translate(xPos, yPos, zPos);
266             if (isLine)
267                 Gl.Begin(OpenGL.GL_LINE_STRIP);
268             else
269                 Gl.Begin(OpenGL.GL_POLYGON);
270 
271             // 顶面
272             Gl.Vertex(0.0f, 0.0f, 0.0f);
273             Gl.Vertex(0.0f, 0.0f, -1.0f);
274             Gl.Vertex(-1.0f, 0.0f, -1.0f);
275             Gl.Vertex(-1.0f, 0.0f, 0.0f);
276 
277             // 前面
278             Gl.Vertex(0.0f, 0.0f, 0.0f);
279             Gl.Vertex(-1.0f, 0.0f, 0.0f);
280             Gl.Vertex(-1.0f, -1.0f, 0.0f);
281             Gl.Vertex(0.0f, -1.0f, 0.0f);
282 
283             // 右面
284             Gl.Vertex(0.0f, 0.0f, 0.0f);
285             Gl.Vertex(0.0f, -1.0f, 0.0f);
286             Gl.Vertex(0.0f, -1.0f, -1.0f);
287             Gl.Vertex(0.0f, 0.0f, -1.0f);
288 
289             // 左面
290             Gl.Vertex(-1.0f, 0.0f, 0.0f);
291             Gl.Vertex(-1.0f, 0.0f, -1.0f);
292             Gl.Vertex(-1.0f, -1.0f, -1.0f);
293             Gl.Vertex(-1.0f, -1.0f, 0.0f);
294 
295             // 底面 
296             Gl.Vertex(0.0f, 0.0f, 0.0f);
297             Gl.Vertex(0.0f, -1.0f, -1.0f);
298             Gl.Vertex(-1.0f, -1.0f, -1.0f);
299             Gl.Vertex(-1.0f, -1.0f, 0.0f);
300 
301 
302             // 后面
303             Gl.Vertex(0.0f, 0.0f, 0.0f);
304             Gl.Vertex(-1.0f, 0.0f, -1.0f);
305             Gl.Vertex(-1.0f, -1.0f, -1.0f);
306             Gl.Vertex(0.0f, -1.0f, -1.0f);
307             Gl.End();
308             Gl.PopMatrix();
309         }
310 
311 
312 
313     }
314 }

这个代码朋友们主要需注意下面两个重点:

(1) 机器人的6个部分的坐标为什么要取下面这样的值?

因为画每个部分都用了入栈出栈, 因此每个部分都是独立相对于原点来计算的, 计算的时候你还得参考  长*高*宽 的信息, 我已经把它标注到注释里了.

DrawHead(ref Gl, 1f, 2f, 0f);     // 绘制头部  2*2*2
DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干,  3*5*2
DrawArm(ref Gl, 2.5f, 0.0f, -0.5f);  //胳膊1, 1*4*1  
DrawArm(ref Gl, -1.5f, 0.0f, -0.5f); //胳膊2, 1*4*1
DrawLeg(ref Gl, -0.5f, -5.0f, -0.5f); //腿部1,1*5*1 
DrawLeg(ref Gl, 1.5f, -5.0f, -0.5f); //腿部2, 1*5*1

(2) 好好体会堆栈的操作, 主要在画机器人的函数DrawRobot()里.

 

程序运行时截取了一帧,效果如下:

 

 

OpenGL的"变换" 主题终于彻底讲完了! 最初笔者接触这些内容时, 感觉术语太多, 枯燥无趣. 但是它是OpenGL真正最基本的入门功夫. 就像 徐明亮在《OpenGL游戏编程》这本书里说的: 说完OpenGL变换的知识后, 读者已经有足够的基础可以开始编写游戏了! 

我当时还郁闷,  就学这点知识就可以开始写游戏? 那材质灯光呢? 开什么玩笑?

现在我就同意这一说法了, 因为"变换"的知识是重中之重, 请引起朋友们足够的重视, 踏实把它学好! 不要像笔者一样浮澡走弯路!

 

本节源代码下载

 

原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/

 

posted @ 2016-08-23 11:11  猪悟能  阅读(3522)  评论(2编辑  收藏  举报