LOADING

加载过慢请开启缓存 浏览器默认开启

Unity 向量计算专题(2):向量技巧浅谈


一、角度插值(Angle Interpolation)

1.1 核心问题

在游戏中,物体从当前方向快速转向目标方向时,若直接使用线性插值会出现速度不均匀,且无法正确处理超过 180° 的反向旋转。我们需要沿最短旋转弧线、以恒定角速度或加速度完成平滑过渡。

1.2 数学原理

$$\theta = \arccos\Bigl(\frac{\mathbf{u}\cdot\mathbf{v}}{|\mathbf{u}|\ |\mathbf{v}|}\Bigr), \quad\text{旋转轴 } \mathbf{a} = \frac{\mathbf{u}\times\mathbf{v}}{|\mathbf{u}\times\mathbf{v}|},\quad\Delta\theta = \omega\ \Delta t$$

  • 构造步进四元数:

    $$q_{\Delta} = \bigl(\cos\frac{\Delta\theta}{2},\ \mathbf{a}\ \sin\frac{\Delta\theta}{2}\bigr)$$

  • 更新方向:

    $$\mathbf{u}' = q_{\Delta}\ \mathbf{u}\ q_{\Delta}^{-1}$$

1.3 Unity 内置方法

float maxDeg = turnSpeed * Time.deltaTime;
Vector3 newDir = Vector3.RotateTowards(
    currentDir, targetDir,
    maxRadiansDelta: maxDeg * Mathf.Deg2Rad,
    maxMagnitudeDelta: 0f
);
transform.rotation = Quaternion.LookRotation(newDir);

1.4 手写伪代码

// fromDir, toDir 已归一化;turnSpeed 单位:deg/s
float dot = Mathf.Clamp(Vector3.Dot(fromDir, toDir), -1f, 1f);
float theta = Mathf.Acos(dot);                     // [0, π]
float maxDelta = turnSpeed * Time.deltaTime * Mathf.Deg2Rad;
float t = Mathf.Min(1f, maxDelta / theta);

Vector3 axis = Vector3.Cross(fromDir, toDir).normalized;
float half = theta * t * 0.5f;
float sinH = Mathf.Sin(half);
Quaternion qStep = new Quaternion(
    axis.x * sinH, axis.y * sinH, axis.z * sinH,
    Mathf.Cos(half)
);

Vector3 resultDir = (qStep * new Vector4(fromDir, 0)).normalized;

二、方向曲线(Direction Curves)

2.1 场景需求

抛物线、贝塞尔轨迹、自然摆动效果等,都需要在 3D 空间内让方向或位置沿曲线平滑运动。

2.2 二次/三次贝塞尔曲线

$$B_2(t) = (1-t)^2P_0 + 2t(1-t)P_1 + t^2P_2,\quad t\in[0,1]$$

$$B_3(t) = (1-t)^3P_0 + 3t(1-t)^2P_1 + 3t^2(1-t)P_2 + t^3P_3$$

2.3 三次贝塞尔伪代码

Vector3 Bezier3(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
    float u = 1 - t;
    float tt = t * t;
    float uu = u * u;
    float uuu = uu * u;
    float ttt = tt * t;
    Vector3 point = uuu * p0;            // (1-t)^3 * P0
    point += 3 * uu * t * p1;            // 3(1-t)^2 * t * P1
    point += 3 * u * tt * p2;            // 3(1-t) * t^2 * P2
    point += ttt * p3;                   // t^3 * P3
    return point;
}

// 用法示例
void Update() {
    float t = Mathf.Clamp01((Time.time - startTime) / duration);
    transform.position = Bezier3(A, B, C, D, t);
}

2.4 曲线方向(切线)计算

$$B_3'(t)= 3(1-t)^2(P_1 - P_0)\ +\ 6(1-t)t(P_2 - P_1)\ +\ 3t^2(P_3 - P_2)$$

归一化后即可作为方向向量:

Vector3 tangent = Bezier3Tangent(A, B, C, D, t).normalized;

三、最短路径(球面插值 Slerp)

3.1 问题描述

在单位球面上插值时,需保持向量长度不变,沿大圆(Great Circle)轨迹运动。

3.2 SLERP 公式

$$\mathrm{Slerp}(u,v,t)=\frac{\sin\bigl((1 - t)\ \theta\bigr)}{\sin\theta}\ u\ +\ \frac{\sin\bigl(t,\theta\bigr)}{\sin\theta}\ v,\quad\theta = \arccos\bigl(u \cdot v\bigr)$$

3.3 Unity 接口

Vector3 result = Vector3.Slerp(fromDir, toDir, t);

3.4 手写伪代码

Vector3 Slerp(Vector3 u, Vector3 v, float t) {
    float dot = Mathf.Clamp(Vector3.Dot(u, v), -1f, 1f);
    float theta = Mathf.Acos(dot);
    float sinTheta = Mathf.Sin(theta);
    if (sinTheta < 1e-5f)
        return Vector3.Lerp(u, v, t); // 退化到 Lerp
    float a = Mathf.Sin((1 - t) * theta) / sinTheta;
    float b = Mathf.Sin(t * theta) / sinTheta;
    return a * u + b * v;
}

四、自定义向量结构(Custom Vec3)

4.1 动机

在 ECS 或顶点重构等场景,对 Vector3 的封装开销敏感时,可自定义轻量结构并内联计算。

4.2 结构定义

public struct Vec3 {
    public float x, y, z;
    public Vec3(float x, float y, float z) {
        this.x = x; this.y = y; this.z = z;
    }

    // 运算符重载
    public static Vec3 operator +(Vec3 a, Vec3 b)
        => new Vec3(a.x + b.x, a.y + b.y, a.z + b.z);
    public static Vec3 operator -(Vec3 a, Vec3 b)
        => new Vec3(a.x - b.x, a.y - b.y, a.z - b.z);
    public static Vec3 operator *(Vec3 v, float s)
        => new Vec3(v.x * s, v.y * s, v.z * s);
    public static Vec3 operator /(Vec3 v, float s)
        => new Vec3(v.x / s, v.y / s, v.z / s);

    public float SqrMagnitude() => x*x + y*y + z*z;
    public float Magnitude()    => Mathf.Sqrt(SqrMagnitude());
    public Vec3  Normalized()   {
        float inv = 1f / Magnitude();
        return this * inv;
    }
}

4.3 性能对比

操作 Unity Vector3 自定义 Vec3
构造 方法调用 直接赋值
normalized 多次函数/属性访问 单次 Sqrt 与算术运算
数组存取 额外封装 结构对齐更易 SIMD 优化

Tip:在 Burst/ECS 环境下,自定义 Vec3 结合 float3 能拿到最高性能。


五、总结

  1. 角度插值:Quaternion 或 RotateTowards 可保持旋转弧线最短、速度可控。
  2. 方向曲线:贝塞尔曲线生成自然轨迹,切线公式可驱动方向。
  3. 最短路径:SLERP 保持向量长度恒定,沿大圆运动。
  4. 自定义向量:轻量结构、内联计算,适用于极限性能场景。