一、简介

在虚拟的游戏世界中,与3D有关的数学知识决定了游戏引擎如何计算和模拟出开发者以及玩家看到的每一帧画面。学习或者回想一下基础的3D有关的数学知识,可以帮助开发者对游戏引擎产生更深刻的了解。

二、向量

在数学中向量的定义是:既有大小又有方向的量叫作向量。在空间中,向量可以用一段有方向的线段来表示。

向量在游戏开发过程中的应用十分广泛,可用于描述具有大小和方向两个属性的物理量,例如物体运动的速度、加速度、摄像机观察方向、刚体受到的力等都是向量,因此向量是物理、动画、三维图形的基础。

1、向量相关概念

·模:向量的长度。

·标准化(Normalizing):保持方向不变,将向量的长度变为1。

·单位向量:长度为1的向量。

·零向量:各分量均为0的向量。

2、向量的运算

(1)加减

向量的加法(减法)为各分量分别相加(相减)。在物理上可以用来计算两个力的台力,或者几个速度分量的叠加。

unity取向量的模_叉乘

(2)数乘

向量与一个标量相乘称为数乘。数乘可以对向量的长度进行缩放,如果标量大于0,那么向量的方向不变;若标量小于0,则向量的方向会变为反方向。

(3)点乘

两个向量点相乘得到一个标量,数值等于两个向量长度相乘后再乘以二者夹角的余弦值。如果两个向量a,b均为单位向量,那么a.b等于向量b在向量a方向上的投影的长度(也等于向量a在向量b方向的投影)


unity取向量的模_向量_02


通过两个向量点乘结果的符号可以快速地判断两个向量的夹角情况:

若u·v = 0,则向量u、v相互垂直。

若u·v > 0,则向量u、v夹角小于90°。

若u·v <0,则向量u、v夹角大于90°。

(4)叉乘

两个向量的叉乘得到一个新的向量,新向量垂直于原来的两个向量,并旦长度等于原向量长度相乘后再乘夹角的正弦值。

unity取向量的模_unity取向量的模_03

可以通过左手摆出上图所示的手势来判断叉乘结果的方向(使用左手是因为Unity里用的是左手坐标系)。假设有向量Result=a×b,将拇指朝向a的方向,食指指向b的方向,则中指指向的方向为叉乘结果的方向。

注意:叉乘不满足交换律,即a× b≠ b× a。

三、Vector3类

在Unity中,和向量有关的类有Vector2、Vector3、Vector4,分别对应不同维度的向量,其中Vector3的使用最为广泛。

1、成员变量和方法

Vector3类的常用成员变量和方法如下:

x、y、z:向量的X分量、Y分量、Z分量。

normalized:获取单位化后的向量(只读)。

magnitude:获取向量长度(只读)。

sqrMagnitude获取向量长度的平方(只读)。

Cross():向量叉乘。

Dot():向量点乘。

Project():计算向量在另一向量上的投影。

Angle():返回两个向量之间的夹角。

Distance():返回两个向量之间的距离。

运算符:+、-、*、/、==、!=

下面是Vector3类的—些应用示例。

2、示例1(Demo7_1_DistanceTest.unity)

该例子演示了如何计算两个位置之间的距离。其中other变量绑定的游戏对象的坐标为(50,50,50),脚本绑定的目标对象的坐标为(-2,-3.3,-10)。

在Distance.unity场景中使用了本示例的源文件,运行效果如下:


unity取向量的模_unity取向量的模_04


下面是该场景中使用的C#脚本代码(DistanceTest.cs文件):


unity取向量的模_向量_05



using UnityEngine;
using System.Collections;
public class DistanceTest : MonoBehaviour
{
    public Transform other;
    void Start()
    {
        if (other != null)
        {
            var dist = Vector3.Distance(other.position, transform.position);
            print("Distance to other: " + dist);
        }
    }
}



unity取向量的模_向量_05


3、示例2(Demo7_2_DirectionMove.unity)

该例子演示如何让游戏对象沿着指定的方向移动。

DirectionMove.cs的代码如下:


unity取向量的模_向量_05



using UnityEngine;
using System.Collections;
public class DirectionMove : MonoBehaviour
{
    public Vector3 direction = Vector3.forward;//移动方向
    public float speed = 5.0f;//速度

    void Update()
    {
        transform.position += direction * speed * Time.deltaTime;
    }
}



unity取向量的模_向量_05


4、示例3(Demo7_3_SqrMagnitude.unity)

