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);
然而,此時的結果,正好是和正確表現反向的。黃色是正確結果,藍色是反向的。
我們知道,肯定是和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);
以上。