某些时候,需要根据相机位置,调整游戏物体的位置。但是不选中相机,就看不到相机的视野范围,因此,添加此小工具。
首先,获取相机坐标系的视野范围信息,然后,将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();
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;
}
}
}
}
写这个小工具并不是一帆风顺的。
一开始,没有考虑到相机会旋转,只写出了相机Local坐标系的情况。
写world坐标系的时候,其实,也是经历了由繁到简的过程。
由繁到简,有三种方式可以实现:
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
(); 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); }
查看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();
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;
}
}
通过第二种方式实现之后,定睛一看,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);
以上。