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

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

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

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

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

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

 public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
{
gl.PushMatrix();
{
gl.Color(1f, 0f, 0f);
gl.Translate(xPos, yPos, zPos);
gl.Scale(2f, 1f, 1f);
gl.Rotate(, 1f, 0f, 0f);
DrawCube(ref gl, , , , true);
}
gl.PopMatrix(); gl.PushMatrix();
{
gl.Color(0f, 1f, 0f);
gl.Translate(xPos - 2f, yPos, zPos);
DrawCube(ref gl, , , , true);
}
gl.PopMatrix();
}

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

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

   public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
{
//gl.PushMatrix();
//{
gl.Color(1f, 0f, 0f);
gl.Translate(xPos, yPos, zPos);
gl.Scale(2f, 1f, 1f);
gl.Rotate(, 1f, 0f, 0f);
DrawCube(ref gl, , , , true);
//}
//gl.PopMatrix(); //gl.PushMatrix();
//{
gl.Color(0f, 1f, 0f);
gl.Translate(xPos - 2f, yPos, zPos);
DrawCube(ref gl, , , , true);
//}
//gl.PopMatrix();
}

然后结果是这样的:

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

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

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

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

先贴出源代码:

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpGL; namespace SharpGLWinformsApplication1
{
public partial class SharpGLForm : Form
{
public float angle; // 机器人绕视点旋转的角度
float[] legAngle = new float[]; // 腿的当前旋转角度
float[] armAngle = new float[]; // 胳膊的当前旋转角度 bool leg1 = true; // 机器人腿的状态,true向前,flase向后
bool leg2 = false;
bool arm1 = true;
bool arm2 = false; private float rotation = 0.0f;
public SharpGLForm()
{
InitializeComponent();
angle = 0.0f; // 设置初始角度为0
legAngle[] = legAngle[] = 0.0f;
armAngle[] = armAngle[] = 0.0f;
} private void openGLControl_OpenGLDraw(object sender, PaintEventArgs e)
{
OpenGL gl = openGLControl.OpenGL;
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT); gl.LoadIdentity();
gl.Rotate(rotation, 0.0f, 1.0f, 0.0f); drawGrid(gl);
DrawRobot(ref gl, , , );
//rtest(ref gl, 0, 0, 0);
rotation += 1.0f;
} private void openGLControl_OpenGLInitialized(object sender, EventArgs e)
{
OpenGL gl = openGLControl.OpenGL;
gl.ClearColor(, , , );
} private void openGLControl_Resized(object sender, EventArgs e)
{
OpenGL gl = openGLControl.OpenGL; gl.MatrixMode(OpenGL.GL_PROJECTION); gl.LoadIdentity();
gl.Perspective(60.0f, (double)Width / (double)Height, 0.01, 100.0);
gl.LookAt(-, , , , , , , , );
gl.MatrixMode(OpenGL.GL_MODELVIEW);
} //测试例子
public void rtest(ref OpenGL gl, float xPos, float yPos, float zPos)
{
gl.PushMatrix();
{
gl.Color(1f, 0f, 0f);
gl.Translate(xPos, yPos, zPos);
gl.Scale(2f, 1f, 1f);
gl.Rotate(, 1f, 0f, 0f);
DrawCube(ref gl, , , , true);
}
gl.PopMatrix(); gl.PushMatrix();
{
gl.Color(0f, 1f, 0f);
gl.Translate(xPos - 2f, yPos, zPos);
DrawCube(ref gl, , , , true);
}
gl.PopMatrix();
} public void DrawRobot(ref OpenGL Gl, float xPos, float yPos, float zPos)
{
Gl.PushMatrix();
{
Gl.Translate(xPos, yPos, zPos); ///绘制各个部分
//Gl.LoadIdentity();
DrawHead(ref Gl, 1f, 2f, 0f); // 绘制头部 2*2*2
DrawTorso(ref Gl, 1.5f, 0.0f, 0.0f); //躯干, 3*5*2 Gl.PushMatrix();
{
//如果胳膊正在向前运动,则递增角度,否则递减角度
if (arm1)
armAngle[] = armAngle[] + 1f;
else
armAngle[] = armAngle[] - 1f; ///如果胳膊达到其最大角度则改变其状态
if (armAngle[] >= 15.0f)
arm1 = false;
if (armAngle[] <= -15.0f)
arm1 = true; //平移并旋转后绘制胳膊
Gl.Translate(0.0f, -0.5f, 0.0f);
Gl.Rotate(armAngle[], 1.0f, 0.0f, 0.0f);
DrawArm(ref Gl, 2.5f, 0.0f, -0.5f); //胳膊1, 1*4*1
}
Gl.PopMatrix(); Gl.PushMatrix();
{
if (arm2)
armAngle[] = armAngle[] + 1f;
else
armAngle[] = armAngle[] - 1f; if (armAngle[] >= 15.0f)
arm2 = false;
if (armAngle[] <= -15.0f)
arm2 = true; Gl.Translate(0.0f, -0.5f, 0.0f);
Gl.Rotate(armAngle[], 1.0f, 0.0f, 0.0f);
DrawArm(ref Gl, -1.5f, 0.0f, -0.5f); //胳膊2, 1*4*1
}
Gl.PopMatrix(); Gl.PushMatrix();
{
///如果腿正在向前运动,则递增角度,否则递减角度
if (leg1)
legAngle[] = legAngle[] + 1f;
else
legAngle[] = legAngle[] - 1f; ///如果腿达到其最大角度则改变其状态
if (legAngle[] >= 15.0f)
leg1 = false;
if (legAngle[] <= -15.0f)
leg1 = true; ///平移并旋转后绘制胳膊
Gl.Translate(0.0f, -0.5f, 0.0f);
Gl.Rotate(legAngle[], 1.0f, 0.0f, 0.0f);
DrawLeg(ref Gl, -0.5f, -5.0f, -0.5f); //腿部1,1*5*1
}
Gl.PopMatrix(); Gl.PushMatrix();
{
if (leg2)
legAngle[] = legAngle[] + 1f;
else
legAngle[] = legAngle[] - 1f; if (legAngle[] >= 15.0f)
leg2 = false;
if (legAngle[] <= -15.0f)
leg2 = true; Gl.Translate(0.0f, -0.5f, 0.0f);
Gl.Rotate(legAngle[], 1.0f, 0.0f, 0.0f);
DrawLeg(ref Gl, 1.5f, -5.0f, -0.5f); //腿部2, 1*5*1
}
Gl.PopMatrix();
}
Gl.PopMatrix();
} // 绘制一个手臂
void DrawArm(ref OpenGL Gl, float xPos, float yPos, float zPos)
{
Gl.PushMatrix();
Gl.Color(1.0f, 0.0f, 0.0f); // 红色
Gl.Translate(xPos, yPos, zPos);
Gl.Scale(1.0f, 4.0f, 1.0f); // 手臂是1x4x1的立方体
DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
Gl.PopMatrix();
} // 绘制一条腿
void DrawLeg(ref OpenGL Gl, float xPos, float yPos, float zPos)
{
Gl.PushMatrix();
Gl.Color(1.0f, 1.0f, 0.0f); // 黄色
Gl.Translate(xPos, yPos, zPos);
Gl.Scale(1.0f, 5.0f, 1.0f); // 腿是1x5x1长方体
DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
Gl.PopMatrix();
} // 绘制头部
void DrawHead(ref OpenGL Gl, float xPos, float yPos, float zPos)
{
Gl.PushMatrix();
Gl.Color(1.0f, 1.0f, 1.0f); // 白色
Gl.Translate(xPos, yPos, zPos);
Gl.Scale(2.0f, 2.0f, 2.0f); //头部是 2x2x2长方体
DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
Gl.PopMatrix();
} // 绘制机器人的躯干
void DrawTorso(ref OpenGL Gl, float xPos, float yPos, float zPos)
{
Gl.PushMatrix();
Gl.Color(0.0f, 0.0f, 1.0f); // 蓝色
Gl.Translate(xPos, yPos, zPos);
Gl.Scale(3.0f, 5.0f, 2.0f); // 躯干是3x5x2的长方体
DrawCube(ref Gl, 0.0f, 0.0f, 0.0f,false);
Gl.PopMatrix();
} void drawGrid(OpenGL gl)
{
//绘制过程
gl.PushAttrib(OpenGL.GL_CURRENT_BIT); //保存当前属性
gl.PushMatrix(); //压入堆栈
gl.Translate(0f, -20f, 0f);
gl.Color(0f, 0f, 1f); //在X,Z平面上绘制网格
for (float i = -; i <= ; i += )
{
//绘制线
gl.Begin(OpenGL.GL_LINES);
{
if (i == )
gl.Color(0f, 1f, 0f);
else
gl.Color(0f, 0f, 1f); //X轴方向
gl.Vertex(-50f, 0f, i);
gl.Vertex(50f, 0f, i);
//Z轴方向
gl.Vertex(i, 0f, -50f);
gl.Vertex(i, 0f, 50f); }
gl.End();
}
gl.PopMatrix();
gl.PopAttrib();
} internal void DrawCube(ref OpenGL Gl, float xPos, float yPos, float zPos,bool isLine)
{
Gl.PushMatrix();
Gl.Translate(xPos, yPos, zPos);
if (isLine)
Gl.Begin(OpenGL.GL_LINE_STRIP);
else
Gl.Begin(OpenGL.GL_POLYGON); // 顶面
Gl.Vertex(0.0f, 0.0f, 0.0f);
Gl.Vertex(0.0f, 0.0f, -1.0f);
Gl.Vertex(-1.0f, 0.0f, -1.0f);
Gl.Vertex(-1.0f, 0.0f, 0.0f); // 前面
Gl.Vertex(0.0f, 0.0f, 0.0f);
Gl.Vertex(-1.0f, 0.0f, 0.0f);
Gl.Vertex(-1.0f, -1.0f, 0.0f);
Gl.Vertex(0.0f, -1.0f, 0.0f); // 右面
Gl.Vertex(0.0f, 0.0f, 0.0f);
Gl.Vertex(0.0f, -1.0f, 0.0f);
Gl.Vertex(0.0f, -1.0f, -1.0f);
Gl.Vertex(0.0f, 0.0f, -1.0f); // 左面
Gl.Vertex(-1.0f, 0.0f, 0.0f);
Gl.Vertex(-1.0f, 0.0f, -1.0f);
Gl.Vertex(-1.0f, -1.0f, -1.0f);
Gl.Vertex(-1.0f, -1.0f, 0.0f); // 底面
Gl.Vertex(0.0f, 0.0f, 0.0f);
Gl.Vertex(0.0f, -1.0f, -1.0f);
Gl.Vertex(-1.0f, -1.0f, -1.0f);
Gl.Vertex(-1.0f, -1.0f, 0.0f); // 后面
Gl.Vertex(0.0f, 0.0f, 0.0f);
Gl.Vertex(-1.0f, 0.0f, -1.0f);
Gl.Vertex(-1.0f, -1.0f, -1.0f);
Gl.Vertex(0.0f, -1.0f, -1.0f);
Gl.End();
Gl.PopMatrix();
} }
}

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

