• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

向量和Unity向量类Vcetor3的使用二

武飞扬头像
暴走的萝莉大叔
帮助1

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

1、Vector3向量类

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

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

public struct Vector3 : IEquatable<Vector3>, IFormattable
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中的公开的方法中都有。

//
// 摘要:
//     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三维坐标系空间中的某两个位置。

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向量类中的向量的模长计算

学新通

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

// 摘要:
//     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。

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

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

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

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

学新通

Vector3中实现代码如下:

//
// 摘要:
//     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类中代码实现如下:

[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中实现如下:

[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轴上的向量进行相加或相减。这里提一下负向量,它表示的是与原向量大小相等,方向相反的向量,实现如下:

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

1.5两个向量的相等性比较

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

 // 摘要:
 //     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中实现代码如下,很显然是一致的。

 // 摘要:
 //     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);
 }

举个例子:

 //求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()表示。基于向量的计算公式实现代码如下:

 //
 // 摘要:
 //     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;
 }

学新通

举个简单例子:

 //求向量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()可以一步到位求两个向量之间的角度值,代码如下:

 // 摘要:
 //     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;
 }

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

 //求向量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()表示。基于向量的计算公式实现代码如下:

 // 摘要:
 //     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。

举个简单例子:

 //计算向量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()表示。基于向量的计算公式实现代码如下:

 // 摘要:
 //     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主要运用于创建垂直于两个向量所在的平面的向量。当然我们也知道通过叉乘得到的向量的模长它可以求出两个向量组成的平行四边的面积。
举个简单例子:

 //构建一个底边长为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坐标系中线与线的相交性、线与圆的相交性等后续有机会在探讨。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgeeaga
系列文章
更多 icon
同类精品
更多 icon
继续加载