矩阵变换
转变#
简单来说,3D图形中的矩阵是按行和列排列的数字数组:
通常使用 4x4 矩阵。矩阵的这种大小(4x4)是由3D空间中的平移状态引起的。当您将新节点放到世界中时,它具有一个4x4的世界变换矩阵,该矩阵定义了该节点在世界中的位置。
在UNIGINE中,矩阵是主列(面向列)。因此,变换矩阵的第一列代表局部坐标系的 X 向量( v1 ),第二列代表 Y 向量( v2 ),第三个代表 Z 向量( v3 ),第四个代表转换向量 t 。前三列显示局部坐标轴的方向(旋转)和原点的比例。最后一列包含本地起源相对于世界起源的翻译。
身份矩阵#
世界起源具有以下矩阵:
此矩阵称为恒等矩阵,该矩阵的主对角线为 ones ,其他地方为 zeros 。如果将矩阵与单位矩阵相乘,则不会发生任何变化:所得矩阵将与乘法前相同。
如果本地原点具有单位矩阵,则表示本地原点与世界原点是巧合。
回转#
要更改本地原点的方向,应更改矩阵的前三列。
要沿不同的轴旋转原点,应使用适当的矩阵:
在上面给出的矩阵中,α是沿轴的旋转角度。
下一个矩阵显示本地原点沿 Y 轴以45度旋转:
![]() |
![]() |
翻译#
变换矩阵的最后一列显示相对于世界原点的世界上本地原点的位置。下一个矩阵显示了原点的翻译。转换向量 t 是(3,0,2)。
![]() |
![]() |
缩放比例#
向量的长度显示沿轴的比例系数。
要计算矢量长度(也称为幅度),您应该找到矢量分量平方和的平方根。公式如下:
|vector length| = √(x² + y² + z²)
下面的矩阵沿所有轴将本地原点最多缩放2个单位。
![]() |
![]() |
累计转换#
代码中矩阵转换的顺序非常重要。
如果要实现一系列累积转换,则代码中的转换顺序应如下:
TransformedVector = TransformationMatrixN * ... * TransformationMatrix2 * TransformationMatrix1 * Vector
从TransformationMatrix1开始到TransformationMatrixN结束,一个接一个地应用转换。
例#
此示例显示了矩阵转换的两个顺序之间的差异。
下面的代码示例获取物料球对象。在第一种情况下,旋转后进行翻译,在第二种情况下,平移后进行旋转。
在AppWorldLogic.h文件中,定义material_ball节点智能指针。
// AppWorldLogic.h
/* ... */
class AppWorldLogic : public Unigine::WorldLogic {
public:
/* .. */
private:
Unigine::NodePtr material_ball;
};
在AppWorldLogic.cpp文件中,执行以下操作:
- Include the UnigineEditor.h, UnigineVisualizer.h, UnigineConsole.h headers.
- 使用using namespace Unigine和using namespace Unigine::Math指令:Unigine和Unigine::Math命名空间的名称将注入到全局命名空间中。
- 通过将show_visualizer 1 命令传递给run() function of the Console class.
- 从编辑器中获取material ball。
- 创建新的旋转和平移矩阵。
- 计算新的变换矩阵并将其应用于material ball。
- 使用VisualizerrenderVector()类的方法渲染世界原点。
// AppWorldLogic.cpp file
#include "AppWorldLogic.h"
#include "UnigineWorld.h"
#include "UnigineVisualizer.h"
#include "UnigineConsole.h"
// inject Unigine and Unigine::Math namespaces names to global namespace
using namespace Unigine;
using namespace Unigine::Math;
/* ... */
int AppWorldLogic::init() {
// enable the visualizer for world origin rendering
Console::run("show_visualizer 1");
// get the material ball
material_ball = World::getNodeByName("material_ball");
// create rotation and translation matrices
Mat4 rotation_matrix = (Mat4)rotateZ(-90.0f);
Mat4 translation_matrix = (Mat4)translate(vec3(0.0f, 3.0f, 0.0f));
// create a new transformation matrix for the material ball
// by multiplying the current matrix by rotation and translation matrices
Mat4 transform = translation_matrix * rotation_matrix * material_ball->getTransform();
// set the transformation matrix to the material ball
material_ball->setTransform(transform);
return 1;
}
int AppWorldLogic::update() {
// render world origin
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(1.0f,0.0f,0.1f), vec4_red);
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,1.0f,0.1f), vec4_green);
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,0.0f,1.1f), vec4_blue);
return 1;
}
要更改顺序,只需更改累积转换的行即可:
Mat4 transform = rotation_matrix * translation_matrix * material_ball->getTransform();
结果将有所不同。下图显示了差异(相机位于同一位置)。
![]() |
![]() |
顺序:旋转和平移
|
顺序:平移和旋转
|
上面的图片显示了与世界原点相关的网格的位置。
矩阵层次结构#
一个更重要的概念是矩阵层次结构。当一个节点作为另一个节点的子节点添加到世界中时,它具有与父节点相关的转换矩阵。这就是为什么Node类区分函数getTransform(), setTransform()和getWorldTransform(), setWorldTransform()的原因,它们分别返回局部和世界变换矩阵。
使用矩阵层次结构的原因是什么?相对于另一个节点移动一个节点。而且,当您移动父节点时,子节点也将被移动。
![]() |
![]() |
父原点与世界原点相同
|
父原点已移动,子原点也已移动
|
上面的图片显示了矩阵层次结构的要点。当父原点(节点)移动时,子原点也将移动,并且子节点的局部转换矩阵也不会更改。但是孩子的世界转换矩阵将会改变。如果需要与世界原点相关的子项的世界变换矩阵,则应使用getWorldTransform(), setWorldTransform()函数;如果需要与父级相关的子级的局部转换矩阵,则应使用getTransform(), setTransform()函数。
例#
下面的示例显示矩阵层次结构的重要性。
在此示例中,我们获取节点并将其克隆。然后,我们更改这些节点的变换矩阵。我们回顾两种情况:
- 两个节点是独立的。
- 一个节点是另一个节点的子节点。
在AppWorldLogic.h文件中,定义material_ball子节点和父节点的智能指针。
// AppWorldLogic.h
/* ... */
class AppWorldLogic : public Unigine::WorldLogic {
public:
/* .. */
private:
Unigine::NodePtr material_ball_child;
Unigine::NodePtr material_ball_parent;
};
在AppWorldLogic.cpp中,实现以下代码:
// AppWorldLogic.cpp
#include "AppWorldLogic.h"
#include "UnigineEditor.h"
#include "UnigineVisualizer.h"
#include "UnigineConsole.h"
#include "UnigineLog.h"
using namespace Unigine;
using namespace Unigine::Math;
int AppWorldLogic::init() {
// enable the visualizer for world origin rendering
Console::run("show_visualizer 1");
// get the material ball and clone it
material_ball_child = World::getNodeByName("material_ball");
material_ball_parent = material_ball_child->clone();
// make the one node the child of another
material_ball_parent->addChild(material_ball_child);
// create rotation and translation matrices for the first material_ball
Mat4 rotation_matrix = (Mat4)rotateZ(-90.0f);
Mat4 translation_matrix = (Mat4)translate(vec3(3.0f, 0.0f, 0.0f));
// create translation matrix for the second (parent) material ball
Mat4 translation_matrix_clone = (Mat4)translate(vec3(0.5f, 0.0f, 1.0f));
// create new transformation matrices for the material balls
Mat4 transform = translation_matrix * rotation_matrix * material_ball_child->getTransform();
Mat4 transform_clone = translation_matrix_clone * material_ball_parent->getTransform();
// set the transformation matrices to the material balls
material_ball_child->setTransform(transform);
material_ball_parent->setTransform(transform_clone);
return 1;
}
int AppWorldLogic::update() {
// render world origin
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(1.0f,0.0f,0.1f), vec4_red);
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,1.0f,0.1f), vec4_green);
Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,0.0f,1.1f), vec4_blue);
return 1;
}
int AppWorldLogic::shutdown() {
// clear smart pointers
material_ball_child.clear();
material_ball_parent.clear();
return 1;
}
如果您注释以下行:
// make the one node the child of another
material_ball_parent->addChild(material_ball_child);
您会得到不同的结果:
![]() |
![]() |
父子节点
|
节点是独立的
|
当节点独立时,它们具有不同的本地和世界转换矩阵。对于父子节点,子对象的局部变换矩阵在移动后保持不变,但是世界变换矩阵已更改(可以使用调试概要分析器进行检查)。
本页面上的信息适用于 UNIGINE 2.20 SDK.