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

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. eclipse中svn提交报错的解决

  2. python模块之 - subprocess执行unix/linux命令

    subprocess模块提供了一种一致的方法来创建和处理附加进程,与标准库中的其它模块相比,提供了一个更高级的接口,subprocess模块用来生成子进程,并可以通过管道连接它们的输入/输出/错误,以 ...

  3. interproscan 软件对序列进行GO 注释

    interproscan 软件实际上将对输入的查询序列和interpro 数据库中的序列去比对,将比对上的序列对应的GO信息作为查询序列的GO注释 在interpro 数据库中,每条蛋白质序列有一个唯 ...

  4. R语言ggplot2 简介

    ggplot2是一个绘制可视化图形的R包,汲取了R语言基础绘图系统(graphics) 和l attice包的优点,摒弃了相关的缺点,创造出来的一套独立的绘图系统: ggplot2 有以下几个特点: ...

  5. AsmTools

    前言 https://wiki.openjdk.java.net/display/CodeTools/asmtools 在OpenJDK里有一个AsmTools项目,用来生成正确的或者不正确的java ...

  6. kafka学习之-KafkaOffsetMonitor后台监控

    1.下载Kafka Consumer Offset Monitor安装包 http://pan.baidu.com/s/1ntzIUPN 2.在/usr/local/hadoop路径下面建立放置Kaf ...

  7. ADCD 1.9 ZOS 配置 CTCI-W32 TCPIP 网络

    试验步骤:两步走,第一步修改Hercules的配置文件 在hercules 配置文件末尾加上    0E20-0E21 CTCI     -n 0A-00-27-00-00-00  192.168.5 ...

  8. .net的session详解 存储模式 存到数据库中 使用范围与大小限制 生命周期

    Session又称为会话状态,是Web系统中最常用的状态,用于维护和当前浏览器实例相关的一些信息.举个例子来说,我们可以把已登录用户的用户名放在Session中,这样就能通过判断Session中的某个 ...

  9. 如果你的eclipse在每次run或debug时都莫名其妙的做一件事

    新项目,使用Ant打war包.结果写完了Ant以后,包是打好了,却使eclipse以后每次run或debug时都莫名其妙地自动先执行这个Ant, 让人十分苦恼. 其实,是你的eclipse设置出了问题 ...

  10. jrMz and angles(水题)

    jrMz and angles Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)T ...