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

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

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

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

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

  1. DrawHead(ref Gl, 1f, 2f, 0f); // 绘制头部 2*2*2
  2. DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干, 3*5*2
  3. DrawArm(ref Gl, 2.5f, 0.0f, -0.5f); //胳膊1, 1*4*1
  4. DrawArm(ref Gl, -1.5f, 0.0f, -0.5f); //胳膊2, 1*4*1
  5. DrawLeg(ref Gl, -0.5f, -5.0f, -0.5f); //腿部1,1*5*1
  6. 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/

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

  1. SharpGL学习笔记(三) 投影变换和视点变换

    从本节开始,我们使用SharpGL带的VS2010扩展,来直接生成SharpGL工程. 如果你新建项目时,没有看到下面的SharpGL项目,那么请事先在SharpGL源代码中找到一个叫 ”SharpG ...

  2. SharpGL学习笔记(七) OpenGL的变换总结

    笔者接触OpenGL最大的困难是: 经常调试一份代码时, 屏幕漆黑一片, 也不知道结果对不对,不知道如何是好! 这其实就是关于OpenGL"变换"的基础概念没有掌握好, 以至于对& ...

  3. SharpGL学习笔记(十一) 光源创建的综合例子:光源参数可调节的测试场景

    灯光的测试例子:光源参数可以调节的测试场景 先看一下测试场景和效果. 场景中可以切换视图, 以方便观察三维体和灯光的位置.环境光,漫射光,镜面反射光都可以在四种颜色间切换. 灯光位置和摄像机位置(Lo ...

  4. Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...

  5. python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑

    python3.4学习笔记(八) Python第三方库安装与使用,包管理工具解惑 许多人在安装Python第三方库的时候, 经常会为一个问题困扰:到底应该下载什么格式的文件?当我们点开下载页时, 一般 ...

  6. Go语言学习笔记八: 数组

    Go语言学习笔记八: 数组 数组地球人都知道.所以只说说Go语言的特殊(奇葩)写法. 我一直在想一个人参与了两种语言的设计,但是最后两种语言的语法差异这么大.这是自己否定自己么,为什么不与之前统一一下 ...

  7. 【opencv学习笔记八】创建TrackBar轨迹条

    createTrackbar这个函数我们以后会经常用到,它创建一个可以调整数值的轨迹条,并将轨迹条附加到指定的窗口上,使用起来很方便.首先大家要记住,它往往会和一个回调函数配合起来使用.先看下他的函数 ...

  8. go微服务框架kratos学习笔记八 (kratos的依赖注入)

    目录 go微服务框架kratos学习笔记八(kratos的依赖注入) 什么是依赖注入 google wire kratos中的wire Providers injector(注入器) Binding ...

  9. Redis学习笔记八:集群模式

    作者:Grey 原文地址:Redis学习笔记八:集群模式 前面提到的Redis学习笔记七:主从复制和哨兵只能解决Redis的单点压力大和单点故障问题,接下来要讲的Redis Cluster模式,主要是 ...

随机推荐

  1. 数据源(HikariCP)

    HikariCP 是一个高性能的 JDBC 连接池组件.下图是性能的比较测试结果: 自从看到了这张图,我就对于我之前一直在使用了 c3p0 产生了深深的怀疑,迫切的期望得到对应的数据来优化我的代码. ...

  2. HTTP 请求头与请求体 - 某熊的全栈之路 - SegmentFault

    本文从属于笔者的HTTP 理解与实践系列文章,对于HTTP的学习主要包含HTTP 基础.HTTP 请求头与请求体.HTTP 响应头与状态码.HTTP 缓存这四个部分,而对于HTTP相关的扩展与引申,我 ...

  3. Thinkphp5笔记五:配置data文件夹

    如果你看项目下的各种文件,有种乱七八糟的感觉的话,你就可以进行以下配置. 配置data文件夹的,整理各种文件,让看起来舒服些. 一.设置runtime文件夹 index.php define('RUN ...

  4. EA修改生成代码的表头注释

    我们在做项目的过程中,每个代码文件都应有此文件的注释,比如说作者,文件说明等.但是如果用EA生成的代码文件的注释是纯英文的,而且有些不是我们需要显示的注释,有些我们需要显示的它又不具备.那么我们就可以 ...

  5. 生成asm-offset

    因为不善于在Makefile中调用shell的相关工具,所以关于asm-offsets.h中的产生的16进制数并不知如何做到. 因此自己写了个脚本,可以生成同样的文件(再次造了轮子). 参考:http ...

  6. 非抢占式RCU实现(二),解释:为什么 RCU_NEXT_SIZE 宏值是4?

    参考:2.6.34 一个很奇怪的问题. 没有查找到为什么 RCU_NEXT_SIZE的值为4的原因(包括Documentation),主要是在rcu_state中定义了一个四级的list,感到很有意思 ...

  7. css实现图片横向排列滚动

    .imageList{ overflow-x: auto; overflow-y: hidden; height:180px; white-space: nowrap; img{ width:auto ...

  8. phpcms v9模板制作常用代码集合(转)

    phpcms v9模板制作常用代码集合(个人收藏) 1.截取调用标题长度 {str_cut($r[title],36,'')} 2.格式化时间 调用格式化时间 2011-05-06 11:22:33 ...

  9. SPREAD for Windows Forms 代码片段

    'スクロールバーの移動 FpSpread1.ShowColumn(, , HorizontalPosition.Left) 'SetActiveCellの後.LeaveCellを呼び出す Dim ss ...

  10. 7 -- Spring的基本用法 -- 7... 创建Bean的3种方式

    7.7 创建Bean的3种方式 ① 调用构造器创建Bean. ② 调用静态工厂方法创建Bean. ③ 调用实例工厂方法创建Bean. 7.7.1 使用构造器创建Bean实例. 使用构造器来创建Bean ...