你的位置:首页 > ASP.net教程

[ASP.net教程]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*2100         DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干, 3*5*2101 102         Gl.PushMatrix();103         {104           //如果胳膊正在向前运动,则递增角度,否则递减角度 105           if (arm1)106             armAngle[0] = armAngle[0] + 1f;107           else108             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           else128             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*1140         }141         Gl.PopMatrix();142 143         Gl.PushMatrix();144         {145           ///如果腿正在向前运动,则递增角度,否则递减角度 146           if (leg1)147             legAngle[0] = legAngle[0] + 1f;148           else149             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           else169             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*1179         }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           else246             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       else269         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*2DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干, 3*5*2DrawArm(ref Gl, 2.5f, 0.0f, -0.5f); //胳膊1, 1*4*1 DrawArm(ref Gl, -1.5f, 0.0f, -0.5f); //胳膊2, 1*4*1DrawLeg(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变换的知识后, 读者已经有足够的基础可以开始编写游戏了! 

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

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

 

本节源代码下载