[Unity3D]相機視野範圍OutLine小工具

1.最終代碼

某些時候,需要根據相機位置,調整遊戲物體的位置。但是不選中相機,就看不到相機的視野範圍,因此,添加此小工具。
首先,獲取相機座標系的視野範圍信息,然後,將Gizmos.matrix設置爲相機的。繪製的時候,從camera local座標系,變換到world座標系。
其中,注意,相機是右手座標系,相機的forward實際是vector3.back.
先給上最終的代碼:

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

[RequireComponent(typeof(Camera))]
public class CameraOutLine : MonoBehaviour
{
    private void OnDrawGizmos()
    {
        if (this.enabled)
        {
            Camera cam = this.GetComponent<Camera>();
            if (cam.orthographic)
            {
                Vector3 center = Vector3.back * (cam.nearClipPlane + cam.farClipPlane) * 0.5f;
                Vector3 size = new Vector3(cam.orthographicSize * 2 * cam.aspect, cam.orthographicSize * 2, cam.farClipPlane - cam.nearClipPlane);

                Matrix4x4 m = Gizmos.matrix;
                Gizmos.matrix = cam.cameraToWorldMatrix;
                Gizmos.DrawWireCube(center,size);
                Gizmos.matrix = m;
            }
            else
            {
                Matrix4x4 matrixCam = cam.cameraToWorldMatrix;
                Matrix4x4 nagtiveZ = Matrix4x4.identity;
                nagtiveZ.SetTRS(Vector3.zero, Quaternion.Euler(0, 0, 0), new Vector3(1, 1, -1));
                Gizmos.matrix = matrixCam * nagtiveZ;
                // center是平截頭的頂端,即攝像機的位置。相對於自己是zero.
                Vector3 center = Vector3.zero;
                Gizmos.DrawFrustum(center, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);

                Gizmos.matrix = m;
            }
        }
    }
}

2.過程

寫這個小工具並不是一帆風順的。

一開始,沒有考慮到相機會旋轉,只寫出了相機Local座標系的情況。

寫world座標系的時候,其實,也是經歷了由繁到簡的過程。

由繁到簡,有三種方式可以實現:

方法1.完全手動進行頂點位置的平移和旋轉

Camera的參數中,可以通過相似三角形計算出近截面和遠截面的頂點位置。

首先,計算出頂點相對於Camera的位置。

fieldOfView表示相機視野的張角(y-z平面,Height方向)。根據相似三角形,可以得到任意遠位置的高度的一半。

view表示視野張角度數,far是距離相機的位置。

private float halfHeight(float view, float far)
{
     float w = Mathf.Tan(view * 0.5f * Mathf.Deg2Rad) * far;
     return w;
}

帶入fieldOfView、nearClipPlane、farClipPlane,可以得到近截面高度的一半和遠截面高度的一半。

float nearHalfHeight = halfHeight(cam.fieldOfView, cam.nearClipPlane);
float farHalfHeight = halfHeight(cam.fieldOfView, cam.farClipPlane);

相機的aspect爲寬高比,所以,寬=高* aspect。

寬和高都有了,再根據近截面和遠截面的中心點,就可以計算出它們的頂點。

注意,近截面和遠截面的方向是相機的前方,但是相機是右手座標系,而Unity3D使用左手座標系,因此,相機的前方,實際上是系統的後方。

Vector3 nearPos = -Vector3.forward * cam.nearClipPlane;
Vector3 farPos = -Vector3.forward * cam.farClipPlane;

接下來計算近截面和遠截面的8個頂點:

Vector3 nearTopLeft = new Vector3(nearPos.x - nearHalfHeight * aspect, nearPos.y + nearHalfHeight, nearPos.z);
Vector3 nearTopRight = nearTopLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);
Vector3 nearBottomLeft = nearTopLeft - new Vector3(0, 2 * nearHalfHeight, 0);
Vector3 nearBottomRight = nearBottomLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);

Vector3 farTopLeft = new Vector3(farPos.x - farHalfHeight * aspect, farPos.y + farHalfHeight, farPos.z);
Vector3 farTopRight = farTopLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);
Vector3 farBottomLeft = farTopLeft - new Vector3(0, 2 * farHalfHeight, 0);
Vector3 farBottomRight = farBottomLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);

