0-1 缩放 3d 球体与光线投射统一

0-1 scaling on 3d sphere with raycast in unity

我正在 3d 球体上拍摄光线投射,我想要一种方法可以根据光线投射点(位置)获得 0(球体中心)到 1(球体周长或轮廓)之间的浮点数。我怎样才能做到这一点。

我已经尝试计算球体位置和光线投射影响点之间的差异,它有些工作,但在代码中实现起来非常复杂。

是否有任何其他方法可以做到这一点,或者我是否缺少一些数学函数或向量函数可以在这种情况下提供帮助。

你可以像这样Plane which is perpendicular to the ray direction and goes through the sphere's origin and project the hit position onto it using Plane.Raycast

...
if(Physics.Raycast(ray, out var hit))
{
    if(hit.collider is SphereCollider hitSphere)
    {
        // This is assuming that your sphere is actually a sphere with uniform local scales
        var sphereRadius = hitSphere.radius * hitSphere.tranform.lossyScale.x;

        var hitObjectCenter = hit.collider.bounds.center;
        var plane = new Plane(-ray.diretcion, hitObjectCenter);
        if(plane.Raycast(ray, out var hitDistance))
        {
            var planeHitPoint = ray.GetPoint(hitDistance);

            var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;

            // now result should be your value between 0 (sphere center) and 1 (exactly on the sphere "edge" - probably never fully reached of course)
        }
    }
}

由于我们的平面使用正常的光线方向,您可能也可以使用 Plane.ClosestPointOnPlane 作为近似值,例如

var plane = new Plane(-ray.diretcion, hitObjectCenter);
var planeHitPoint = plane.ClosestPointOnPlane(hitDistance);
var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;

结果应该几乎相同,不确定哪个更快,但我猜光线投射更精确(我们说的差异有点像 0.000001 ;))。


作为一个小演示,因为解释它是如何工作的有点复杂^^

public class Example : MonoBehaviour
{
    public Camera mainCamera;

    private float result;
    private Ray ray;
    private Vector3 hitPoint;
    private Vector3? hitObjectCenter;
    private Vector3 planeHitPoint;

    private void Awake()
    {
        if (!mainCamera) mainCamera = Camera.main;
    }

    private void Update()
    {
        ray = mainCamera.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out var hit) && hit.collider is SphereCollider hitSphere)
        {
            hitPoint = hit.point;

            // This is assuming that your sphere is actually a sphere with uniform local scales
            var sphereRadius = hitSphere.radius * hitSphere.transform.lossyScale.x;

            hitObjectCenter = hit.collider.bounds.center;
            var plane = new Plane(-ray.direction, hitObjectCenter.Value);
            if(plane.Raycast(ray, out var hitDistance))
            {
                var planeHitPoint = ray.GetPoint(hitDistance);

                var result = Vector3.Distance(hitObjectCenter, planeHitPoint) / sphereRadius;
            }
        }
        else
        {
            hitObjectCenter = null;
        }
    }

    private Mesh planeMesh;

    private void OnDrawGizmos()
    {
        // if not hitting a sphere do nothing
        if (!hitObjectCenter.HasValue)
        {
            return;
        }

        // lazy initialize plan visualization
        if (!planeMesh)
        {
            planeMesh = new Mesh
            {
                vertices = new Vector3[4]
                {
                    new Vector3(-1, -1),
                    new Vector3(-1, 1),
                    new Vector3(1, 1),
                    new Vector3(1, -1)
                },
                triangles = new[]
                {
                    0, 1, 2,
                    0, 2, 3,

                    // backfaces
                    0, 2, 1,
                    0, 3, 2
                },
                normals = new Vector3[4]
                {
                    Vector3.forward,
                    Vector3.forward,
                    Vector3.forward,
                    Vector3.forward,
                }
            };
        }

        // Visualize ray
        Gizmos.color = Color.green;
        Gizmos.DrawLine(ray.origin, hitPoint);

        // Visualize hit point on sphere
        Gizmos.DrawWireSphere(hitPoint, 0.05f);

        // Visualize Plane
        Gizmos.color = Color.cyan;
        Gizmos.DrawWireMesh(planeMesh, hitObjectCenter.Value, Quaternion.LookRotation(ray.direction));

        // visualize hit point on plane
        Gizmos.DrawWireSphere(planeHitPoint, 0.05f);

        // Visualize distance
        Gizmos.color = Color.red;
        Gizmos.DrawLine(planeHitPoint, hitObjectCenter.Value);

        // print result on screen
        Handles.Label((planeHitPoint + hitObjectCenter.Value) / 2f, $"RESULT: {result:0.000}", new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter });
    }
}