WorldToScreenPoint bug returning huge numbers?

I’m getting a strange error with WorldToScreenPoint where it returns really high numbers then goes negative from a 0.1 change in the targets Y position.

The returns go from (-58046110.0, -28027160.0, 0.0) to (10813970.0, 5221314.0, 0.0) when i go from -176.6 to -176.7. The numbers around the -176 range just make a bunch of way too big of numbers.

Here is a code snippet that reproduces the issue:

using UnityEngine;

public class WorldToScreenPointTest : MonoBehaviour
{
    public void Start()
    {
        Camera.main.transform.rotation = Quaternion.Euler(new Vector3(75, 25, 0));
        Camera.main.transform.position = new Vector3(0, 20, 0);

        var locationA = new Vector3(-176.6f, 0, 0);
        var bigNegativeNumber = Camera.main.WorldToScreenPoint(locationA);

        var locationB = new Vector3(-176.7f, 0, 0);
        var bigPositiveNumber = Camera.main.WorldToScreenPoint(locationB);
    }
}

Looks like float overflow. When a number gets so big (or small) that it overflows the buffer and wraps around to the other end of the number range. So, very negative wraps around and becomes very large if you keep subtracting.

I can’t say exactly the math involved in WorldToScreenPoint, but because of the camera frustrum it’s not surprising that linear increases (or decreases) in the horizontal position would result in exponential increases in the screen position. I think you’re just going way too far off screen.

Are you asking just because this was an interesting find? Or is there actual real work you’re doing this far off screen?

1 Like

I agree with dgoyette. But I wonder, are you instead wanting to call Screen To World Point? If so, then remember to give a Z value other than zero (otherwise you just get the camera position).

var locationA = new Vector3(-176.6f, 0, Camera.main.nearClipPlane);
var Pos1 = Camera.main.ScreenToWorldPoint(locationA);
Debug.Log(Pos1);

var locationB = new Vector3(-176.7f, 0, Camera.main.nearClipPlane);
var Pos2 = Camera.main.ScreenToWorldPoint(locationB);
Debug.Log(Pos2);

The reason I’m using it is for making an offscreen marker to the “safe zone” in my game. I get the world to screen point then clip it to the screen width/height in order to render the marker onto the canvas. When it hits this magic number of -176 the marker then points in the wrong direction since it goes to a positive value rather than negative.

The x value of -176 doesn’t seem too big to me though? Maybe its just a mixture of the Y height of the camera along with the rotation that makes it break? ScreenToWorld point returns numbers like -28027160 with -176 on the Z but will return something reasonable like -2000 for a value of -170 (which lines up with what @dgoyette is saying about it increasing exponentially). It’s a top down game, I’m surprised I’m the only one getting this issue.

Hmm. I use basically the same approach in my game for showing the direction that a damage source came from:

targetScreenPos = camera.WorldToScreenPoint(target3DPosition);

Though it’s entirely likely that I’m never 200 or so units away from the source of damage, or maybe I’d be running into this as well.

One thing you could try is to assume that any target further than a certain distance (let’s say 50 units) should be clamped to being 50 units away. So, for example:

void FindScreenPos(Vector3 actualPosition) {
    Vector3 effectivePosition = actualPosition;
    if (Vector3.Distance(effectivePosition , camera.main.position) > 50) {
        effectivePosition  = camera.main.position + (effectivePosition - camera.main.position).ClampMagnitude(50);
    }

    // Then do the calculation against effectivePosition
}

I might have some errors in that code, but the basic idea is that if actualPosition is too far from the camera, you find a point on the same line between camera and actualPosition, and use that instead. I haven’t tried it out, but it might allow you to keep things mainly on with a reasonable distance from the camera.

Or maybe there’s a much better approach which I just don’t know about yet.

2 Likes

That’s a good idea. I’ll go that route and move on :slight_smile:

I’m curious if you get the same issue. The Camera position/rotation has a big affect on the numbers so you probably would see the bug at a different number. It just so happens that the rotation I have the camera at causes -176 to be the magic number that it breaks at.

