UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Landscape Tool
Using Editor Tools for Specific Tasks
Extending Editor Functionality
编程
Setting Up Development Environment
Usage Examples
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
应用程序接口
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes

矩阵变换

UNIGINE中的许多计算都是通过使用矩阵进行的。实际上,矩阵变换是3D引擎的主要概念之一。本文包含使用示例的矩阵转换说明。

也可以看看#

转变#

简单来说,3D图形中的矩阵是按行和列排列的数字数组:

通常使用 4x4 矩阵。矩阵的这种大小(4x4)是由3D空间中的平移状态引起的。当您将新节点放到世界中时,它具有一个4x4的世界变换矩阵,该矩阵定义了该节点在世界中的位置。

在UNIGINE中,矩阵是主列(面向列)。因此,变换矩阵的第一列代表局部坐标系的 X 向量( v1 ),第二列代表 Y 向量( v2 ),第三个代表 Z 向量( v3 ),第四个代表转换向量 t 。前三列显示局部坐标轴的方向(旋转)和原点的比例。最后一列包含本地起源相对于世界起源的翻译。

身份矩阵#

世界起源具有以下矩阵:

此矩阵称为恒等矩阵,该矩阵的主对角线为 ones ,其他地方为 zeros 。如果将矩阵与单位矩阵相乘,则不会发生任何变化:所得矩阵将与乘法前相同。

如果本地原点具有单位矩阵,则表示本地原点与世界原点是巧合

回转#

要更改本地原点的方向,应更改矩阵的前三列。

要沿不同的轴旋转原点,应使用适当的矩阵:

在上面给出的矩阵中,α是沿轴的旋转角度。

下一个矩阵显示本地原点沿 Y 轴以45度旋转:

翻译#

变换矩阵的最后一列显示相对于世界原点的世界上本地原点的位置。下一个矩阵显示了原点的翻译。转换向量 t (3,0,2)

缩放比例#

向量的长度显示沿轴的比例系数。

要计算矢量长度(也称为幅度),您应该找到矢量分量平方和的平方根。公式如下:

Vector Length
|vector length| = √(x² + y² + z²)

下面的矩阵沿所有轴将本地原点最多缩放2个单位。

累计转换#

代码中矩阵转换的顺序非常重要。

如果要实现一系列累积转换,则代码中的转换顺序应如下:

Transformation order
TransformedVector = TransformationMatrixN * ... * TransformationMatrix2 * TransformationMatrix1 * Vector

从TransformationMatrix1开始到TransformationMatrixN结束,一个接一个地应用转换。

#

此示例显示了矩阵转换的两个顺序之间的差异。

下面的代码示例获取物料球对象。在第一种情况下,旋转后进行翻译,在第二种情况下,平移后进行旋转。

AppWorldLogic.h文件中,定义material_ball节点智能指针。

源代码 (C++)
// 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 Unigineusing namespace Unigine::Math指令:Unigine和Unigine::Math命名空间的名称将注入到全局命名空间中。
  • 通过将show_visualizer 1 命令传递给run() function of the Console class.
  • 从编辑器中获取material ball
  • 创建新的旋转和平移矩阵。
  • 计算新的变换矩阵并将其应用于material ball
  • 使用VisualizerrenderVector()类的方法渲染世界原点。
源代码 (C++)
// 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(1.0f,0.0f,0.0f,1.0f));
	Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,1.0f,0.1f), vec4(0.0f,1.0f,0.0f,1.0f));
	Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,0.0f,1.1f), vec4(0.0f,0.0f,1.0f,1.0f));

	return 1;
}

要更改顺序,只需更改累积转换的行即可:

源代码 (C++)
Mat4 transform = rotation_matrix * translation_matrix * material_ball->getTransform();

结果将有所不同。下图显示了差异(相机位于同一位置)。

顺序:旋转和平移
顺序:平移和旋转

上面的图片显示了与世界原点相关的网格的位置。

矩阵层次结构#

一个更重要的概念是矩阵层次结构。当一个节点作为另一个节点的子节点添加到世界中时,它具有与父节点相关的转换矩阵。这就是为什么Node类区分函数getTransform(), setTransform()getWorldTransform(), setWorldTransform()的原因,它们分别返回局部和世界变换矩阵。

注意
如果添加的节点没有父节点,则此节点使用 World转换矩阵

使用矩阵层次结构的原因是什么?相对于另一个节点移动一个节点。而且,当您移动父节点时,子节点也将被移动。

父原点与世界原点相同
父原点已移动,子原点也已移动

上面的图片显示了矩阵层次结构的要点。当父原点(节点)移动时,chlld原点也将移动,并且子节点的局部转换矩阵也不会更改。但是孩子的世界转换矩阵将会改变。如果需要与世界原点相关的子项的世界变换矩阵,则应使用getWorldTransform(), setWorldTransform()函数;如果需要与父级相关的子级的局部转换矩阵,则应使用getTransform(), setTransform()函数。

#

下面的示例显示矩阵层次结构的重要性。

在此示例中,我们获取节点并将其克隆。然后,我们更改这些节点的变换矩阵。我们回顾两种情况:

  1. 两个节点是独立的。
  2. 一个节点是另一个节点的子节点。

AppWorldLogic.h文件中,定义material_ball子节点和父节点的智能指针。

源代码 (C++)
// AppWorldLogic.h

/* ... */

class AppWorldLogic : public Unigine::WorldLogic {
	
public:
	/* .. */
private:
	Unigine::NodePtr material_ball_child;
	Unigine::NodePtr material_ball_parent;
};

AppWorldLogic.cpp中,实现以下代码:

源代码 (C++)
// 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(1.0f,0.0f,0.0f,1.0f));
	Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,1.0f,0.1f), vec4(0.0f,1.0f,0.0f,1.0f));
	Visualizer::renderVector(Vec3(0.0f,0.0f,0.1f), Vec3(0.0f,0.0f,1.1f), vec4(0.0f,0.0f,1.0f,1.0f));

	return 1;
}

int AppWorldLogic::shutdown() {
	// clear smart pointers
	material_ball_child.clear();
	material_ball_parent.clear();

	return 1;
}

如果您注释以下行:

源代码 (C++)
// make the one node the child of another
material_ball_parent->addChild(material_ball_child);

您会得到不同的结果:

父子节点
节点是独立的

当节点独立时,它们具有不同的本地和世界转换矩阵。对于父子节点,子对象的局部变换矩阵在移动后保持不变,但是世界变换矩阵已更改(可以使用调试概要分析器进行检查)。

最新更新: 2020-10-10