详解向量与Unity中向量类Vcetor3的使用(二)

详解向量与Unity中向量类Vcetor3的使用(一)中,我们学习了向量的数学意义、几何意义以及向量运算,掌握了向量的概念及使用。在Unity中,向量的表示类为Vector2(2D)与Vector3(3D)。这篇文章,我们结合前面一篇文章,对Vector3的源代码进行解剖,看看内部是如何实现向量的表示及向量计算的,同时例举一个例子帮助理解。掌握了Vector3自然就掌握了Vector2,本篇只讨论Vector3。

1、Vector3向量类

1.1向量类的声明及构造函数

我们先看Unity中Vector3类的声明,采用的是结构体struct而非Class,目的是为了性能优化、内存管理和数据传递效率等。继承了IEquatable接口,主要是为了比较两个向量是否相等,IFormattable接口主要是为了输出向量的字符串形式。

arduino 复制代码
public struct Vector3 : IEquatable<Vector3>, IFormattable

下面的Vector3类中定义了两个float类型的常量以及x、y、z三个float类型的公开字段,分别对应向量在x轴上的分量、y轴上的分量、z轴上的分量,这与我们在上一篇文章说的向量的定义是一致的。为什么选择float类型而不是double类型,其实也是一种提高性能的手段。

arduino 复制代码
public const float kEpsilon = 1E-05f;

public const float kEpsilonNormalSqrt = 1E-15f;

// 摘要:
// X component of the vector.
public float x;

// 摘要:
// Y component of the vector.
public float y;

// 摘要:
// Z component of the vector.
public float z;

我们来看看Vector3的构造函数,如下,有两个构造函数,Vector3(float x, float y, float z)接收三个float类型的值传入,分别为x、y、z赋值。Vector3(float x, float y)接收两个float类型的值传入,分别为x、y赋值,z值为0。 [MethodImpl(MethodImplOptions.AggressiveInlining)]这个特性表示的是:指示编译器对这个方法使用"积极内联"优化,这意味着编译器会尝试将方法的代码直接插入调用处,以提高性能。这个特性在Vector3中的公开的方法中都有。

csharp 复制代码
//
// 摘要:
//     Creates a new vector with given x, y, z components.
// 参数:
//   x:
//   y:
//   z:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3(float x, float y, float z)
{
    this.x = x;
    this.y = y;
    this.z = z;
}

//
// 摘要:
//     Creates a new vector with given x, y components and sets z to zero.
// 参数:
//   x:
//   y:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3(float x, float y)
{
     this.x = x;
     this.y = y;
     z = 0f;
}

例如我们声明两个向量a、b、c表示Unity三维坐标系空间中的某两个位置。

ini 复制代码
Vector3 a = new Vector3(10, 10, 0);
Vector3 c = new Vector3(10, 10);      //在z轴上的分量为0,向量a与向量b是等同的
Vector3 b = new Vector3(20, 0, 20);

1.2向量类中的向量的模长计算

由上一篇文章我们知道一个向量的模长等于其在x、y、z轴上的分量的平方和的平方根,计算公式如下:

我们看下在Vector3类中代码是如何实现的,如下代码:

arduino 复制代码
// 摘要:
//     Returns the length of this vector (Read Only).
public float magnitude
{
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     get
     {
          return (float)Math.Sqrt(x * x + y * y + z * z);
     }
}