Floats don’t overflow in the same way that integers do. A float that gets too large in either direction will stop at Infinity or -Infinity. Similarly, a float that gets too small (in the decimal range) will “underflow” to zero or a denormal/subnormal number.

Personally, I’d probably calculate the direction to the marker in world space and then convert the world space directions into screen space.

2 Likes

I ended up going this sort of route. I got the ScreenToWorldspace for the camera and calculated the location and distance of the offscreen marker from this. I did the Vector3.ClampMagnitude that @dgoyette recommended with it as well and everything seems to work correctly now.

I still feel like there is a bug i the WorldToScreenSpace when the Camera position/rotation is set like I have it.

Thanks for all the help!

1 Like

Any news from 2021 on this problem?
I move my screen ~one screen height to the top and go from 1300 to 1600. Next screen movement takes me to 15k on x axis. Next one goes to 69k and snaps to -1870676

In my case changed my canvas setting solved this issue, I use screen space camera instead for overlay.
Someone who is searching for this issue may have to checkout your canvas setting.

Since the thread was already bumped: WorldToScreenPoint only works reliable as long as the position is inside the view frustum. If it’s outside the view, there’s no meaningful linear mapping to the screen. So you may want to check if the point is actually inside the camera frustum.

Of course it depends on what you need that position for. Most people want to place 2d GUI markers for worldspace objects. There are many different concepts how to actually place markers for things that are off-screen. So it highly depends on what behaviour you want.

3 Likes

I’m also having this issue. Many tutorials use the method of WorldToScreenPoint, but for me once the object has traveled a certain distance from the camera I get the overflowing behavior (which ends up flipping my screen markers to the opposite of where they should be).

This tutorial uses it.

As well as this one:

So I’m wondering, is it a bug in the engine? Have these tutorial authors just not had an object move past a certain distance? Been stuck on this one for awhile now.

1 Like

Almost certainly not. Usually people just have a bug in their code or they’re not using the API correctly.

But in any case, please do not necro-post.

If you have an issue, please start a new post… it’s FREE!

When you post, here is how to report your problem productively in the Unity3D forums:

http://plbm.com/?p=220

This is the bare minimum of information to report:

  • what you want
  • what you tried
  • what you expected to happen
  • what actually happened, especially any errors you see
    - links to documentation you used to cross-check your work (CRITICAL!!!)

If you post a code snippet, ALWAYS USE CODE TAGS:

How to use code tags: https://discussions.unity.com/t/481379

If you have no idea what your code is doing, fix that first. It’s not optional.

You must find a way to get the information you need in order to reason about what the problem is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

IF you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

When in doubt, print it out!™

Note: the print() function is an alias for Debug.Log() provided by the MonoBehaviour class.

1 Like

You quoted two tutorials. So you have watched them, right? In the first one at time 02:20 he shows where he uses WorldToScreenPoint. Do you see the code the follows it? I mean the part where he checks the z position if the point is in front or behind of the camera? Because if it’s behind the camera things would be flipped. He even has a comment about that in the code. The camera has a view frustum which is a rectangular cone forward. Only positions inside that frustum are translated correctly to screen coordinates. Anything outside is still translated, but due to the nature of the coordinate space, things get more and more stretched out the closer a position get to 90° off the camera’s forward axis. Once the object is behind the camera, you essentially have an inverted cone backwards. Just think about any ray that goes from the camera to a point in front of the camera. Trace that ray backwards behind the camera. This would be the same screen position. So a position at the top right of the screen is the same as a position behind the camera at the bottom left. That’s why he multiplies the position by “-1” to flip both axis around.

2 Likes

Sorry, the last post was from Nov 7th, less than a month ago, so I thought I was okay to keep the topic alive… (especially since there have been varying answers that don’t seem to directly answer the “overflow-like” behavior, other than Bunny’s statement that you shouldn’t use WorldToScreenPoint for offscreen objects - more on that below).

I’m pretty pumped people answered though, and so fast!

It actually seems like the issue is when the camera hits a certain distance traveled (in the x or z axis). I noticed this because the overflow-like behavior was happening on two different points that I was tracking at the same time, though they were different distances away from the camera.

Bunny, you said " Since the thread was already bumped: WorldToScreenPoint only works reliable as long as the position is inside the view frustum."

If that is the case, then am I misunderstanding the statement? Or should these tutorials not be using this method at all, since they are for explicitly keeping track of objects NOT in the camera view? That’s how I first understood your post, but then found only tutorials that seemed to use WorldToScreenPoint at some point during the process.

My game is 3d, but the game-play is only on the x and z axis-es. (So I guess 2.5d technically). Perhaps I’ve translated some of the code wrong, but to be honest I tried doing this before I looked at these tutorials and had the same issue (everything works great until the player (camera) has traveled a certain distance, then WorldToScreen numbers exhibit overflow-like behavior and are flipped). I was using a method of clamping the pointers to the screen size.

Anyway, I’ll spend some time and see if I can get a small reproducible sample. But I still am curious about if I’m understanding your statement correctly Bunny.

This was the key for me. The game in the first tutorial is fully 3d, so the issue of an object being behind that camera’s frustum makes perfect sense.

In my case, the objects are NEVER technically (or maybe in this case theoretically? - based on if you trust the math in the calculation or not) behind the camera (as it’s 2.5d and they should always be in front). HOWEVER, I suppose that once, like you mentioned, they get close enough to 90° from the forward axis of the camera, they are calculated as behind. I noticed in the logs that once the Z value went to negative in the WorldToScreenPoint calculation, I was seeing my issue.

In the second tutorial, the game is fully 2D so perhaps that is why this issue doesn’t show up? I’d have to revisit that tutorial to see if there is any correction going on.

Bunny thank you for the in depth explanation. I must admit I had trouble visualizing and understanding until I made my reproduction project and was able to reproduce. Now that I re-read your post and had a working knowledge of what to look for in the logs, I’ve found my solution. The solution, for anyone who runs into something like this, was to (like Bunny mentioned) check if the z goes negative on that WorldToScreenPoint calculation and multiply the resulting value by -1 to flip the vector3 (return the values back to as if you’re looking forward and not backward from the camera).

Vector3 currentScreenPoint = Camera.main.WorldToScreenPoint(transform.position);

if (currentScreenPoint.z < 0)
{
   currentScreenPoint *= -1;
}

Vector3 targetScreenPoint = new Vector3(Mathf.Clamp(currentScreenPoint.x, 0, _screenWidth), Mathf.Clamp(currentScreenPoint.y, 0, _screenHeight), 0);

_uiNavHelper.transform.position = targetScreenPoint;
1 Like

Well, that depends on a few factors. First of all what kind of camera you’re using (orthographic or perspective). The other thing is 2.5d is technically still 3d. You usually call it that way when the movement is restricted to a 2d plane. It’s true that things could usually not be behind the camera when the camera is perfectly parallel to the 2d plane where the objects are located in. However when the camera is tilted, things can be actually behind it, not just theoretically ^^.

For orthographic cameras you don’t get this projected point at infinite when it’s 90° off the view direction since an orthographic view is a linear space. Perspecive cameras do not. As you may know they always distort things the closer you get to the edge of the screen. With a moderate field of view this is barely noticable. However you get an idea when you use a higer fov. The coordinate space extends similarly outside the view but gets more and more distorted / stretched and ultimatively to infinite at which point the space essentially flips around.

1 Like

Interesting. I was wondering if this issue would be present with an ortho cam (and guessed it wouldn’t, thanks for confirming).

Yes you’ve described what seems to be happening. I’ve got the movement happening on a 2D plane, but my camera is rotated by 60°. I can see where, given the distortion at extreme lengths combined with the camera tilt, the objects could actually be behind the camera frustum.

Thank you Bunny for the info and explanations, they’ve been very enlightening .