然後需要將頂點從相機的local座標系變換到世界座標系。點的變化,直接使用camera.transform.TransformPoint:

private Vector3 transPointFromCamToWorld(Camera cam, Vector3 point)
{
   return cam.transform.TransformPoint(point);
}

最後,使用Gizmos.DrawLine的方式,畫出平截頭輪廓。

整個代碼爲:

    private void OnDrawGizmos()
    {
        if (this.enabled)
        {
            Color c = Gizmos.color;
            Gizmos.color = Color.yellow;
            Camera cam = this.GetComponent<Camera>();
            if (cam.orthographic)
            {
				// 省略
            }
            else
            {
                float nearHalfHeight = halfHeight(cam.fieldOfView, cam.nearClipPlane);
                float farHalfHeight = halfHeight(cam.fieldOfView, cam.farClipPlane);
                Vector3 nearPos = -Vector3.forward * cam.nearClipPlane;
                Vector3 farPos = -Vector3.forward * cam.farClipPlane;

                float aspect = cam.aspect;

                Vector3 nearTopLeft = new Vector3(nearPos.x - nearHalfHeight * aspect, nearPos.y + nearHalfHeight, nearPos.z);
                Vector3 nearTopRight = nearTopLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);
                Vector3 nearBottomLeft = nearTopLeft - new Vector3(0, 2 * nearHalfHeight, 0);
                Vector3 nearBottomRight = nearBottomLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);


                Vector3 farTopLeft = new Vector3(farPos.x - farHalfHeight * aspect, farPos.y + farHalfHeight, farPos.z);
                Vector3 farTopRight = farTopLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);
                Vector3 farBottomLeft = farTopLeft - new Vector3(0, 2 * farHalfHeight, 0);
                Vector3 farBottomRight = farBottomLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);


                nearTopLeft = transPointFromCamToWorld(cam, nearTopLeft);
                nearTopRight = transPointFromCamToWorld(cam, nearTopRight);
                nearBottomLeft = transPointFromCamToWorld(cam, nearBottomLeft);
                nearBottomRight = transPointFromCamToWorld(cam, nearBottomRight);
                
                farTopLeft = transPointFromCamToWorld(cam, farTopLeft);
                farTopRight = transPointFromCamToWorld(cam, farTopRight);
                farBottomLeft = transPointFromCamToWorld(cam, farBottomLeft);
                farBottomRight = transPointFromCamToWorld(cam, farBottomRight);

                Gizmos.DrawLine(nearTopLeft, nearTopRight);
                Gizmos.DrawLine(nearTopRight, nearBottomRight);
                Gizmos.DrawLine(nearBottomRight, nearBottomLeft);
                Gizmos.DrawLine(nearBottomLeft, nearTopLeft);

                Gizmos.DrawLine(farTopLeft, farTopRight);
                Gizmos.DrawLine(farTopRight, farBottomRight);
                Gizmos.DrawLine(farBottomRight, farBottomLeft);
                Gizmos.DrawLine(farBottomLeft, farTopLeft);

                Gizmos.DrawLine(nearTopLeft, farTopLeft);
                Gizmos.DrawLine(nearTopRight, farTopRight);
                Gizmos.DrawLine(nearBottomLeft, farBottomLeft);
                Gizmos.DrawLine(nearBottomRight, farBottomRight);

            }
            Gizmos.color = c;
        }
    }
    private float halfHeight(float view, float far)
    {
        float w = Mathf.Tan(view * 0.5f * Mathf.Deg2Rad) * far;
        return w;
    }
    private Vector3 transPointFromCamToWorld(Camera cam, Vector3 point)
    {
        return cam.transform.TransformPoint(point);
    }

方法2.使用Gizmos.matrix

查看Gizmos的定義的時候,發現Gizmos有個matrix變量,可以讓Gizmos繪製的時候,使用特定的matrix。

而matrix中,我們知道,記錄了3D位置或者向量的平移、旋轉、縮放信息。

使用Camera的matrix,就不需要手動對頂點進行變換了。