//
// 摘要:
//     Returns the squared length of this vector (Read Only).
public float sqrMagnitude
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
    {
         return x * x + y * y + z * z;
    }
}
    
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Magnitude(Vector3 vector)
{
     return (float)Math.Sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SqrMagnitude(Vector3 vector)
{
     return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
}

Vector3提供了一个模长以及一个模长的平方的float值,主要目的在于实际运用过程中如果你只是比较两个向量的模长而不需要计算确切的模长值时,可以使用Vector3.sqrMagnitude,避免了数学上的开根运算,可以提高性能。需要计算确切的模长时使用Vector3.magnitude。同时也可以使用静态方法Magnitude()和SqrMagnitude()传入Vector3来求模长及模长的平方值。

1.3向量类中的单位向量与零向量

我们知道,单位向量就是模长为1的向量,在Vector3提供了哪些单位向量有呢?如下代码,定义了静态只读的常用的单位向量,零向量是既没有长度也没有方向的向量,下面代码中还有一个oneVector,表示的是该向量在x、y、z轴上的分量均为1,该向量一般用于缩放。

很显然,单位向量都是在x、y、z其中一条轴上的分量为1或-1,其余两轴上的分量为0,由向量的模长计算公式可知其模长均为1。

java 复制代码
private static readonly Vector3 zeroVector = new Vector3(0f, 0f, 0f);

private static readonly Vector3 oneVector = new Vector3(1f, 1f, 1f);

private static readonly Vector3 upVector = new Vector3(0f, 1f, 0f);

private static readonly Vector3 downVector = new Vector3(0f, -1f, 0f);

private static readonly Vector3 leftVector = new Vector3(-1f, 0f, 0f);

private static readonly Vector3 rightVector = new Vector3(1f, 0f, 0f);

private static readonly Vector3 forwardVector = new Vector3(0f, 0f, 1f);

private static readonly Vector3 backVector = new Vector3(0f, 0f, -1f);

单位向量通过相应公开的静态属性对外暴露,如下:

csharp 复制代码
// 摘要:
//     Shorthand for writing Vector3(0, 0, 1).
public static Vector3 forward
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    get
     {
         return forwardVector;
     }
}

如果向量不是单位向量,我们怎么求它的单位向量呢?就是向量本身除以本身的模长,计算公式如下:

Vector3中实现代码如下:

csharp 复制代码
//
// 摘要:
//     Returns this vector with a magnitude of 1 (Read Only).
public Vector3 normalized
{
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
      get
      {
          return Normalize(this);
      }
}
    
//
// 摘要:
//     Makes this vector have a magnitude of 1.
// 参数:
//   value:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 Normalize(Vector3 value)
{
    float num = Magnitude(value);
    if (num > 1E-05f)
    {
         return value / num;
    }

    return zero;
}

可以通过已经初始化的向量的属性值normalized,也可以通过传参的方式使用Vector3的静态方法获取。这里做了一个判断处理,如果该向量的模长小于1E-05f,就认为这个向量的标准化向量为零向量

单位向量的一个应用场景就是在unity中知道一条有向线段以及一个该线段方向上的点,我们只要知道该点距离该有向线段的起点或终点的距离,就可以求该点的坐标值。

1.4向量类中向量的加减乘除

1.4.1标量与向量的乘除

由上一篇向量的知识我们已知,一个向量可以与一个标量相乘,得到的是一个与原向量平行的向量,是放大还是缩小,是正向还是反向,取决于标量的值,标量与向量不能进行加减操作。

在Vector3类中代码实现如下:

