- Home /
Adjusting returned WorldToScreenPoint for perspective angled camera
I'm having a very difficult time getting a 3d world point projected onto an angled perspective camera. I'm making an rts game with the "typical" rts camera, that is, it's angled at about 45 degrees (actually 55 degrees in my case) looking down on the terrain and unit characters.
The problem is that due to the camera's angle, the projected point (the unit's pivot point in this case) onto screen space slowly gets more "off" as the unit approaches the edges of the screen. It doesn't matter what edge it approaches, top, down, left or right, the point will drift off-center relative to the unit from where it was in the center of the screen.
I did some testing, and found that WorldToScreenPoint (and WorldToViewportPoint) work just find IF (big if) I'm using an orthographic projection, or if I'm using a perspective projection with zero angle to it. Which in the latter case, the perspective camera would basically have to be on the ground to see anything worthwhile.
To me, this means the WorldToScreenPoint function works great, but I somehow have to do some math to account for the angle of the camera. What math or vector manipulation can I do to account for the angle of the camera?
The overall goal, is to get a unit's position in 3d world space, project it onto the screen, and have that point stay exactly the same relative to the unit no matter where the unit is on-screen.
If the goal is to lock the camera on the object, why not just moving the camera with the object? Whenever you move the object, move the camera as well.
Unfortunately that's not the goal though. The camera is controlled by the player from a top-down perspective by dragging their mouse to the edges of the screen (like in StarCraft II, Company of Heroes, Supreme Commander, etc). I don't need to lock the camera on the object. There could be 100 units visible on the screen, and I need to "track" all of them accurately in 2d screen-space. This will be for unit selection purposes :)
Answer by karma0413 · Oct 14, 2017 at 11:15 AM
I finally figured it out.... i was having a simiar problem....
Quick Description of my problem: I was using the Canvas to display a sprite ( a healthbar) which floats over all the characters. The problem however, was when i tried to obtain the WorldToScreenPoint it kept giving a result that was slightly off.... for example: the healthbar looked a little okay when the character was immediately in front of the camera... but as the character walks to the edge of the camera's fulstrum, the screens x,y placement becomes more and more incorrect.
Days and days of research and trying different combinations finally showed me that maybe there is a scaling issue which pointed me to look at Canvas / Canvas Scaler / scaling mode: scale with screen
Originally, this worked wonderfully when i had only 1 character and his healthbar stayed stuck to the top of the screen like old classic double dragon games. BUT when i made the decision to have many characters and they all need "floating healthbars", i didnt come back to re-evaluate whether this option needed to change.
Setting the canvas Scaler to: keep constant pixel size , fixes the problem and i now have the correct WORLDtoSCREENpoint that i needed! And now the healthbar floats beautifully above the characters...
BUT WAIT, ANOTHER PROBLEM! Now, if the screen resolution is small... the Ui sprite is obsurdly large..... and if the screen resolution is high definition then Ui sprite is way too small!
QUESTION: So how do i use the "scale with screen size" mode, but yet also still get back a correct WorldToScreenPoint?
ANSWER: you must take into consideration the overal scaling of the canvas when it is stretched to fit (whatever current resolution that you are using)
INSTEAD OF:
RectTransform myRect = GetComponent<RectTransform>();
Vector2 myPositionOnScreen = Camera.main.WorldToScreenPoint (myOwner);
myRect.anchoredPosition = myPositionOnScreen;
YOU CALCULATE THE OVERALL SCALE FACTOR LIKE THIS:
RectTransform myRect = GetComponent<RectTransform>();
Vector2 myPositionOnScreen = Camera.main.WorldToScreenPoint (myOwner);
Canvas copyOfMainCanvas = GameObject.Find ("Canvas").GetComponent <Canvas>();
float scaleFactor = copyOfMainCanvas.scaleFactor
Vector2 finalPosition = new Vector2 (myPositionOnScreen.x / scaleFactor , myPositionOnScreen.y / scaleFactor);
myRect.anchoredPosition = finalPosition;
If this helped anyone please log in to give me a thumbs up....
This was my problem too, thanks for the tip - constant pixel size. Ended up making a custom script to scale my RectTransform depending on screen size to replace the Canvas Scaling.
Answer by Tomer-Barkan · Oct 30, 2013 at 07:33 PM
You are wrong in your assumption that WorldToScreenPoint
is off in the far edges of the camera view. I tested, just to be sure, by attaching this script to a cube:
public class test : MonoBehaviour {
void OnGUI() {
Vector3 screenpos = Camera.main.WorldToScreenPoint(transform.position);
GUI.Box(new Rect(screenpos.x - 10, Screen.height - (screenpos.y + 10), 20, 20), "X");
}
}
The camera angles was set to 45, and different angles as well. And it doesn't matter how far away the cube is from the camera, or how close to the edge of the screen, the X on the screen always appears exactly where the cube appears in the camera.
So WorldToScreenPoint
already does all the calculations for you, and it gives you the exact place that the world position appears on the screen.
Hmm, that is odd. Were you using perspective projection tbkn? I actually have some screenshots showing what it does in my game. I'll link you the 3 screenshots I have.
Pic album: http://imgur.com/a/dJdVY
Especially note the box/point relative to the unit itself. It gets further off as the unit approaches the edge of the screen. In your test were you looking for little differences like that?
I see you're also answering about the same question in another "Answer" now, and you suggest using a plane as well. Perhaps I can glean some information from that.
WorldToScreenPoint
only translates one point to screen position, probably the object's position (normally the center).
That specific point is EXACTLY in the screen x/y (in pixels) that the method returns, but since the object is more than one pixel, an optical illusion could be created in a perspective view where it seems like that point is not in the center of the mesh.
But worry not, the differences are pretty small and this is normal behaviour in RTS games. You can see other related questions and they all use the same solution in the end - convert object's pivot point to screen position, and see if it is within the box that the user dragged.
http://answers.unity3d.com/questions/33901/accurate-rts-selection.html
tbkn, I appreciate the help. But you say "convert object's pivot point to screen position, and see if it is within the box that the user dragged". That is exactly what I'm trying to do. And actually, I already have a system/method by which I can select one point pretty accurately. But that's using actual world-space, and not screen space.
But the problem persists in that even one point (one single solitary point) when converted via WorldToScreenPoint(), will not stay the same relative to the unit as it moves around the screen (on a perspective angled camera). That's the whole problem.
If I am to solve this problem correctly, I need to compensate for the "optical illusion" as you put it. Even though it's not an optical illusion, it's what perspective and angle does to the point. I'm trying to correct the optical illusion.
P.S. I feel like I should add that I understand the WorldToScreenPoint() function works correctly. It does. It just doesn't account for some things afaik.
The "optical illusion" does not affect the single solitary point - the pivot. That pivot will always appear at the exact pixel that WorldToScreenPoint()
returns. So if you drag a box on the screen, and that box encompasses the pivot point, your algorithm should select that object just fine. If you want to write an algorithm that will select the object even if only it's edge was encompassed in the box, while the pivot point was not, then you'll have a hard time, you'll have to check more than one point (all the edge points of the object basically), and will probably suffer a performance hit as well. There's no magic formula to convert an entire object to screen position, it very much depends on the object's mesh.
If you really want, you can go the other way around - convert from screen position to world position, but it will yield the same result.
you can read my answer here to learn how to convert a screen point to a world point on a given plane, and you will then need to check if the object's world position is within the box created this way. But this is not recommended. Like I said, every RTS solution I found in unity all simply convert the object to screen and check whether it is within the box. That will yield a good enough and optimized result.
http://answers.unity3d.com/questions/167471/selecting-objects-inside-selection-box.html
Well, I'm not trying to be argumentative, but I disagree that the point will always appear at the exact pixel that WorldToScreenPoint() returns, because it simply doesn't. When using a perspective projection with a camera angle, the unit's pivot and the WorldToScreenPoint() "point" is never the same (unless your unit is in the exact middle of the screen). $$anonymous$$y pictures show this. And I'm seeing it in person. The pivot point and the projected point don't match up.
That being said, I really do appreciate your help. Especially with the trajectory function you posted :) I'm not going to give up on this though, I'm gonna keep working on it.
Answer by robertbu · Oct 27, 2013 at 05:12 PM
An easy way to solve this problem is to use Unity's mathematical Plane() class. Planes are defined by a normal and a point, so assuming the surface is the XZ plane passing through the origin, then your normal will be Vector3.up and the point can be Vector3.zero. Rather than Physics.Raycast() you use Plane.Raycast() to find the point. Note if you have a character walking on a surface, it is more accurate/easier to define the plane as it would pass through the character. So if your character was two units high with a pivot point one unit from the ground, you would Vector3(0,1,0) as the point for the plane. Here is a link to an example of a Plane Raycast:
http://forum.unity3d.com/threads/15802-Plane-Raycast-Example
Thanks for the answer robertbu. So I'm trying to picture what it is you're saying to do with the plane exactly. So if my camera is angled, would I always have to be getting the direction of my unit to the camera, and then normalizing it and using that as my plane's normal vector? So essentially, would I be creating a plane perfectly parallel with my main camera's viewport?
And separately, after doing Plane.Raycast() to find the point on the plane, would the next step then be to WorldToScreenPoint() that same point? I'm just trying to deter$$anonymous$$e where in the process the creation of the plane needs to happen and how the plane is supposed to be used.
Answer by mbzdmvp · Dec 09, 2016 at 11:12 PM
To anyone else coming across this year later, the correct answer is here: http://answers.unity3d.com/questions/566519/camerascreentoworldpoint-in-perspective.html
Just take the function listed by Tomer-Barkan and pass the screen position and distance you would normally pass to ScreenToWorldPoint.
Your answer
![](https://koobas.hobune.stream/wayback/20220613120728im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Unity camera troubles 1 Answer
Apply Projector-Realsense calibration to Unity3d Camera 0 Answers
How to get the forward vector normal to the camera's forward vector regardless of camera pitch 1 Answer
WorldToViewportPoint and WorldToScreenPoint give wrong positions when VR is enabled 1 Answer
WorldToScreenPoint, wrapping around when outside screen. 1 Answer