What causes this z position change?

This is a follow-up on the following thread.

I'm applying a code that will move an object just outside the camera's frustum. I have used the same code in other projects, and it worked fine.

However, I'm using it with URP now, and I'm experiencing a z-position change while I expected only a x position change.

And I just don't see why it does that.

I have recorded a video here.

If I press the button, the cube should be moved to the left only. But it also moves to the back (z position).

And this is the script that I have applied to the cube in the video:

using System;
using UnityEngine;

public class PlaceOutsideCamera : MonoBehaviour
{
public void PlaceOutsideFrustum(Transform uTransform, bool uLeft)
{
uTransform.position = pPlaceOutsideFrustum(uTransform, uLeft);
}
public static Vector3 pPlaceOutsideFrustum(Transform uTransform, bool uLeft)
{
var frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
Ray ray;
Plane plane;

if (uLeft)
{
// 1.
ray = Camera.main.ScreenPointToRay(new Vector3(0, Camera.main.pixelHeight / 2f, 0));
plane = frustumPlanes[0];
}
else
{
ray = Camera.main.ScreenPointToRay(new Vector3(Camera.main.pixelWidth - 1, Camera.main.pixelHeight / 2f, 0));
plane = frustumPlanes[1];
}

float fDistance = Mathf.Abs(Camera.main.transform.position.z - uTransform.position.z);

// 2.
var borderPoint = ray.GetPoint(fDistance);

// 3.
var frustumOutside = -plane.normal;

Bounds b = BoundsFromTransform(uTransform);
// 4.
var halfDiameter = b.size.magnitude / 2f;

return borderPoint + frustumOutside * halfDiameter;
}
public static Bounds BoundsFromTransform(Transform uTransform)
{
try
{
Bounds bounds = new Bounds(uTransform.position, Vector3.zero);//wir hätten auch einfach new Bounds() nehmen können, meint Manfredas

foreach (Renderer renderer in uTransform.GetComponentsInChildren<Renderer>())
{
bounds.Encapsulate(renderer.bounds);
}

Vector3 nOff = bounds.center - uTransform.position;

return new Bounds(nOff, bounds.size);
}
catch (Exception ex)
{
UnityEngine.Debug.Break();
return new Bounds();
}
}
public void OnGUI()
{
if (GUI.Button(new Rect(0, 350, 100, 50), "DoThis!"))
{
pDoThis();
}
}
private void pDoThis()
{
this.PlaceOutsideFrustum(this.transform, true);
}

}


Can anybody tell me why my code does this? I just don't see it. Thank you very much for the help!

Edit: Here is another video that shows the camera position in the Inspector.

• Can you confirm, in your video, where is your camera? Is it toward the top or the bottom of the screen in your scene view? – DMGregory Mar 3 '20 at 14:00
• @DMGregory The camera position is (0, 1, -10). I have added another video to my posting to show the camera properties. – tmighty Mar 3 '20 at 22:05

I'd like to expand on Jon Koelzer's answer, with a bit more detail about what aspects of the code cause this, and how to modify the code to correct it.

When you measure your distance like this:

float fDistance = Mathf.Abs(Camera.main.transform.position.z - uTransform.position.z);


...you're measuring the distance along the z axis only.

But when you apply this distance to your ray like this:

 var borderPoint = ray.GetPoint(fDistance);


...you're measuring the distance diagonally, along the direction of the ray. Part of that distance moves along the z axis, but part of it moves along the x axis (pushing the object off the left side of the screen).

Because you're dividing what was previously just the z depth between two axes, the amount that ends up in your final z distance is less than what you started with, making the object move closer to the camera with each invocation.

Then of course we move a little closer to the camera again when we apply the half-bounding-box offset, since we're moving along the frustum plane normal, and that points slightly backward as the planes fan outward.

You can fix this by not thinking about rays at all.

// Cache the camera reference for brevity, and so we don't search for it repeatedly.
var camera = Camera.main;

// Measure the depth along the camera's forward vector, so it works regardless of rotation.
float forwardDepth = Vector3.Dot(uTransform.position - camera.transform.position, camera.transform.forward);

// Use this depth to specify the point in viewport-depth space we want from the camera.
Vector3 viewportEdge = new Vector3(0f, 0.5f, forwardDepth);

// This will hold the signed secant of the angle our frustum plane normal makes
// with the camera's right vector - see diagram below for why!
float shiftScale;