csharp 复制代码
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator *(Vector3 a, float d)
{
     return new Vector3(a.x * d, a.y * d, a.z * d);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator *(float d, Vector3 a)
{
     return new Vector3(a.x * d, a.y * d, a.z * d);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator /(Vector3 a, float d)
{
     return new Vector3(a.x / d, a.y / d, a.z / d);
}

通过重载"*"与"/"运算符,实现了标量与向量的乘除,这与我们在上一篇中向量的知识一致。

1.4.2向量与向量的加减

相同维数的向量与向量可以进行加减操作,这里我们讨论的是Unity中的三维向量。Vector3中实现如下:

css 复制代码
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator +(Vector3 a, Vector3 b)
{
     return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator -(Vector3 a, Vector3 b)
{
     return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
}

同样通过通过重载"+"与"-"运算符实现向量的加减,两个向量的加减为两个向量在x、y、z轴上的向量进行相加或相减。这里提一下负向量,它表示的是与原向量大小相等,方向相反的向量,实现如下:

csharp 复制代码
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3 operator -(Vector3 a)
{
    return new Vector3(0f - a.x, 0f - a.y, 0f - a.z);
}

1.5两个向量的相等性比较

如果两个向量相等,则表示这两个向量大小相等、方向相同。

csharp 复制代码
 // 摘要:
 //     Returns true if the given vector is exactly equal to this vector.
 // 参数:
 //   other:
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public override bool Equals(object other)
 {
      if (!(other is Vector3))
      {
          return false;
      }

      return Equals((Vector3)other);
 }

 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public bool Equals(Vector3 other)
 {
      return x == other.x && y == other.y && z == other.z;
 }
 
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static bool operator ==(Vector3 lhs, Vector3 rhs)
 {
       float num = lhs.x - rhs.x;
       float num2 = lhs.y - rhs.y;
       float num3 = lhs.z - rhs.z;
       float num4 = num * num + num2 * num2 + num3 * num3;
       return num4 < 9.99999944E-11f;
 }

 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static bool operator !=(Vector3 lhs, Vector3 rhs)
 {
      return !(lhs == rhs);
 }

同样通过通过重载"=="与"!="运算符实现向量的相等性比较,运算符"=="判定是否相等时,若两个向量在x、y、z轴上的分量的差值的平方和小于9.99999944E-11f,则两个向量相等。若采用Equals()方法判定一个向量与另外一个向量是否相等时,则需要判定两个向量在x、y、z轴上的分量是否都相等,若相等则两个向量相等。

1.6距离公式

求两点之间的距离公式,公式表示如下:

Vector3中实现代码如下,很显然是一致的。

csharp 复制代码
 // 摘要:
 //     Returns the distance between a and b.
 //
 // 参数:
 //   a:
 //   b:
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static float Distance(Vector3 a, Vector3 b)
 {
      float num = a.x - b.x;
      float num2 = a.y - b.y;
      float num3 = a.z - b.z;
      return (float)Math.Sqrt(num * num + num2 * num2 + num3 * num3);
 }

举个例子:

ini 复制代码
 //求a、b两点之间的距离,结果应为10
 Vector3 a = new Vector3(10, 10, 0);
 Vector3 b = new Vector3(10, 0, 0);
 var distance = Vector3.Distance(a, b);
 Debug.Log($"向量表示的点与向量b表示的点的距离为{distance}");

输出结果如下:

1.7向量的点乘

向量的点击数学意义是向量各分量的积的和,在Vector3类中向量的点乘为静态函数Dot()表示。基于向量的计算公式实现代码如下:

csharp 复制代码
 //
 // 摘要:
 //     Dot Product of two vectors.
 // 参数:
 //   lhs:
 //   rhs:
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static float Dot(Vector3 lhs, Vector3 rhs)
 {
      return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z;
 }

由上一篇我们可知,通过向量的点乘我们可以求两个向量之间的夹角,公式为:

举个简单例子:

ini 复制代码
 //求向量a与向量b的夹角,输出值应为45°
 Vector3 a = new Vector3(10, 10, 0);
 Vector3 b = new Vector3(10, 0, 0);
 var cos = Vector3.Dot(a, b) / (a.magnitude * b.magnitude);
 var angle = Mathf.Acos(cos);
 Debug.Log($"向量a与向量b的夹角为{angle*180/Mathf.PI}");

输出结果如下:

上面的代码虽然可以求角度,但是过程是先求的两个向量的余弦值,再通过反三角函数及弧度转化为角度得到,代码略显复杂,Vector3提供了一个函数Angle()可以一步到位求两个向量之间的角度值,代码如下:

csharp 复制代码
 // 摘要:
 //     Calculates the angle between vectors from and.
 // 参数:
 //   from:
 //     The vector from which the angular difference is measured.
 //   to:
 //     The vector to which the angular difference is measured.
 // 返回结果:
 //     The angle in degrees between the two vectors.
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static float Angle(Vector3 from, Vector3 to)
 {
      float num = (float)Math.Sqrt(from.sqrMagnitude * to.sqrMagnitude);
      if (num < 1E-15f)
      {
           return 0f;
      }

      float num2 = Mathf.Clamp(Dot(from, to) / num, -1f, 1f);
      return (float)Math.Acos(num2) * 57.29578f;
 }

我们替换下代码看下输出结果是否一致,替换代码如下:

css 复制代码
 //求向量a与向量b的夹角,输出值为45°
 Vector3 a = new Vector3(10, 10, 0);
 Vector3 b = new Vector3(10, 0, 0);
 Debug.Log($"向量a与向量b的夹角为{Vector3.Angle(a, b)}");

输出结果仍然为45°,所以要求两向量之间的夹角的之后直接用Angle()函数,当需要求余弦值时选用Dot()方法。

1.8向量的投影

给定两个向量vn ,能将v 分解成两个分量:v1v2 。它分别平行于和垂直于n ,并满足v=v1+v2 。一般称平行分量v1vn上的投影。

向量的投影用静态函数Project()表示。基于向量的计算公式实现代码如下:

scss 复制代码
 // 摘要:
 //     Projects a vector onto another vector.
 // 参数:
 //   vector:
 //   onNormal:
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static Vector3 Project(Vector3 vector, Vector3 onNormal)
 {
      float num = Dot(onNormal, onNormal);
      if (num < Mathf.Epsilon)
      {
          return zero;
      }

      float num2 = Dot(vector, onNormal);
      return new Vector3(onNormal.x * num2 / num, onNormal.y * num2 / num, onNormal.z * num2 / num);
 }

这里与前面向量的投影计算公式表现不一致,说明一下,向量的计算公式为:

但是上述实现代码貌似与上述表达式不一致,其实表达的是一个意思,我们将上面代码实现的公式写一下,如下:

那么问题来了,为什么n·n=||n||^2呢?上面代码中看到在计算向量vectoronNormal 上的投影向量是做了一个判断,如果onNormal的点积为0,则返回一个零向量,我们知道一个向量在零向量上投影向量肯定为零。若onNormal 向量不为零,那么它本身的点积就等于||onNormal|| ||onNormal|| cosx,它自身向量的夹角为0,正弦值为1,所以n·n =||n||^2。

举个简单例子:

ini 复制代码
 //计算向量a在向量b上的投影向量,结果应为(10,0,0)
 Vector3 a = new Vector3(10, 10, 0);
 Vector3 b = new Vector3(50, 0, 0);
 var projectVector = Vector3.Project(a, b);
 Debug.Log($"向量a在向量b上的投影向量为({projectVector.x},{projectVector.y},{projectVector.z})");

输出结果为:

1.9向量的叉乘

向量的投影用静态函数Cross()表示。基于向量的计算公式实现代码如下:

csharp 复制代码
 // 摘要:
 //     Cross Product of two vectors.
 // 参数:
 //   lhs:
 //   rhs:
 [MethodImpl(MethodImplOptions.AggressiveInlining)]
 public static Vector3 Cross(Vector3 lhs, Vector3 rhs)
 {
      return new Vector3(lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x);
 }

向量的叉乘在3D主要运用于创建垂直于两个向量所在的平面的向量 。当然我们也知道通过叉乘得到的向量的模长它可以求出两个向量组成的平行四边的面积。

举个简单例子:

ini 复制代码
 //构建一个底边长为20,高为10的平行四边形的两个向量a,b,面积为200
 Vector3 a = new Vector3(10, 10, 0);
 Vector3 b = new Vector3(20, 0, 0);
 
 //求叉乘的向量
 var crossVector = Vector3.Cross(a, b);
 
 //叉乘向量的模长等于所构建的平行四边形的面积
 var area = crossVector.magnitude;
 Debug.Log($"向量a与向量b所构建的平行四边形的面积为{area}");

输出结果:

2、结语

通过两篇文章,我们已经对向量以及Unity中向量类Vector3有了较为全面的理解,Vector3中向量的计算都是基于向量的基本运算及相关的运算法则的。理解向量的数学意义及几何意义,在Unity中,我们更加关注的是几何意义,更加直观的帮助我们理解向量的运用。

向量的高级运用就涉及到了向量的旋转,向量的旋转在Unity中有欧拉角、四元数等,特别是四元数,它的数学原理更为复杂。对于Unity坐标系中线与线的相交性、线与圆的相交性等后续有机会在探讨。

相关推荐
新手unity自用笔记10 小时前
项目-坦克大战学习-子弹的移动与销毁
笔记·学习·c#
qinzechen11 小时前
分享几个做题网站------学习网------工具网;
java·c语言·c++·python·c#
yufei-coder15 小时前
C# Windows 窗体开发基础
vscode·microsoft·c#·visual studio
dangoxiba15 小时前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十三集:制作小骑士的接触地刺复活机制以及完善地图的可交互对象
游戏·unity·visualstudio·c#·游戏引擎
AitTech15 小时前
深入理解C#中的TimeSpan结构体:创建、访问、计算与格式化
开发语言·数据库·c#
hiyo58519 小时前
C#中虚函数和抽象函数的概念
开发语言·c#
开心工作室_kaic21 小时前
基于微信小程序的校园失物招领系统的设计与实现(论文+源码)_kaic
c语言·javascript·数据库·vue.js·c#·旅游·actionscript
时光追逐者1 天前
WaterCloud:一套基于.NET 8.0 + LayUI的快速开发框架,完全开源免费!
前端·microsoft·开源·c#·.net·layui·.netcore
friklogff1 天前
【C#生态园】打造现代化跨平台应用:深度解析.NET桌面应用工具
开发语言·c#·.net
hiyo5852 天前
C#的面向对象
开发语言·c#