(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/

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. Windows 2008安装Oracle10g提示操作系统版本检查未通过

    原文链接:http://www.cnblogs.com/emanlee/archive/2012/12/18/2824147.html 因开发环境需要,在Windows Server 2008 R2 ...

  2. Android中利用C++处理Bitmap对象

    相信有些Android&图像算法开发者和我一样,遇到过这样的状况:要对Bitmap对象做一些密集计算(例如逐像素的滤波),但是在java层写循环代码来逐像素操作明显是不现实的,因为Java代码 ...

  3. 【玩转Golang】reflect.DeepEqual

    如果有两个map,内容都一样,只有顺序不同 m1:=map[,,}; m2:=map[,,}; 我们怎么判断二者是否一致呢? 如果你打算这么写: fmt.Println("m1==m2&qu ...

  4. thinkphp 外部js语言包

    Thinkphp php文件也外部js文件公用同一个语言包 一 . php语言包转json数据格式 1.新建验证字段的语言包 application\common\lang\validate-cn.p ...

  5. 【WP8】键盘弹出时控制Frame位置

    WP上,当使用TextBox输入文字的时候,键盘会把TextBox向上推一段距离 当编辑第二个TextBox的时候,页面被上推,键盘刚好和TextBox靠在一起 当编辑第一个TextBox的时候,页面 ...

  6. 《HTTP权威指南》学习笔记——URL和资源

    URL与资源 URL是互联网资源的标准化名称 1.浏览互联网资源 URL是浏览器寻找信息时所需的资源位置 URI是一类更通用的资源标识符,URL是它的子集. URI的两个子集:URL和URN URL提 ...

  7. wingIDE设置支持中文注释

    用wingIDE快2年了,实在是受不了不支持中文,每次中文都报错,一个小逗号也要查很久,别说中文注释了,在网上找解决办法,原来这么简单! 下面,把破解放大和支持中文支持的方法备份下来,以便以后查看. ...

  8. linux下使用yum安装 mencached

    1. 安装 yum -y install memcached 2. 启动memcached ./usr/bin/memcached -d -m 256 -u root -p 11211 -c 1024 ...

  9. linux中通过awk进行文本的对齐格式化处理?awk printf左对齐?

    需求描述: 今天在对一个从excel文件中粘出来的内容进行整理的时候,发现格式很乱,就想用awk工具格式化一下,在此记录一下. 操作过程: 1.从excel中复制出来的内容 job_name    j ...

  10. swagger的说明、配置及使用

    一.What is swagger? 官方介绍:Swagger是一个规范且完整的框架,提供描述.生产.消费和可视化RESTful Web Service.专业角度:Swagger是由庞大工具集合支撑的 ...