if(uLeft) {
shiftScale = -1f / Vector3.Dot(frustumPlanes[0].normal, camera.transform.right);
} else {
// Since we're in viewport space, not screenspace, we don't need pixel dimensions.
viewportEdge.x = 1f;

shiftScale = -1f / Vector3.Dot(frustumPlanes[1].normal, camera.transform.right);
}

// Convert our viewport-depth point to a point in world space, not a ray.
Vector3 worldPoint = camera.ViewportToWorldPoint(viewportEdge);

// Get bounds for shifting it out of frame.
Bounds b = BoundsFromTransform(uTransform);

// Shift directly left or right, with no component forward/back, so we preserve depth.
return worldPoint + shiftScale * radius * camera.transform.right;


As for how that shiftScale is calculated - we can get it with a little trigonometry:

And the fact that since the plane normal and camera.transform.right are both unit vectors, their dot product gives us cos(theta) :)

• Thanks for clarifying for me DMGregory :) i am stuck at work and unable to spend the time to elaborate – Jon Mar 4 '20 at 2:54
• as another approach, we have the planes. Why not simply project the object’s translation onto the plane, and then add an additional offset to account for the bounding sphere radius? I like this better 😁 – Jon Mar 4 '20 at 2:56
• I'm unsure what to do now, whose answer to mark as the solution. – tmighty Mar 4 '20 at 23:03
• I've got plenty of rep, so don't worry about me. ;) – DMGregory Mar 4 '20 at 23:10
• With Jon's approach which we elaborated a bit more in a private chat, we couldn't get it to work (yet), so I have seleced the solution which actually works. – tmighty Mar 5 '20 at 17:27

The planes that define your frustum are canted and the amount is dependent on your FOV setting, which I've circled in red on the right in the diagram below. If I'm not mistaken, the lower your FOV, the more narrow that frustum is- the narrower your frustum is, the less your object will translate along the Z-axis.

This is just a rough sketch, but the vector Fo represents your frustumOutside variable. The Fo vector is angled and has used both the x and z elements to express this direction, that's where your Z movement comes from.

• Thank you. Can you please also post some code to verify / provide a solution? – tmighty Mar 3 '20 at 22:11
• I have answered both of your questions "Can anybody tell me why my code does this" and "what causes this Z position change". I do not understand what you mean by solution, you stated the goal was "I'm applying a code that will move an object just outside the camera's frustum" which as demonstrated by your video, you have accomplished. – Jon Mar 3 '20 at 22:15
• Yes, I am really thankful, but can you perhaps also code a solution to counter-act the Fo vector? Or tell me about one so that I can code it? – tmighty Mar 3 '20 at 22:17
• @tmighty Yes, simply use the Camera.Left vector as your direction instead of Fo, and use the pythagorean theorem and the above diagram to determine the correct scalar quantity. You were moving along the hypotenuse before, now you are moving along the small side of the right triangle. – Jon Mar 3 '20 at 22:24
• Sorry, "Unable", as Capt. Sullenberger would have said. This is beyond my mathematical horizon / capabilities. Would you be able to lend a helping hand in this case? It's not laziness, it's pure inability of mine. – tmighty Mar 3 '20 at 22:42

If you want to move it on x axis only then you need to restrict z axis of cube's transformation. There might be other ways to do it but the easiest way for now is

public void PlaceOutsideFrustum(Transform uTransform, bool uLeft)
{
uTransform.position = new Vector3(pPlaceOutsideFrustum(uTransform, uLeft).x, pPlaceOutsideFrustum(uTransform, uLeft).y, uTransform.position.z);
}

• Thank you, but unfortunately your code doesn't place the transform outside the camera. I can still see it. To test the code you can create a new scene, create a cube, put the script on the cube and then press the "DoIt!" button the game view. – tmighty Mar 3 '20 at 22:10

Jon's answer explains where your problem is coming from. The planes of the frustum aren't perpendicular to the x axis. I suggest a different approach:

If your objective is to put the object outside the camera's view, consider changing the object position based on raycasting the screen edge and adding to that half the width of the object. The object will now be outside of the camera's view independently of the FOV.

• It looks to me like your suggestion is what OP's code already does. Can you clarify the differences in the approach you have in mind? – DMGregory Mar 3 '20 at 13:57