[Unity3D]相机视野范围OutLine小工具

文章目录

      • 1.最终代码
      • 2.过程
        • 方法1.完全手动进行顶点位置的平移和旋转
        • 方法2.使用Gizmos.matrix
        • 方法3.使用Gzimos.DrawFrustum

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

然而,此时的结果,正好是和正确表现反向的。黄色是正确结果,蓝色是反向的。

[Unity3D]相机视野范围OutLine小工具_第1张图片

我们知道,肯定是和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);

以上。

你可能感兴趣的:([Unity3D]相机视野范围OutLine小工具)