【问题标题】:Computationally quickly project 3d object onto 2d plane and get surface area of that (for aerodynamic drag)?计算快速地将 3d 对象投影到 2d 平面上并获得其表面积(用于空气动力学阻力)?
【发布时间】:2018-04-18 07:32:46
【问题描述】:

我试图在 Unity 中模拟游泳(使用 c#),实际上是让物体的运动产生阻力,然后推动物体穿过液体。

为此,我使用公式

F = -½ * C * d * 速度平方 * A

其中 C 是阻力系数,d 是液体的密度,A 是物体面向运动方向的表面积。 A 是通过将 3D 对象投影到垂直于速度矢量的 2D 平面上来计算的。

这是一张解释 A 的图片: https://www.real-world-physics-problems.com/images/drag_force_2.png

现在我怀疑 Unity 有一种内置方式来进行这种类型的投影(因为每次场景中有摄像机时它都会这样做)。

我的问题是:

  1. 我该怎么做?搜索对我没有帮助(除非您尝试使用相机)
  2. Unity 中有内置函数吗?

  3. 这计算成本高吗?我将一次为可能数千个对象执行此操作。

我不需要它非常准确。我只是想让它有点逼真,所以我希望 A 大得多的对象比 A 小得多的对象具有更大的阻力. 细微的差别是无关紧要的。对象本身不会非常复杂,但有些对象可能具有非常不同的区域,具体取决于方向。例如,就像一个圆锥体,它可能会根据它的移动方向而发生相当大的变化。如果需要,我可以用一个简单的形状来近似 A,比如椭圆形或矩形。

如果计算量很大,我阅读了一篇期刊文章,该文章使用了一种很酷的方法来近似它。他在均匀分布的物体内创建了一个点网格(他称之为体素),这有效地将物体分成大小相等的球体(它们的横截面面积总是一个圆(易于计算)。然后他计算了每个球体上的阻力并将它们相加得出总阻力(见图)。

论文报告中的图像:基于物理的实时动画 人形游泳者,Jurgis Pamerneckas,2014

链接https://dspace.library.uu.nl/bitstream/handle/1874/298577/JP-PhysBAnimHumanSwim.pdf?sequence=2

这为他成功估算了阻力。但我看到了一个问题,即物体深处的“体素”仍然会产生阻力,而只有靠近前沿的“体素”才会产生阻力。

所以,我想到了一种可能性,我可以将体素点投影到 2D 平面上(垂直于速度),然后找到边界形状或其他东西,并以这种方式进行近似。我怀疑投影几个点会比投影整个 3d 对象更快。

这又引发了几个问题:

  1. 这似乎是一种更好的方法?
  2. 如何在 Unity 中创建体素?
  3. 计算速度更快吗?
  4. 有更好的想法吗?

我的另一个想法是进行某种光线投射,虽然我不知道该怎么做,也许是平行于速度矢量的光线投射网格?并且只计算大约区域的命中数?

更新

我设法通过手动输入 A 的值来实现基本的阻力,现在我需要以某种方式近似 A。即使是手动输入,对于非常基础的“游泳者”来说,它的效果也出奇的好。在下图中,游泳者正确地向右旋转,因为他的左臂更大(我给它的值是 A 的两倍)。

更新 2

基于@Pierre 的 cmets,我尝试使用对象的顶点(以及通过选择顶点上的几个点)计算整体形状的 A,将它们投影到平面上,并计算所得多边形的总面积。但是,这仅计算了对象上的整体阻力。它没有计算由于物体的某些部分比其他部分移动得更快而引起的任何旋转阻力。例如,想想棒球棒的挥动,球棒最远的部分会产生更大的阻力,因为它的挥动速度比手柄快。

这让我回到了“体素”的想法,因为我可以计算在对象的几个部分采样的局部拖动。

我正在玩弄这个想法,用一个圆圈来估计体素的表面积。但是仍然有一些问题使这个估计相对准确。尽管它不准确,但这似乎工作得很好。

首先,我使用重铸来确定体素是否可以在速度方向上“看到”以确定它是否位于对象的前导面上。如果是这样,那么我取体素的局部(圆形)表面积,并将其乘以圆的法线和局部速度矢量的点积。这会根据实际面向运动方向的程度来缩放区域。

到目前为止的不准确是由于圆圈实际上没有很好地估计局部表面积,尤其是对于奇怪的拉长物体。进一步的顶点彼此相距越远,则估计变得越差。对此部门的任何帮助将不胜感激。

另外,我需要在计算上对此进行优化。现在,对每个顶点都这样做被证明是相当昂贵的。随着我的进步,我会不断更新,任何输入都会非常有帮助!一旦我走得更远,我会尽快发布一些代码。

更新 3

我使用手动放置在对象表面上的体素进行了相当准确的实现,并在面对该体素时手动估计了局部 A。然后我使用点积来估计该区域有多少面向运动方向。这工作得很好。但随之而来的问题是,即使不在物体前缘的体素也会造成阻力。所以我使用 Physics.Raycasts 在速度方向上从体素弹出一小段距离,然后将光线投射回体素。如果此光线投射击中实际对象(而不是体素)的对撞机,则意味着它位于前沿。这非常有效,并且产生了令人惊讶的准确自然外观的阻力行为。奇怪形状的物体最终会像您期望的那样旋转以最小化阻力。然而,当我提高体素的分辨率和/或在场景中添加更多对象时,我的帧速率下降到接近 3fps。剖析器显示,计算的首当其冲是由于光线投射步骤。我试图想其他方法来确定体素是否处于领先地位,但到目前为止无济于事。

所以 TLDR,我很好地模拟了阻力,但不是以计算速度快的方式。

【问题讨论】:

  • 您肯定需要对数千个对象进行某种近似。每个对象(例如椭圆体)具有一个简单形状的近似值是否足够准确?否则,您可以将体素投影到平面上并对其进行光栅化(即将体素覆盖的 2D 坐标插入地图或类似结构中)。最后,计算地图有多少条目。
  • 是的,像椭球这样简单的形状就可以了。我认为圈子有点太简单了。我也可以用矩形来完成
  • 还只是补充一点,对象本身不会非常复杂,但根据方向的不同,某些区域可能会有很大不同。例如,就像一个圆锥体,A 可能会根据它移动的方向而发生相当大的变化。也将此添加到问题中。
  • 如果您可以访问形状的顶点,您可以尝试将所有顶点投影到平面上,然后获取投影的凸包吗?这可以在 O(n log n) 中完成。我应该非常适合立方体、椭圆体、圆锥体、圆柱体等简单形状。但是,如果您的全局形状是几个简单形状的联合体,则要小心,因为凸包可能比真实投影更大。
  • 谢谢 Pierre,我会试试看效果如何。

标签: c# unity3d projection


【解决方案1】:

我从来没有想出加快计算速度的方法,但只要体素数量少,模拟效果就很好。

模拟根据每个体素的速度计算阻力。它检查它是否在对象的前缘,如果是,则应用它的阻力。

代码可能有点难以理解,但如果您想尝试一下,至少应该可以帮助您入门。如果您有任何问题或需要澄清,请告诉我。

此代码是我上面的 Update#3 中稍微清理过的版本。

在行动: 模拟开始时(物体沿直线向屏幕右下方移动)

您可以看到为可视化添加的力箭头和代表体素的圆圈。该力与体素大致代表的表面积成正比。并且只有形状的前缘会产生阻力

随着模拟的继续,由于阻力,形状正确地旋转到最符合空气动力学的位置,并且后部不再产生阻力。


启用拖动的形状类

这是在主对象(刚体)上拖动以启用拖动。您可以让它围绕球形创建体素。或者加载您自己的自定义体素,这些体素是附加了体素脚本的游戏对象,并且是该对象的子对象。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

[RequireComponent (typeof(Rigidbody))]
public class DragEnabledShape : MonoBehaviour {

    const float EPSILON = 0.0001f;

    public Voxel voxelPrefab;
    public float C = 1f;
    public float d = 0.5f;
    public int resolutionFactor = 2;
    public float maxDistanceFromCenter = 10f;
    public bool displayDragVisualization = false;
    public float forceVisualizationMultiplier = 1f;
    public bool displayVoxels = false;
    public bool loadCustomVoxels = false;

    List<Voxel> voxels;
    Rigidbody rb;


    // Use this for initialization
    void Awake () {
        voxels = new List<Voxel> ();
        rb = GetComponent<Rigidbody> ();
    }

    void OnEnable () {

        if (loadCustomVoxels) {
            var customVoxels = GetComponentsInChildren<Voxel> ();
            voxels.AddRange (customVoxels);
            if (displayDragVisualization) {
                foreach (Voxel voxel in customVoxels) {
                    voxel.DisplayDrag (forceVisualizationMultiplier);
                }
            }
            if (displayVoxels) {
                foreach (Voxel voxel in customVoxels) {
                    voxel.Display ();
                }
            }
        }
        else {
            foreach (Transform child in GetComponentsInChildren<Transform> ()) {
                if (child.GetComponent<Collider> ()) {
                    //print ("creating voxels of " + child.gameObject.name);
                    CreateSurfaceVoxels (child);
                }
            }
        }
    }

    void CreateSurfaceVoxels (Transform body) {
        List<Vector3> directionList = new List<Vector3> ();
        for (float i = -1; i <= 1 + EPSILON; i += 2f / resolutionFactor) {
            for (float j = -1; j <= 1 + EPSILON; j += 2f / resolutionFactor) {
                for (float k = -1; k <= 1 + EPSILON; k += 2f / resolutionFactor) {
                    Vector3 v = new Vector3 (i, j, k);
                    directionList.Add (v);
                }
            }
        }
        //float runningTotalVoxelArea = 0;
        foreach (Vector3 direction in directionList) {
            Ray upRay = new Ray (body.position, direction).Reverse (maxDistanceFromCenter);
            RaycastHit[] hits = Physics.RaycastAll (upRay, maxDistanceFromCenter);
            if (hits.Length > 0) {
                //print ("Aiming for " + body.gameObject.name + "and hit count: " + hits.Length); 
                foreach (RaycastHit hit in hits) {

                    if (hit.collider == body.GetComponent<Collider> ()) {
                        //if (GetComponentsInParent<Transform> ().Contains (hit.transform)) {
                        //print ("hit " + body.gameObject.name);  
                        GameObject empty = new GameObject ();
                        empty.name = "Voxels";
                        empty.transform.parent = body;
                        empty.transform.localPosition = Vector3.zero;
                        GameObject newVoxelObject = Instantiate (voxelPrefab.gameObject, empty.transform);
                        Voxel newVoxel = newVoxelObject.GetComponent<Voxel> ();
                        voxels.Add (newVoxel);
                        newVoxel.transform.position = hit.point;
                        newVoxel.transform.rotation = Quaternion.LookRotation (hit.normal);
                        newVoxel.DetermineTotalSurfaceArea (hit.distance - maxDistanceFromCenter, resolutionFactor);
                        newVoxel.attachedToCollider = body.GetComponent<Collider> ();
                        if (displayDragVisualization) {
                            newVoxel.DisplayDrag (forceVisualizationMultiplier);
                        }
                        if (displayVoxels) {
                            newVoxel.Display ();
                        }
                        //runningTotalVoxelArea += vox.TotalSurfaceArea;
                        //newVoxel.GetComponent<FixedJoint> ().connectedBody = shape.GetComponent<Rigidbody> ();
                    }
                    else {
                        //print ("missed " + body.gameObject.name + "but hit " + hit.transform.gameObject.name); 
                    }
                }


            }

        }

    }

    void FixedUpdate () {
        foreach (Voxel voxel in voxels) {
            rb.AddForceAtPosition (voxel.GetDrag (), voxel.transform.position);
        }
    }



}

体素类

此脚本附加到放置在形状周围的小型游戏对象上。它们表示计算阻力的位置。所以对于复杂的形状,它们应该在任何末端,并且应该在物体上相当分散。体素对象的刚体质量应该近似于该体素所代表的对象部分。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Voxel : MonoBehaviour {

    Vector3 velocity;


    public Collider attachedToCollider;

    Vector3 drag;

    public Vector3 Drag {
        get {
            return drag;
        }
    }

    float dragMagnitude;

    public float DragMagnitude {
        get {
            return dragMagnitude;
        }
    }

    bool leadingEdge;

    public bool LeadingEdge {
        get {
            return leadingEdge;
        }
    }

    bool firstUpdate = true;
    public float localSurfaceArea;

    Vector3 prevPos;
    public VoxelForceVisualizer forceVisualizer;
    public VoxelVisualizer voxelVisualizer;

    const float AREA_COEFFICIENT = 1.1f;
    const float EPSILON = 0.001f;
    const float FAR_DISTANCE = 5f;
    const float MAX_FORCE = 100f;



    public void DetermineTotalSurfaceArea (float distanceFromCenter, float resolution) {
        float theta = (Mathf.PI / 4) / resolution;
        float localR = distanceFromCenter * Mathf.Tan (theta) * AREA_COEFFICIENT;// * (resolution / 0.01f);
        localSurfaceArea = Mathf.PI * localR * localR;
    }


    bool IsVisibleFromPlane () {
        if (attachedToCollider == null) {
            throw new MissingReferenceException ("attached to collider not set");
        }
        bool visibleFromPlane = false;

        //checks if this is leading edge of this part of object.
        Ray justOutsideSurface = new Ray (this.transform.position, velocity).Reverse (EPSILON);
        RaycastHit hit;
        if (Physics.Raycast (justOutsideSurface, out hit, EPSILON * 2f)) {
            if (hit.collider == attachedToCollider) {

                //checks if other parts of this object are in front, blocking airflow.
                //Ray wayOutsideSurface = new Ray (this.transform.position, velocity).Reverse (FAR_DISTANCE);
                //RaycastHit firstHit;
                //if (Physics.Raycast (wayOutsideSurface, out firstHit, FAR_DISTANCE * 2f)) {
                //if (firstHit.collider == attachedToCollider) {
                visibleFromPlane = true;
                //}
                //}

            }
        }

        //}
        leadingEdge = visibleFromPlane;
        return visibleFromPlane;
    }

    void FixedUpdate () {
        if (firstUpdate) {
            prevPos = transform.position;
            firstUpdate = false;
        }

        velocity = (transform.position - prevPos) / Time.deltaTime;
        prevPos = transform.position;
    }

    public Vector3 GetDrag () {
        if (IsVisibleFromPlane ()) {
            float alignment = Vector3.Dot (velocity, this.transform.forward);
            float A = alignment * localSurfaceArea;
            dragMagnitude = DragForce.Calculate (velocity.sqrMagnitude, A);

            //This clamp is necessary for imperfections in velocity calculation, especially with joint limits!
            //dragMagnitude = Mathf.Clamp (dragMagnitude, 0f, MAX_FORCE);

            drag = -velocity * dragMagnitude;
        }
        return drag;
    }

    public void Display () {
        voxelVisualizer.gameObject.SetActive (true);
    }

    public void TurnOffDisplay () {
        voxelVisualizer.gameObject.SetActive (false);
    }

    public void DisplayDrag (float forceMultiplier) {
        forceVisualizer.gameObject.SetActive (true);
        forceVisualizer.multiplier = forceMultiplier;
    }

    public void TurnOffDragDisplay () {
        forceVisualizer.gameObject.SetActive (false);
    }


}

体素力可视化器

这是一个附加到细箭头的预制件,我将它作为体素的子级放置,以允许在调试阻力时绘制力箭头。

using UnityEngine;

public class VoxelForceVisualizer : MonoBehaviour {

    const float TINY_NUMBER = 0.00000001f;

    public Voxel voxel;
    public float drag;
    public float multiplier;

    void Start () {
        voxel = this.GetComponentInParent<Voxel> ();
    }
    // Update is called once per frame
    void Update () {
        Vector3 rescale;
        if (voxel.LeadingEdge && voxel.Drag != Vector3.zero) {
            this.transform.rotation = Quaternion.LookRotation (voxel.Drag);
            rescale = new Vector3 (1f, 1f, voxel.DragMagnitude * multiplier);

        }
        else {
            rescale = Vector3.zero;
        }
        this.transform.localScale = rescale;
        drag = voxel.DragMagnitude;
    }

}

体素可视化器

这作为体素空的子元素附加到一个小球体对象。只是看看体素在哪里,让上面的脚本显示/隐藏体素而不禁用阻力计算。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VoxelVisualizer : MonoBehaviour {


}

拖拽力

这会计算阻力

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class DragForce {

    const float EPSILON = 0.000001f;


    public static float Calculate (float coefficient, float density, float vsq, float A) {
        float f = coefficient * density * vsq * A;
        return f;
    }

    public static float Calculate (float vsq, float A) {
        return Calculate (1f, 1f, vsq, A);
    }


}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-06
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2017-10-04
    • 2014-05-06
    • 1970-01-01
    相关资源
    最近更新 更多