代碼如下:

    private void OnDrawGizmos()
    {
        if (this.enabled)
        {
            Color c = Gizmos.color;
            Gizmos.color = Color.yellow;
            Camera cam = this.GetComponent<Camera>();
            if (cam.orthographic)
            {
                // 省略
            }
            else
            {
                float nearHalfHeight = halfHeight(cam.fieldOfView, cam.nearClipPlane);
                float farHalfHeight = halfHeight(cam.fieldOfView, cam.farClipPlane);
                Vector3 nearPos = -Vector3.forward * cam.nearClipPlane;
                Vector3 farPos = -Vector3.forward * cam.farClipPlane;

                float aspect = cam.aspect;

                Vector3 nearTopLeft = new Vector3(nearPos.x - nearHalfHeight * aspect, nearPos.y + nearHalfHeight, nearPos.z);
                Vector3 nearTopRight = nearTopLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);
                Vector3 nearBottomLeft = nearTopLeft - new Vector3(0, 2 * nearHalfHeight, 0);
                Vector3 nearBottomRight = nearBottomLeft + new Vector3(2 * nearHalfHeight * aspect, 0, 0);


                Vector3 farTopLeft = new Vector3(farPos.x - farHalfHeight * aspect, farPos.y + farHalfHeight, farPos.z);
                Vector3 farTopRight = farTopLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);
                Vector3 farBottomLeft = farTopLeft - new Vector3(0, 2 * farHalfHeight, 0);
                Vector3 farBottomRight = farBottomLeft + new Vector3(2 * farHalfHeight * aspect, 0, 0);
				
                Matrix4x4 m = Gizmos.matrix;
                // 使用相機的matrix
                Gizmos.matrix = cam.cameraToWorldMatrix;

                Gizmos.DrawLine(nearTopLeft, nearTopRight);
                Gizmos.DrawLine(nearTopRight, nearBottomRight);
                Gizmos.DrawLine(nearBottomRight, nearBottomLeft);
                Gizmos.DrawLine(nearBottomLeft, nearTopLeft);

                Gizmos.DrawLine(farTopLeft, farTopRight);
                Gizmos.DrawLine(farTopRight, farBottomRight);
                Gizmos.DrawLine(farBottomRight, farBottomLeft);
                Gizmos.DrawLine(farBottomLeft, farTopLeft);

                Gizmos.DrawLine(nearTopLeft, farTopLeft);
                Gizmos.DrawLine(nearTopRight, farTopRight);
                Gizmos.DrawLine(nearBottomLeft, farBottomLeft);
                Gizmos.DrawLine(nearBottomRight, farBottomRight);
                Gizmos.matrix = m;

            }
            Gizmos.color = c;
        }
    }

方法3.使用Gzimos.DrawFrustum

通過第二種方式實現之後,定睛一看,Gizmos還有一個叫做DrawFrustum的方法,直接可以繪製平截頭,此時感覺前兩種方式白費勁了[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-K1gRJ5pI-1584869500445)(file:///C:\Users\eric\AppData\Local\Temp\SGPicFaceTpBq\7700\05C7D0C4.png)]。(仔細一想,還是從前兩種方式中,鞏固了3D知識的,不算虧)

Gizmos.matrix = cam.cameraToWorldMatrix;
// center是平截頭的頂端,即攝像機的位置。相對於自己是zero.
Vector3 center = Vector3.zero;
Gizmos.DrawFrustum(center, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);

然而,此時的結果,正好是和正確表現反向的。黃色是正確結果,藍色是反向的。

image-20200322172456423

我們知道,肯定是和cameraToWorldMatrix中,z軸的方向相關,和世界座標系是相反方向的。

我們只需要改變matrix中z軸的方向就可以了。

已知,矩陣相乘可以看做是一個對另外一個矩陣進行平移、旋轉和縮放操作,因此,我們只需要對cameraToWorldMatrix進行z軸相反方向的縮放即可。

Matrix4x4 matrixCam = cam.cameraToWorldMatrix;
Matrix4x4 nagtiveZ = Matrix4x4.identity;
nagtiveZ.SetTRS(Vector3.zero, Quaternion.Euler(0, 0, 0), new Vector3(1, 1, -1));
Gizmos.matrix = matrixCam * nagtiveZ;
 // center是平截頭的頂端,即攝像機的位置。相對於自己是zero.
Vector3 center = Vector3.zero;
Gizmos.DrawFrustum(center, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);

以上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章