该例子演示如何利用Vector3.sqrMagnitude来判断目标对象的距离是否小于触发距离,其中other变量的坐标为(-2,-3,-10),该脚本绑定的游戏对象的坐标为(-2,-3.3,-10)。

使用的脚本(SqrMagnitude.cs文件)代码如下:


unity取向量的模_向量_05



using UnityEngine;
using System.Collections;
public class SqrMagnitude : MonoBehaviour
{
    public Transform other;   //目标物体的Transform
    public double closeDistance = 5.0;  // 触发距离
    void Update()
    {
        if (other)
        {
            var sqrLen = (other.position - transform.position).sqrMagnitude;
            // 使用Vector3.sqrMagnitude比Vector3.magnitude计算速度要快
            if (sqrLen < closeDistance * closeDistance)
            {
                print("目标物体已靠近!");
            }
        }
    }
}



unity取向量的模_向量_05


5、示例4(Demo7_4_MoveToTarget.unity)

该例子演示对象从初始点平滑移动到目标点的动画,其中start变量绑定的对象坐标为(-3,-3,-10),end变量绑定的坐标为(-3,-3,20)。

使用的脚本(MoveToTarget.cs文件)代码如下:


unity取向量的模_向量_05



using UnityEngine;
using System.Collections;
public class MoveToTarget : MonoBehaviour
{
    public Transform start; //初始位置
    public Transform end; //终点位置
    void Update()
    {
        transform.position = Vector3.Lerp(start.position, end.position, Time.time);
    }
}



unity取向量的模_向量_05


6、示例5(Demo7_5_SunRiseAndDown.unity)

该例子演示如何利用Slerp插值方法模拟太阳升起和落下的过程。

使用的脚本(SunRiseAndDown.cs文件)代码如下:


unity取向量的模_向量_05



using UnityEngine;
using System.Collections;
public class SunAndRise : MonoBehaviour
{
    public Transform sunrise;//升起位置
    public Transform sunset; //落下位置 
    public float journeyTime = 10.0f; //从升起到落下需要的时间,以秒为单位
    private float startTime; //用于记录开始的时间

    void Start()
    {
        startTime = Time.time; // 设置开始的时间
    }

    void Update()
    {
        var center = (sunrise.position + sunset.position) * 0.5f;//计算运行轨迹的圆心点
        center -= new Vector3(0, 1, 0);
        var riseRelCenter = sunrise.position - center;//升起位置到圆心的向量
        var setRelCenter = sunset.position - center; //落下位置到圆心的向量
        var fracComplete = (Time.time - startTime) / journeyTime;//计算用于插值的系数
        transform.position = Vector3.Slerp(riseRelCenter, setRelCenter, fracComplete);//Slerp插值
        transform.position += center;
    }
}



unity取向量的模_向量_05


下面是该例子的运行效果:


unity取向量的模_unity3d_15


四、矩阵

相向量一样,矩阵也是3D数学中十分重要基础。

1、矩阵的概念

m× n的矩阵是一个具有m行、n列的矩形数组,行数和列数分别为矩阵的维度。在游戏引擎中使用的矩阵通常为4×4矩阵,因为它可以描述向量的平移、旋转、缩放等所有的线性变换。

2、矩阵的计算

自己看其他参考资料吧。

3、Matrix4x4类

Unity使用Matrix4x4类来描述4×4矩阵。

更多介绍请参看其他参考资料。

五、奇次坐标

在3D数学中,齐次坐标就是将原本3维的向量(x,y,z)用4维向量(wx,wy,wz,w)来表示。引入齐次坐标的主要有如下目的:

1、可更好地区分向量和点。在三维空间中, (x,y,z)既可以表示点也可以表示分量,不便于区分,如果引入齐次坐标,则可以使用(x,y,z,1)来表示坐标点,使用(x,y,z,0)来表示向量。

2、统—用矩阵乘法表示平移、旋转、缩放变换。如果使用3×3的矩阵,矩阵乘法只能表示旋转和缩放变换,无法表示平移变换。而在4D齐次空间中,可以使用4×4的齐次矩阵来统一表示平移、旋转、缩放变换。

3、当分量w=0时可以用来表示无穷远的点。

齐次坐标是计算机图形学中一个非常重要的概念,但是在Unity中很少需要直接和它打交道,在编写一些shader可能会用到它,平时在脚本中主要还是使用三维向量Vector3。Unity通过便利的接口设计将这一重要概念隐藏在了引擎后面。