首页 > 解决方案 > 如何在 Unity3D 中计算两帧(轴向系统)之间的(正确)变换矩阵

问题描述

对于 Unity3D 中的一个项目,我正在尝试通过更改帧来转换世界上的所有对象。这意味着新框架的原点被旋转、平移和缩放以匹配旧框架的原点,然后此操作应用于所有其他对象(包括旧原点)。为此,我需要一个通用的 3 维(因此是 4x4)变换矩阵。

我看过使用 Unity 的内置 Matrix4x4.TRS() 方法,但这似乎没用,因为它只将平移、旋转和缩放应用于定义的点。我正在寻找的是框架的变化,其中新框架相对于原始框架具有不同的原点、旋转和比例。

为了可视化这个问题,我制作了一个小 GIF(我目前有一个 3D 工作版本,没有使用矩阵,也没有任何旋转): https ://gyazo.com/8a7ab04dfef2c96f53015084eefbdb01

每个球体的值:

Origin1 (Red Sphere)

Before:   
Position (-10, 0, 0)
Rotation (0,0,0)
Scale    (4,4,4)   

After:
Position (10, 0, 0)
Rotation (0,0,0)
Scale    (8,8,8)   

-
Origin2 (Blue Sphere)

Before:   
Position (-20, 0, 0)
Rotation (0,0,0)
Scale    (2,2,2)   

After:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale    (4,4,4)   

-
World-Object (White Sphere)

Before:   
Position (0, 0, 10)
Rotation (0,0,0)
Scale    (2,2,2)   

After:
Position (30, 0, 20)
Rotation (0,0,0)
Scale    (4,4,4)  

目前,我只是在两个原点之间获取向量,将其缩放到两个原点之间的差异,然后将其应用到原始(第一个)原点的新位置之上。当旋转应用于 2 个原点中的任何一个时,这当然不起作用。

// Position in original axes
Vector3 positionBefore = testPosition.TestPosition - origin.TestPosition;
// Position in new axes
Vector3 positionAfter = (positionBefore * scaleFactor) + origin.transform.position;

我正在寻找的是一个可以做到这一点的矩阵(并包括旋转,使得 Origin2 旋转到 Origin1 在转换之前的旋转,并且所有其他对象都移动到它们的正确位置)。

有没有办法在不对每个向量进行完整计算的情况下做到这一点(即转换向量前的位置)?它需要每帧应用于(非常)大量对象,因此需要(相当)优化。

编辑:缩放总是统一的。

标签: c#unity3dmatrixtransform

解决方案


可能还有其他解决方案,但这是我会做的

  1. 将您的对象包装到以下层次结构中

    WorldAnchorObject
    |- Red Sphere
    |- Blue Sphere
    |- White Sphere
    
  2. 确保WorldAnchorObject

    position: 0,0,0
    rotation: 0,0,0
    localScale: 1,1,1
    
  3. 定位/旋转/缩放球体(现在将相对于WorldAnchorObject

  4. 现在剩下的就是变换WorldAnchorObject-> 它将移动、缩放和旋转其他任何东西,并保持相对变换不变。

    你如何移动世界锚是你的事。我猜您想始终将某个子对象居中并规范化。也许像

    public void CenterOnObject(GameObject targetCenter)
    {
        var targetTransform = targetCenter.transform;
        // The localPosition and localRotation are relative to the parent
        var targetPos = transform.localPosition;
        var targetRot = targetTransform.localRotation;
        var targetScale = targetTransform.localScale;
    
        // First reset everything
        transform.position = Vector3.zero;
        transform.rotation = Quaternion.Idendity;
        transform.localScale = Vector3.one;
    
        // set yourself to inverted target position
        // After this action the target should be on 0,0,0
        transform.position = targetPos * -1;
        // Scale yourself relative to 0,0,0
        var newScale = Vector3.one * 1/targetScale.x;
        ScaleAround(gameObject, Vector3.zero, newScale);
        // Now everything should be scaled so that the target 
        // has scale 1,1,1 and still is on position 0,0,0
    
        // Now rotate around 0,0,0 so that the rotation of the target gets normalized
        transform.rotation = Quaternion.Inverse(targetRotation);
    }
    
    // This scales an object around a certain pivot and 
    // takes care of the correct position translation as well
    private void ScaleAround(GameObject target, Vector3 pivot, Vector3 newScale)
    {
        Vector3 A = target.transform.localPosition;
        Vector3 B = pivot;
    
        Vector3 C = A - B; // diff from object pivot to desired pivot/origin
    
        float RS = newScale.x / target.transform.localScale.x; // relative scale factor
    
        // calc final position post-scale
        Vector3 FP = B + C * RS;
    
        // finally, actually perform the scale/translation
        target.transform.localScale = newScale;
        target.transform.localPosition = FP;
    }
    

    现在你称它为传递其中一个孩子,例如

    worldAnchorReference.CenterOnObject(RedSphere);
    

    应该会导致您想要实现的目标。(在我的智能手机上破解这个,所以没有保证,但如果有问题,我可以在我再次使用 PC 时立即检查它。;))


推荐阅读