简体   繁体   English

以计算方式将 3d 对象快速投影到 2d 平面上并获得其表面积(用于气动阻力)?

[英]Computationally quickly project 3d object onto 2d plane and get surface area of that (for aerodynamic drag)?

I'm trying to simulate swimming in Unity (using c#) by actually having the movements of the object create drag forces which then propel the object through the liquid.我试图通过实际让物体的运动产生阻力然后推动物体穿过液体来模拟在 Unity 中游泳(使用 c#)。

to do this, I'm using the formula为此,我正在使用公式

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

where C is a coefficient of drag, d is the density of liquid, and A is the object's surface area that faces the direction of motion.其中 C 是阻力系数,d 是液体的密度,A 是物体面向运动方向的表面积。 A is calculated by projecting the 3D object onto a 2D plane perpendicular to the velocity vector. A 是通过将 3D 对象投影到垂直于速度矢量的 2D 平面上来计算的。

Here's an image explaining A: https://www.real-world-physics-problems.com/images/drag_force_2.png这是解释 A 的图像: https : //www.real-world-physics-problems.com/images/drag_force_2.png

Now I suspect Unity has a built in way to do this type of projection (since it does that every time there's a camera in the scene).现在我怀疑 Unity 有一种内置的方式来进行这种类型的投影(因为每次场景中有摄像机时它都会这样做)。

My question is:我的问题是:

  1. How do I do this?我该怎么做呢? Searches have not helped me with this (unless you're trying to do it with a camera)搜索并没有帮助我解决这个问题(除非你想用相机来做)
  2. Is there a built in function in Unity? Unity 中是否有内置函数?

  3. Is this computationally expensive?这在计算上是昂贵的吗? I am going to be doing this individual for possibly thousands of objects at a time.我将一次为可能数以千计的对象做这个人。

I DO NOT need it to be very accurate.我不需要它非常准确。 I'm just trying to make it a bit realistic, so I want objects with much bigger A to have more drag than ones with much lower A. Slight differences are inconsequential.我只是想让它有点现实,所以我希望 A 大得多的物体比 A 小得多的物体具有更大的阻力。细微的差异是无关紧要的。 The objects themselves won't be super complex, but some may have very different areas depending on orientation.对象本身不会非常复杂,但根据方向的不同,有些对象可能具有非常不同的区域。 So like a cone, for example, could change quite a bit depending on which direction it's moving.例如,就像一个圆锥体,根据它移动的方向,它可能会发生很大的变化。 I could approximate the A with a simple shape if needed like ellipsoid or rectangle.如果需要,我可以用一个简单的形状来近似 A,比如椭圆体或矩形。

If it is computationally expensive, I read a journal article that used a cool way to approximate it.如果它的计算成本很高,我会阅读一篇期刊文章,其中使用了一种很酷的方法来近似它。 He created a grid of points (which he called voxels) within the objects spaced evenly, which effectively split the object into equal-sized spheres (which always have a cross-sectional surface area of a circle (easy to calculate). Then he calculated the drag force on each of these spheres and added them up to find the total drag (see images).他在均匀分布的物体内创建了一个点网格(他称之为体素),这有效地将物体分成了大小相等的球体(这些球体的横截面面积始终为圆形(易于计算)。然后他计算了每个球体上的阻力并将它们相加以找到总阻力(见图)。

体素 计算阻力

Images from THESIS REPORT ON: Real-time Physics-based Animation of a Humanoid Swimmer, Jurgis Pamerneckas, 2014论文报告中的图像:基于物理的实时人形游泳者动画,Jurgis Pamerneckas,2014

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

This successfully estimated drag for him.这成功地估计了他的阻力。 But I see one problem, that the "voxels" that are deep in object are still contributing to drag, where only the ones near the leading edge should be contributing.但我看到了一个问题,即物体深处的“体素”仍然对阻力有贡献,只有靠近前缘的那些才应该有贡献。

So, I thought of a possibility where I could project just the voxel points onto the 2Dplane (perpendicular to velocity) and then find a bounding shape or something, and approximate it that way.所以,我想到了一种可能性,我可以只将体素点投影到 2D 平面上(垂直于速度),然后找到一个边界形状或其他东西,并以这种方式近似。 I suspect projecting a few points would be faster than projecting a whole 3d object.我怀疑投影几个点会比投影整个 3d 对象更快。

this raises a few more questions:这又引发了几个问题:

  1. Does this seem like a better method?这似乎是一种更好的方法吗?
  2. How would I create voxels in Unity?我将如何在 Unity 中创建体素?
  3. Is it computationally faster?它的计算速度更快吗?
  4. Any better ideas?有什么更好的想法吗?

Another thought I had was to do raycasting of some sort, though I can't think of how to do that, perhaps a grid of raycasts parallel to the velocity vector?我的另一个想法是进行某种光线投射,尽管我想不出如何做到这一点,也许是平行于速度矢量的光线投射网格? and just count how many hit to approximate area?并计算一下近似区域的命中次数?

UPDATE更新

I managed to implement basic drag force by manually typing in the value for A, now I need to approximate A in some way.我设法通过手动输入 A 的值来实现基本的阻力,现在我需要以某种方式近似 A。 Even with manual typing, it works surprisingly well for very basic "swimmers".即使使用手动打字,它对于非常基本的“游泳者”也非常有效。 In the image below, the swimmer correctly spins to the right since his left arm is bigger (I gave it double the value for A).在下图中,游泳者正确地向右旋转,因为他的左臂更大(我给了它两倍的 A 值)。 文火

UPDATE 2更新 2

Based on @Pierre's comments, I tried computing A for the overall shape using the object's vertices (and also by selecting a few points on the vertices), projecting them onto a plane, and calculating the overall area of the resulting polygon.根据@Pierre 的评论,我尝试使用对象的顶点(以及通过选择顶点上的几个点)计算整体形状的 A,将它们投影到平面上,并计算生成的多边形的总面积。 However, This only calculated the overall drag force on the object.然而,这只计算了物体上的整体阻力。 It didn't calculate any rotational drag caused by certain parts of the object moving faster than others.它没有计算由物体的某些部分比其他部分移动得更快引起的任何旋转阻力。 For example, think of a baseball bat swing, the farthest part of the bat will be creating more drag since it's swinging faster than the handle.例如,想想棒球棒的挥杆,球棒最远的部分会产生更大的阻力,因为它比手柄挥动得更快。

This made me go back to the "voxel" idea, since I could calculate local drag sampled at several parts of the object.这让我回到了“体素”的想法,因为我可以计算在对象的几个部分采样的局部阻力。

I'm playing around with this idea, estimating the voxel's surface area by a circle.我正在考虑这个想法,通过一个圆圈来估计体素的表面积。 But still having a few issues making this estimate relatively accurate.但是仍然存在一些问题,使这个估计相对准确。 Despite it being inaccurate, this seems to work quite well.尽管它不准确,但这似乎工作得很好。

First, I'm using recasts to determine if the voxel can "see" in the direction of the velocity to determine if it's on the leading face of the object.首先,我使用重铸来确定体素是否可以在速度方向上“看到”以确定它是否在对象的引导面上。 If so, then I take the voxel's local (circular) surface area, and multiplying this by the dot product of the circle's normal and the local velocity vector.如果是这样,那么我取体素的局部(圆形)表面积,并将其乘以圆的法线和局部速度矢量的点积。 This scales the area based on how much it's actually facing the direction of motion.这会根据实际面向运动方向的程度来缩放区域。

The inaccuracies so far are due to the circles not actually estimating the local surface area very well, especially for weirdly elongated objects.到目前为止的不准确是由于圆圈实际上没有很好地估计局部表面积,特别是对于奇怪的细长物体。 The further vertices are from each other then the worse the estimation becomes.顶点彼此相距越远,估计就越差。 Any help in this department would be appreciated.本部门的任何帮助将不胜感激。

Also, I need to optimize this computationally.另外,我需要在计算上优化它。 Right now, doing it with every vertex is proving to be fairly expensive.现在,事实证明对每个顶点都这样做是相当昂贵的。 I'll keep updating as I progress, and any input would be very helpful!随着我的进步,我会不断更新,任何输入都会非常有帮助! I'll post some code soon once I get a bit farther.一旦我走得更远,我会很快发布一些代码。

UPDATE 3更新 3

I did a fairly accurate implementation using voxels which I manually placed on the surface of the object, and manually estimated the local A when facing that voxel.我使用手动放置在对象表面上的体素做了一个相当准确的实现,并在面对该体素时手动估计了局部 A。 I then used the dot product to estimate how much of that Area was facing the direction of motion.然后我使用点积来估计该区域有多少面向运动方向。 This worked very well.这非常有效。 But the problem then was that even voxels that weren't on the leading edge of the object were contributing to drag.但问题是,即使不在物体前沿的体素也会导致阻力。 So I used Physics.Raycasts to pop a small distance away from the voxel in the direction of velocity, and then raycast back at the voxel.所以我使用 Physics.Raycasts 在速度方向上从体素弹出一小段距离,然后在体素上进行光线投射。 If this raycast hit the collider of the actual object (not the voxel) it meant it was on the leading edge.如果此光线投射击中实际物体(而不是体素)的对撞机,则意味着它位于前缘。 This worked fantastically and yielded surprisingly accurate natural looking behaviour of drag.这非常有效,并产生了令人惊讶的准确自然的阻力行为。 Strangely shaped objects would eventually rotate to minimize drag just like you'd expect.形状奇怪的物体最终会像您期望的那样旋转以最大限度地减少阻力。 However, as soon as I increased the resolution of voxels and/or added a few more objects into the scene, my frame rate dropped to nearly 3fps.然而,一旦我增加了体素的分辨率和/或在场景中添加了更多的对象,我的帧速率就会下降到接近 3fps。 The profiler showed that the brunt of the calculations were due to the raycasting step.分析器显示,计算的首当其冲是由于光线投射步骤。 I've tried to think of other ways to determine if the voxels are on the leading edge, so far to no avail.我试图想出其他方法来确定体素是否处于前沿,但目前无济于事。

So TLDR, I simulated drag really well, but not in a computationally fast manner.所以 TLDR,我很好地模拟了阻力,但不是以快速计算的方式。

I never figured out a way to speed up the calculations, but the simulation works great as long as the voxel count is low.我从来没有想出加速计算的方法,但只要体素数很低,模拟就可以很好地工作。

The simulation calculates drag based on the velocity of each voxel.模拟根据每个体素的速度计算阻力。 It checks whether it's on the leading edge of the object, and if so applies its drag force.它检查它是否在对象的前缘,如果是,则应用其阻力。

The code is probably a bit difficult to follow but should at least get you started if you want to try it out.该代码可能有点难以理解,但如果您想尝试一下,至少应该让您入门。 Let me know if you have any questions or need clarifications.如果您有任何问题或需要澄清,请告诉我。

This code is a slightly cleaned up version from my Update#3 above.这段代码是我上面更新#3 的一个稍微清理过的版本。

In action: At start of simulation (object moving in straight line towards bottom right of screen)实际操作:模拟开始时(对象沿直线向屏幕右下方移动)

拖动开始

you can see the force arrows added for visualization and the circles representing the voxels.您可以看到为可视化添加的力箭头和代表体素的圆圈。 The force is correctly proportional to the surface area the voxels roughly represent.力与体素粗略表示的表面积成正比。 and only leading edges of the shapes are contributing drag并且只有形状的前缘会产生阻力

As the simulation continues, the shape correctly rotates into the most aerodynamic position because of the drag, and the rear sections stop contributing drag.随着模拟的继续,由于阻力,形状正确旋转到最符合空气动力学的位置,后部停止提供阻力。

模拟更远


Drag Enabled Shape Class拖动启用形状类

this is dragged on main objet (rigidbody) to enable drag.这是在主对象(刚体)上拖动以启用拖动。 You can either have it create voxels in a spread around a sphere shape.您可以让它围绕球体形状创建体素。 Or load in your own custom Voxels which are game objects with the Voxel Script attached, and are children of this object.或者加载您自己的自定义体素,这些体素是附加了体素脚本的游戏对象,并且是该对象的子代。

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



}

Voxel class体素类

This script is attached to small gameObjects placed around a shape.此脚本附加到围绕形状放置的小游戏对象。 They represent the locations at which drag is computed.它们代表计算阻力的位置。 SO for complex shapes these should be at any extremities, and should be fairly spread out over the object.所以对于复杂的形状,这些应该在任何四肢,并且应该在物体上相当分散。 The voxel object's rigid body's mass should approximate the portion of the object this voxel represents.体素对象的刚体质量应近似于该体素所代表的对象部分。

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


}

VoxelForceVisualizer体素力可视化器

This is a attached to prefab of a thin arrow that I put as a child of the voxels to allow force arrows to be drawn during debugging the drag force.这是附加到细箭头的预制件,我将其作为体素的子项放置,以允许在调试阻力期间绘制力箭头。

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

}

VoxelVisualizer体素可视化器

this is attached to a small sphere object as a child of the voxel empty.这作为体素空的子级附加到一个小球体对象上。 It's just to see where the voxels are, and let the above scripts show/hide the voxels without disabling the drag force calculations.这只是为了查看体素的位置,并让上述脚本显示/隐藏体素而不禁用阻力计算。

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

public class VoxelVisualizer : MonoBehaviour {


}

DragForce拖拽力

This calculates the drag force这将计算阻力

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


}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM