Why do Camera.WorldToScreenPoint and Camera.WorldToViewPoint return Vector3.zero?

Hello developers.

Iam working on a Fullscreen-Radar like this in the picture below:


[Screenshot of the game “Freelancer” by Microsoft]

To get the screen-position of the target I use Camera.WorldToScreenPoint or Camera.WorldToViewPoint(). This works well until the target leaves the screen area.
However, I guess that these return-values are wrong if the target has an specific position.

Here is my Testcase:


[Top View of the testfield]

I have set up a testfield with 8 targets on differnt positions. The targets are the red cubes and the player is in the middle and his view goes up to the north (world.z+). The Y-Position of each cube are the same.

You see, there are targets on the left and right side of the player-view direction. The relative Z-Distance from camera to target is 0.
In this case Camera.WorldToScreenPoint and Camera.WorldToViewPoint() return 0,0,0.

Here is the log with the return values:
WRONG LABEL: 2nd objScreenPos is objViewPos:


[Debug.Log() of scrren- and viewPosition of the target]
WRONG LABEL: 2nd objScreenPos is objViewPos:

Please help me to understand this behaviour.
I expect that the viewpoint of the right target (EAST) is something of x= >1f and y= 0.5f. Y=0.5f, because the target.y pos is on the same level like the player.y pos. The player should only rotate to the right side to get the target in view (Without having to pitch down or up).

Thank you for reading to this point. Iam happy to read you suggestions.

Edit: Manual workaround before the usage of Camera.WorldToXPoint()
Here is my Workaround:

Vector3 objWorldPos = this.myTargets[i].transform.position;

//---
// 2. Camera.WorldToXPoint() Bugfixes:     
float dotForward = Vector3.Dot(this.playerCamera.transform.forward, objWorldPos);
//Debug.Log("dotForward: " + dotForward);
if (dotForward == 0f) {
  // Obj is left or right of player forward view.
  // Translate object a little forward to avoid 0,0,0 return value of Camera.WorldToXPoint()
  //Debug.Log("Object is on Left/Right side of player forward view and will be moved temporary");
  objWorldPos.z += 0.01f;
}
if (Vector3.Dot(this.playerCamera.transform.up, objWorldPos) == 0f) {
  // Obj is top or down of player forward view.
  // Translate object a little up to avoid 0,0,0 return value of Camera.WorldToXPoint()
  //Debug.Log("Object is on top/bottom side of player forward view and will be moved temporary");
  objWorldPos.y += 0.01f;
}
if (dotForward > 0f && Vector3.Dot(this.playerCamera.transform.right, objWorldPos) == 0f) {
  // Obj is behind of player forward view.
  // Translate object a little to the right to get an angle where the arrow will look at (if objs is behind camera).
  //Debug.Log("Object is on back side of player forward view and will be moved temporary");
  objWorldPos.x += 0.01f;
}

After that workaround Camara.WorldToXPoint() does not return 0,0,0 anymore.

Those camera functions are probably just grinding your query world point through the camera’s internal projection matrix.

Here’s more of a discussion:

For any objects nearer than your camera’s near clip plane (or behind you), you should probably just test for those yourself as the matrix will break down as the Z offset approaches zero.

Say for example that your view frustum has a 90 degree field of view (and lets ignore height for the sake of this example), and no skew. That means the frustum goes 45 degrees to the left and 45 degrees to the right.

at 1z distance from the camera, the visible width slice of that z plane is 2 units (1 to the left, 1 to the right)
at 10z distance from the camera the visible width slice of that z plane is 20 unites (10 to the left, 10 to the right)
at 0z distance the size of your viewing frustum is just a single point. it has no area.

for all other z distances theres actual area that the local position can divide against.
so if the local position of a target is at {20,0,2}, and since the viewable plane only has a width of 4 at distance 2 the position it calculates for x is 20/2 (2 not 4 cause origin is from the center, not a corner) giving a view position of {10,0,2}. but for z distance 0 its a divide any number by 0 issue. It can’t judge how far away something is on that plane from a div/0 result and I would bet that it wouldn’t make a case specific check for performance reasons. so it just returns 0. The main purpose behind the matrix math is to be fast and efficient, not to be smart.

As @Kurt-Dekker basically said, I wouldn’t rely on WorldToScreenPoint for points inside the nearclip distance of that camera, as the positions start to skew wildly.

Hello Developers.
Thank you for your replies.

@kurt -Decker: Thank you for the information. To be clear: The FullscreenRadar should show arrows on screen for those objects, wo are currently not visible on the screen (Look at the first picture). The method “Camera.WorldToViewPoint” is better, because I will look for <0f or >1f. The Renderer-Component is also visible, if the object is NOT visible on screen to throw shadows in the camera picture. Also it is expensive to reference the Renderer-Component of an object in a loop. In addition the Renderer-Component does not give you the onscreen-position of an object.

@JoshuaMcKenzie : Iam sorry, but I dont understand your explanation. As a User of the API Camera.WorldToScreenPoint or Camera.WorldToViewPoint I expect correct values and not 0,0,0, if the object is on the left/right/top/down side of players view direction.

However, it looks like (also in Unity 2018) that I must translate thoose objects manually before I use Camera.WorldToXPoint().

Here is my Workaround:

Vector3 objWorldPos = this.myTargets[i].transform.position;

//---
// 2. Camera.WorldToXPoint() Bugfixes:  
float dotForward = Vector3.Dot(this.playerCamera.transform.forward, objWorldPos);
//Debug.Log("dotForward: " + dotForward);
if (dotForward == 0f) {
  // Obj is left or right of player forward view.
  // Translate object a little forward to avoid 0,0,0 return value of Camera.WorldToXPoint()
  //Debug.Log("Object is on Left/Right side of player forward view and will be moved temporary");
  objWorldPos.z += 0.01f;
}
if (Vector3.Dot(this.playerCamera.transform.up, objWorldPos) == 0f) {
  // Obj is top or down of player forward view.
  // Translate object a little up to avoid 0,0,0 return value of Camera.WorldToXPoint()
  //Debug.Log("Object is on top/bottom side of player forward view and will be moved temporary");
  objWorldPos.y += 0.01f;
}
if (dotForward > 0f && Vector3.Dot(this.playerCamera.transform.right, objWorldPos) == 0f) {
  // Obj is behind of player forward view.
  // Translate object a little to the right to get an angle where the arrow will look at (if objs is behind camera).
  //Debug.Log("Object is on back side of player forward view and will be moved temporary");
  objWorldPos.x += 0.01f;
}

After that workaround Camara.WorldToXPoint() does not return 0,0,0 anymore.