- Home /
project view frustrum of camera onto plane as white rectangle
I am building a minimap with a rectangle indicating the camera's view, and I want to be able to generate a rectangle showing where the camera can view, as a rectangle (and not a trapezoid). The camera can rotate, and so will this rectangle.
How would I render this rectangle (assuming a plane is used to help) and make sure it is rendered on a certain layer (to show it on the minimap)?
I found new information, and determined I have to do something with eh view frustum planes. I know these planes are "infinite", so i have to raycast along the edges of the camera's frustum, and want to raycast until I hit a specific gameObject collider. Then I have to somehow generate a rectangle mesh from this, and then set it's color or material to white. How would I do this? I would essentially draw 4 rectangles, one for each "side" of the rectangle I want as my frustum visual.
I also acquired new information, I found a way to generate the camera frustum planes, as a mesh, using the near clip and far clip distance. I used the class form this post: Generate Frustum Mesh to generate the frustum mesh. I now need to get the 4 points of the far clip plane, the near clip plane, and do raycasts along the edges to my surface to generate what I want. Then want to use those points to generate a mesh dynamically to create my rectangle.
How would I get the right plane vertices I want?
I found how to get the vertices I want, I can use a method, and some trigonometry to get the clip plane points at a certain distance. I found this video that describes this function:
public struct ClipPlanePoints{
public Vector3 UpperLeft;
public Vector3 UpperRight;
public Vector3 LowerLeft;
public Vector3 LowerRight;
}
public static ClipPlanePoints CameraClipPlanePoints(this Camera cam, float d)
{
ClipPlanePoints clipPlanePoints = ClipPlanePoints();
Transform camTrans = cam.transform;
Vector3 camPos = camTrans.position;
float halfFOV = cam.fieldOfView*0.5 * Mathf.Deg2Rad;
float aspect = cam.aspect;
float height = Mathf.Tan(halfFOV) * d;
float width = height*aspect;
//lower right
clipPlanePoints.LowerRight = (camPos + camTrans.forward * d);
clipPlanePoints.LowerRight += (camTrans.right*width);
clipPlanePoints.LowerRight -= (camTrans.up * height);
//lower left
clipPlanePoints.LowerLeft = (camPos + camTrans.forward * d);
clipPlanePoints.LowerLeft -= (camTrans.right*width);
clipPlanePoints.LowerLeft -= (camTrans.up * height);
//upperRight
clipPlanePoints.UpperRight = (camPos + camTrans.forward * d);
clipPlanePoints.UpperRight += (camTrans.right*width);
clipPlanePoints.UpperRight += (camTrans.up * height);
//upperLeft
clipPlanePoints.UpperLeft = (camPos + camTrans.forward * d);
clipPlanePoints.UpperLeft -= (camTrans.right*width);
clipPlanePoints.UpperLeft += (camTrans.up * height);
return clipPlanePoints;
}
This gets the points at a certain distance from the camera. Now, I just have to do raycasts from the near clip plane points, to the far clip plane points, and determine the colliding points (base don layer) and use those to build a mesh to render my rectangle.
I found out a decent way (but doesnt work if the camera frustum is out of bounds of the ground surface you're projecting onto):
using UnityEngine;
using System.Collections;
public class RenderCameraFrustum:MonoBehaviour
{
bool hasRendered = false;
bool renderFullFrustum = false;
bool projectMesh = false;
bool projectRectangles = true;
public Material material;
public CameraExtension.ClipPlanePoints result;
public float width = 0.5f;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if(!hasRendered)
{
hasRendered=true;
GameObject g = new GameObject("render");
g.transform.position = transform.position;
g.AddComponent<MeshFilter>();
g.AddComponent<MeshRenderer>();
MeshRenderer r = g.GetComponent<MeshRenderer>();
r.material = material;
MeshFilter m = g.GetComponent<MeshFilter>();
if(renderFullFrustum)
{
m.mesh = camera.GenerateFrustumMesh();
//m.RecalculateNormals();
}
else if(projectMesh)
{
//do 4 raycasts, one for each corner vertex
//first get frustum points, 2 sets, near and far
CameraExtension.ClipPlanePoints nearPoints = camera.CameraClipPlanePoints(camera.nearClipPlane);
CameraExtension.ClipPlanePoints farPoints = camera.CameraClipPlanePoints(camera.farClipPlane);
result = new CameraExtension.ClipPlanePoints();
Vector3 rectangleDestination = new Vector3();
Vector3 dir = new Vector3();
LayerMask lm = LayerMask.NameToLayer("Ground");
int mask = 1 << lm.value;
//top left
dir = farPoints.UpperLeft-nearPoints.UpperLeft;
RaycastHit hit;
if (Physics.Raycast(nearPoints.UpperLeft, dir, out hit,camera.farClipPlane,mask ))
{
Debug.Log("raycast upperLeft hit");
result.UpperLeft = hit.point;
}
//top right
dir = farPoints.UpperRight-nearPoints.UpperRight;
if (Physics.Raycast(nearPoints.UpperRight, dir, out hit,camera.farClipPlane,mask ))
{
Debug.Log("raycast upperLeft hit");
result.UpperRight = hit.point;
}
//bottom left
dir = farPoints.LowerLeft-nearPoints.LowerLeft;
if (Physics.Raycast(nearPoints.LowerLeft, dir, out hit,camera.farClipPlane,mask ))
{
Debug.Log("raycast upperLeft hit");
result.LowerLeft = hit.point;
}
//bottom right
dir = farPoints.LowerRight-nearPoints.LowerRight;
if (Physics.Raycast(nearPoints.LowerRight, dir, out hit,camera.farClipPlane,mask ))
{
Debug.Log("raycast upperLeft hit");
result.LowerRight = hit.point;
}
//now use these points to create a mesh
//UL
//UR
//LL
//LR
Vector3[] vertices = new Vector3[]
{
result.UpperLeft,
result.UpperRight,
result.LowerLeft,
result.LowerRight
};
Vector3[] normals =
{
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward
};
Vector2[] uv = new Vector2[]
{
new Vector2(1, 1),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(0, 0),
};
int[] triangles = new int[]
{
0, 1, 2,
2, 1, 3,
};
m.mesh.Clear();
m.mesh.vertices = vertices;
m.mesh.triangles = triangles;
m.mesh.normals = normals;
m.mesh.uv = uv;
//set object position at collided object, then move it with camera.
if (Physics.Raycast(camera.transform.position, camera.transform.forward, out hit,camera.farClipPlane,mask ))
{
//Debug.Log("raycast forward hit");
rectangleDestination = hit.point;
}
g.transform.position = rectangleDestination;
//debug output points
Debug.Log("near points: 1-["+nearPoints.UpperLeft+"] 2-["+nearPoints.UpperRight+"] 3-["+nearPoints.LowerRight+"] 4-["+nearPoints.LowerLeft+"]");
Debug.Log("far points: 1-["+farPoints.UpperLeft+"] 2-["+farPoints.UpperRight+"] 3-["+farPoints.LowerRight+"] 4-["+farPoints.LowerLeft+"]");
Debug.Log("result points: 1-["+result.UpperLeft+"] 2-["+result.UpperRight+"] 3-["+result.LowerRight+"] 4-["+result.LowerLeft+"]");
}
else if(projectRectangles)
{
//do 4 raycasts, one for each corner vertex
//first get frustum points, 2 sets, near and far
CameraExtension.ClipPlanePoints nearPoints = camera.CameraClipPlanePoints(camera.nearClipPlane);
CameraExtension.ClipPlanePoints farPoints = camera.CameraClipPlanePoints(camera.farClipPlane);
CameraExtension.ClipPlanePoints innerResult = new CameraExtension.ClipPlanePoints();
result = new CameraExtension.ClipPlanePoints();
Vector3 rectangleDestination = new Vector3();
Vector3 dir = new Vector3();
LayerMask lm = LayerMask.NameToLayer("Ground");
int mask = 1 << lm.value;
//top left
dir = farPoints.UpperLeft-nearPoints.UpperLeft;
RaycastHit hit;
if (Physics.Raycast(nearPoints.UpperLeft, dir, out hit,camera.farClipPlane,mask ))
{
//Debug.Log("raycast upperLeft hit");
result.UpperLeft = hit.point;
}
//top right
dir = farPoints.UpperRight-nearPoints.UpperRight;
if (Physics.Raycast(nearPoints.UpperRight, dir, out hit,camera.farClipPlane,mask ))
{
Debug.Log("raycast upperLeft hit");
result.UpperRight = hit.point;
}
//bottom left
dir = farPoints.LowerLeft-nearPoints.LowerLeft;
if (Physics.Raycast(nearPoints.LowerLeft, dir, out hit,camera.farClipPlane,mask ))
{
//Debug.Log("raycast upperLeft hit");
result.LowerLeft = hit.point;
}
//bottom right
dir = farPoints.LowerRight-nearPoints.LowerRight;
if (Physics.Raycast(nearPoints.LowerRight, dir, out hit,camera.farClipPlane,mask ))
{
//Debug.Log("raycast upperLeft hit");
result.LowerRight = hit.point;
}
//now get inner rectangle points
innerResult.UpperLeft = new Vector3(result.UpperLeft.x+width, result.UpperLeft.y-width, result.UpperLeft.z);
innerResult.UpperRight = new Vector3(result.UpperRight.x-width, result.UpperRight.y-width, result.UpperRight.z);
innerResult.LowerLeft = new Vector3(result.LowerLeft.x+width, result.LowerLeft.y+width, result.LowerLeft.z);
innerResult.LowerRight = new Vector3(result.LowerRight.x-width, result.LowerRight.y+width, result.LowerRight.z);
Debug.Log("result inner points: 1-["+innerResult.UpperLeft+"] 2-["+innerResult.UpperRight+"] 3-["+innerResult.LowerRight+"] 4-["+innerResult.LowerLeft+"]");
//now use these points to create a mesh
//UL
//UR
//LL
//LR
Vector3[] vertices = new Vector3[]
{
//left part
result.UpperLeft,
innerResult.UpperLeft,
result.LowerLeft,
innerResult.LowerLeft,
//top part
result.UpperLeft,
result.UpperRight,
innerResult.UpperLeft,
innerResult.UpperRight,
//right part
innerResult.UpperRight,
result.UpperRight,
innerResult.LowerRight,
result.LowerRight,
//bottom part
innerResult.LowerLeft,
innerResult.LowerRight,
result.LowerLeft,
result.LowerRight
};
Vector3[] normals =
{
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward,
-Vector3.forward
};
Vector2[] uv = new Vector2[]
{
new Vector2(1, 1),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(0, 0),
new Vector2(1, 1),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(0, 0),
new Vector2(1, 1),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(0, 0),
new Vector2(1, 1),
new Vector2(1, 0),
new Vector2(0, 1),
new Vector2(0, 0)
};
//UL 0
//UR 1
//LL 2
//LR 3
int[] triangles = new int[]
{
0, 1, 2,
2, 1, 3,
4, 5, 6,
6, 5, 7,
8, 9, 10,
10, 9, 11,
12, 13, 14,
14, 13, 15
};
m.mesh.Clear();
m.mesh.vertices = vertices;
m.mesh.triangles = triangles;
m.mesh.normals = normals;
m.mesh.uv = uv;
//set object position at collided object, then move it with camera.
if (Physics.Raycast(camera.transform.position, camera.transform.forward, out hit,camera.farClipPlane,mask ))
{
//Debug.Log("raycast forward hit");
rectangleDestination = hit.point;
}
g.transform.position = rectangleDestination;
//debug output points
//Debug.Log("near points: 1-["+nearPoints.UpperLeft+"] 2-["+nearPoints.UpperRight+"] 3-["+nearPoints.LowerRight+"] 4-["+nearPoints.LowerLeft+"]");
//Debug.Log("far points: 1-["+farPoints.UpperLeft+"] 2-["+farPoints.UpperRight+"] 3-["+farPoints.LowerRight+"] 4-["+farPoints.LowerLeft+"]");
//Debug.Log("result points: 1-["+result.UpperLeft+"] 2-["+result.UpperRight+"] 3-["+result.LowerRight+"] 4-["+result.LowerLeft+"]");
}
}
}
}
This script does do more than necessary, and switches on booleans to render other frustum pars.