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

时间:2022-05-31 14:42:00

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

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();
}

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

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

我们修改下代码, 把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()就相互独立了起来, 不会相互影响.

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

最后我们给出一个运用"变换"的综合性的例子, 它画了一个手脚能动的机器人, 场景做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()里.

程序运行时截取了一帧,效果如下:

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

OpenGL的"变换" 主题终于彻底讲完了! 最初笔者接触这些内容时, 感觉术语太多, 枯燥无趣. 但是它是OpenGL真正最基本的入门功夫. 就像 *亮在《OpenGL游戏编程》这本书里说的: 说完OpenGL变换的知识后, 读者已经有足够的基础可以开始编写游戏了!

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

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

本节源代码下载

原创文章,出自"博客园, 猪悟能'S博客" : http://www.cnblogs.com/hackpig/

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

  1. SharpGL学习笔记&lpar;三&rpar; 投影变换和视点变换

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

  2. SharpGL学习笔记&lpar;七&rpar; OpenGL的变换总结

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

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

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

  4. Learning ROS forRobotics Programming Second Edition学习笔记&lpar;八&rpar;indigo rviz gazebo

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

  5. python3&period;4学习笔记&lpar;八&rpar; Python第三方库安装与使用,包管理工具解惑

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

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

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

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

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

  8. go微服务框架kratos学习笔记八 &lpar;kratos的依赖注入&rpar;

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

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

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

随机推荐

  1. Tomcat配置HTTPS方式&lpar;单向&rpar;

    简要记录主要步骤备忘 1.进入到jdk下的bin目录 2.输入如下指令 keytool -v -genkey -alias tomcat -keyalg RSA -keystore d:/tomcat ...

  2. HDU 4497 数论&plus;组合数学

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4497 解题思路:将满足条件的一组x,z,y都除以G,得到x‘,y',z',满足条件gcd(x',y' ...

  3. C难点分析

    1. 形参和实参 调用函数时,写在括号里面的就是实参,函数本身用的就是形参. 2.字符串问题 char a[5]={"abcd"};注意是4个字符,而不是5个  字符串数组后面带  ...

  4. EasyMock使用说明

    来自官网的使用说明,原文见http://www.easymock.org/EasyMock2_0_Documentation.html 1.1. 准备 大多数的软件系统都不是单独运行的,它们都需要于其 ...

  5. How to delete a team project from Team Foundation Service &lpar;tfs&period;visualstudio&period;com&rpar;

    C:\project>tfsdeleteproject /collection:https://buckh-test2.visualstudio.com/DefaultCollection Te ...

  6. CentOS下安装JDK6u21和设置环境变量bin文件

    1.先通过SSH登录到Linux系统中,通过SSH文件管理工具把Linux的JDK安装包上传到/home/acm/JavaTools/JDK目录: 2.进入/home/acm/JavaTools/JD ...

  7. git archive

    git archive --format zip --output ../g.zip 3.4.2 git archive --format=tar \ --remote=ssh://remote_se ...

  8. npm install 插件 --save与 --save -dev的区别

    npm i 插件   ,会把插件安装到node_modules目录中,不会修改package.json, npm i 插件 --save  ,项目发布上线之后还会依赖用到的插件,没有这些插件,项目不能 ...

  9. git 命令小总结

    下载代码 git clone http://admin@192.168.0.208:7990/scm/klvchen/tale.git 设置用默认户名和密码登录,注意 URI 前面不允许有 http, ...

  10. vscode 调试node&period;js

    在开发的过程中,几乎不可能一次性就能写出毫无破绽的程序,断点调试代码是一个普遍的需求. 作为前端开发工程师,以往我们开发的JavaScript程序都运行在浏览器端,利用Chrome提供的开发者工具就可 ...