Created
September 29, 2025 18:15
-
-
Save kurtdekker/444fa291e3a7c2cdf060905b55c40c6a to your computer and use it in GitHub Desktop.
Camera frustum plane-projecting gizmo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections; | |
using System.Collections.Generic; | |
using UnityEngine; | |
// @kurtdekker | |
// | |
// Projects a Camera frustum onto an arbitrary Transform plane | |
// | |
// You can (probably) drop this script on any GameObject. | |
// | |
// It will try and find the Main Camera if present, or you can specify a Camera. | |
// | |
// You must specify a ProjectionPlane transform as well. | |
// | |
[ExecuteAlways] | |
public class CameraFrustumPlaneGizmo : MonoBehaviour | |
{ | |
public enum ProjAxis | |
{ | |
X, | |
Y, | |
Z, | |
} | |
[Header("Projects on the Z == 0 of this transform.", order = 1)] | |
public Transform ProjectionPlane; | |
[Header("Will try to find Main Camera if empty")] | |
public Camera CameraToUse; | |
[Header("Axis orientation.")] | |
public ProjAxis ProjectionAxis = ProjAxis.Z; | |
private void Start() | |
{ | |
if (!CameraToUse) | |
{ | |
CameraToUse = Camera.main; | |
} | |
// TODO: you might want to destroy this when not in editor... | |
//if (!Application.isEditor) | |
//{ | |
// Destroy(this); | |
//} | |
} | |
bool valid; | |
Vector3[] ProjectedPoints; | |
Vector2[] ViewportCorners = new Vector2[] | |
{ | |
new Vector2( 0, 0), | |
new Vector2( 0, 1), | |
new Vector2( 1, 1), | |
new Vector2( 1, 0), | |
}; | |
private void Update() | |
{ | |
// presume invalidity | |
valid = false; | |
// print out some warnings if you like... | |
if (!CameraToUse) | |
{ | |
return; | |
} | |
if (!ProjectionPlane) | |
{ | |
return; | |
} | |
// malloc | |
if (ProjectedPoints == null || ProjectedPoints.Length != ViewportCorners.Length) | |
{ | |
ProjectedPoints = new Vector3[ ViewportCorners.Length]; | |
} | |
// all four corners must hit | |
for (int cornerIndex = 0; cornerIndex < ProjectedPoints.Length; cornerIndex++) | |
{ | |
Vector2 viewportPosition = ViewportCorners[cornerIndex]; | |
Ray ray = CameraToUse.ViewportPointToRay(viewportPosition); | |
// by default: this projects on back of the Z == 0 | |
Plane plane = new Plane(inNormal: ProjectionPlane.forward, inPoint: ProjectionPlane.position); | |
// choose orientation | |
switch( ProjectionAxis) | |
{ | |
case ProjAxis.X: | |
plane = new Plane(inNormal: ProjectionPlane.right, inPoint: ProjectionPlane.position); | |
break; | |
case ProjAxis.Y: | |
plane = new Plane(inNormal: ProjectionPlane.up, inPoint: ProjectionPlane.position); | |
break; | |
case ProjAxis.Z: // defaulted above | |
break; | |
} | |
// cast | |
float enter; | |
if (plane.Raycast( ray, out enter)) | |
{ | |
ProjectedPoints[cornerIndex] = ray.GetPoint(enter); | |
} | |
else | |
{ | |
// can't hit the plane from this camera viewport; give up fully | |
return; | |
} | |
} | |
// only executed if we hit all four corners | |
valid = true; | |
} | |
private void OnDrawGizmos() | |
{ | |
if (valid) | |
{ | |
for (int cornerIndex = 0; cornerIndex < ProjectedPoints.Length; cornerIndex++) | |
{ | |
// main bounding rect / trapezoid | |
int nextCorner = (cornerIndex + 1) % ProjectedPoints.Length; | |
var point1 = ProjectedPoints[cornerIndex]; | |
var point2 = ProjectedPoints[nextCorner]; | |
Gizmos.DrawLine(point1, point2); | |
// crossing bars (X down the middle) | |
if (cornerIndex < 2) | |
{ | |
nextCorner = (cornerIndex + 2) % ProjectedPoints.Length; | |
point1 = ProjectedPoints[cornerIndex]; | |
point2 = ProjectedPoints[nextCorner]; | |
Gizmos.DrawLine(point1, point2); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment