我們先引入關於"矩陣堆棧"的官方說法:
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變換的知識後, 讀者已經有足夠的基礎可以開始編寫游戲了!
我當時還郁悶, 就學這點知識就可以開始寫游戲? 那材質燈光呢? 開什麼玩笑?
現在我就同意這一說法了, 因為"變換"的知識是重中之重, 請引起朋友們足夠的重視, 踏實把它學好! 不要像筆者一樣浮澡走彎路!
本節源代碼下載
原創文章 : http://www.cnblogs.com/hackpig/