- Home /
With a perspective camera: Distance independent size gameobject
Hi!, I need to make a GameObject approach the camera with a custom screen-space size (i need a object to have a "max size" and stop growing after a set distance). Ideally, i'd want a simple formula so i could scale the object depending its distance from the camera. After hours of trying and searching I found this simple formula:
scale = focalLength / (focalLength + distance);
I tried applying this inversed so the object keeps its size, and this nearly works, but with not enough accuracy because i can't figure out what "focalLength" is exactly (at least this is my best guess, and certainly is not FOV).
Note that i'm struggling to wrap my head around this because i'm not comfortable around projection and transform matrices, so a noob-math friendly answer is mostly welcome.
Anyone can help me out with this or provide a better method?.
Thanks!
In your case, "focalLength" is just your reference distance from the camera, and "distance" is the deviation from that, it is independent from the camera's FoV. Note that both values are local-z-distances from your camera, i.e. the distance vectors projectoed onto camera.transform.forward.
Answer by Ted-Bigham · Apr 30, 2015 at 06:35 AM
Instead of figuring out the math, just pick how big you want it on screen and use the camera functions to "measure" it in world units.
public class LockToScreenSize : MonoBehaviour {
public float sizeOnScreen;
void Update () {
Vector3 a = Camera.main.WorldToScreenPoint(transform.position);
Vector3 b = new Vector3(a.x, a.y + sizeOnScreen, a.z);
Vector3 aa = Camera.main.ScreenToWorldPoint(a);
Vector3 bb = Camera.main.ScreenToWorldPoint(b);
transform.localScale = Vector3.one * (aa - bb).magnitude;
}
}
sizeOnScreen is what? In pixels? Because for me no matter what number i put it doesn't really change much.
Size on screen is in vertical pixels. I just tried this in 800x480 resolution with a sphere. It maintained size on screen perfectly as I moved the camera closer and further. I noticed when the edges of the sphere touch the top and bottom of the viewport, sizeOneScreen is about 420. So it's not an exact number of pixels (or it would be 480), but the size does stay surprisingly consistent.
Ah maybe its because $$anonymous$$e were sprites? When i change the pixels per unit it changed their size on screen but they do keep to a fixed size so it does work. Would of liked to have done via script rather than adjust the pixels per unit some how though.
Ahhh i see my mistake now. Thanks it works properly now. Wish i understood exactly what the code does i suck at maths and understanding of 3D graphics :P But at least i have it working!
All it's doing is creating a point (b) at a certain distance (sizeOnScreen) from the objects position on screen (a). Then it converts those screen points back to world points (aa) and (bb) and measures the distance (aa-bb). The distance is your new scale. I think that aa could be replaced with transform.position, but it's a little harder to read.
Question, for distance with perspective wouldn't you apply "sizeOnScreen" to the Z value given Z is the distance from the camera, such (0,0,20), so if camera was at (0,0,0) with no rotations added to it then that would be 20 units away from the camera? Where as your code applies it to Y which is 20 units up from camera?
Thats kinda whats confusing me at the moment :P
Answer by delstrega · Jun 17, 2012 at 09:02 AM
I don't exactly know what focal length is but googling revealed this relation between FOV and focal length: FOV = 2*arctan * (x / (2*focalLength)) (from http://kmp.bdimitrov.de/technology/fov.html)
The calculator on this page uses the following code to get the focal length f from a given FOV:
function calcf(fov, crop)
{
if (fov == "") return "";
if ((fov <= 0) || (fov >= 180)) return "error";
f = (x / (2 * Math.tan(Math.PI * fov / 360)));
return f / crop;
}
Maybe this is a pointer in the right direction! Hope that helps :)
Answer by Wolfram · Jun 17, 2012 at 09:09 PM
If you don't care about actual pixel sizes, you can ignore the camera's FoV - the perceived scale change of an object is just the quotient objectAtDistanceA/objectAtDistanceB
("distance" being the local z distance to the camera, i.e. the distance vector from camera to object projected onto camera.transform.forward, as opposed to just the distance vector).
If you want to know how big your object is, or want to make an object exactly x pixels large, so you can keep it at a certain pixel height, you can use the formula @DelStrega pointed out to help you compute that size. So the height in pixels of an object at z-distance "distance" is just:
objectPixelHeight=height/distance/(2*Mathf.Tan(Camera.main.fieldOfView/2*Mathf.Deg2Rad))*Camera.main.pixelHeight;
Note all this works correctly only for planar objects - as @Bunny83 pointed out, the perspective (and therefore the object's perceived size) changes if it has a depth.
Ok! I'm almost there!
First used this to get the "height" value:
size = renderer.bounds.size.y
Next, I used this to get the z distance and pixelHeight as you pointed out:
$$anonymous$$atrix4x4 m = Camera.main.worldToCamera$$anonymous$$atrix * $$anonymous$$atrix4x4.TRS( transform.position, transform.rotation, transform.localScale );
Vector3 cameraRelativePos = ***matrix conversion here***
float focalLength = 2 * $$anonymous$$athf.Tan( Camera.main.fov / 2 * $$anonymous$$athf.Deg2Rad );
pixelHeight = size / -cameraRelativePos.z / focalLength * Camera.main.pixelHeight;
Now, to "fix" the scale to a deter$$anonymous$$ed pixel size when it passes a threshold:
if ( pixelHeight > pixelLimit )
transform.localScale = Vector3.one * -cameraRelativePos.z * (pixelLimit / 50);
i'm a bit of lost here, this is working but is not FOV dependent, only works for 60 degrees. "50" is a value i discovered that fits to any pixelLimit for that fov, i can't find out where it is co$$anonymous$$g from. Curiously enough, FOV / FL = 50 for FOV 60, but doesn't scale well enough for other FOV values.
Any clues?
First, I would stay away from low-level $$anonymous$$atrix functions, they are usually not required in Unity, since there are many convenience functions around (for example, Camera.WorldToScreenPoint(), transform.TransformPoint(), and so on). In your example, I'd suggest Vector operations ins$$anonymous$$d, to find the z-distance:
float zDistance=Vector3.Dot(transform.position-Camera.main.transform.position,Camera.main.transform.forward);
A "less mathematical" approach would ignore all that, and just use camera.WorldToScreenPoint() on renderer.bounds.max.y and renderer.bounds.$$anonymous$$.y to directly get the y-size in pixels. But I haven't tested this.
@wolfram - can you help me out and rewrite this calculation:
objectPixelHeight=(height/(distance/(2*$$anonymous$$athf.Tan(Camera.main.fieldOfView/2*$$anonymous$$athf.Deg2Rad))))*Camera.main.pixelHeight;
To calculate the distance at which objectPixelheight == Camera.main.pixelHeight?
I think what I need is this:
distance = 2*$$anonymous$$athf.Tan(Camera.main.fieldOfView/2 * $$anonymous$$athf.Deg2Rad)/height;
which presumable says for a given height what is the distance? Or I could be totally wrong :)
EDIT: yep, totally wrong
I'm trying to workout how to get a perspective rendered object to fill the camera vertically (to create a billboard).
Almost - it's the reciprocal of that:
distance=WCheight/2/$$anonymous$$athf.Tan(Camera.main.fieldOfView/2 * $$anonymous$$athf.Deg2Rad)
Answer by Bunny83 · Jun 17, 2012 at 10:49 AM
Of course you can calculate the size at a certain distance, but it won't help much. A perspective camera always shows closer parts bigger. If you calculate the size for the objects center, it will still be bigger. The closest bounding rectangle the object would fit in is much more complicated to calculate.
You might want to use a second camera and draw it orthographic? Keep in mind to setup the render order (camera's depth parameter) and the second camera should have clearflags none.
I'm not sure if the depth buffer will work in this case. Usually gizmos are displayed anyway. If you want to draw it no matter if it would be covered by something else or not, just set the Clear flags to depth-only
Unfortunately i need the object to respect the z order so the 2nd camera solution is a no-no. I wrote i need a "gizmo-like" object because i thought this was the easier way to people to understand what i'm trying to do. I just want an object to "stop growing" when is near a specific threshold of distance from the camera (i'm maybe thinking something like the maxParticleSize property in Shuriken).
Answer by Nimrod Harel · Jul 01, 2014 at 09:36 AM
In case someone comes across it, this solution worked in my case: Camera can zoom in / out and be moved on x/y axis, and some objects (game dialog) must be opened at fixed size regardless of camera zoom.
If using orthographic camera, just use the orthographic size (assuming you start at 100), and use it as scale for the fixed size object (if not, you can easily convert it to % from 100 if object scale isn't 100 by default).
say camera at size 120 (farther from plane, object must be larger, and will be scaled to 120% of it's original size).
public void updateScale() {
float size = Camera.main.orthographicSize;
Vector3 scale = new Vector3 (size, size, size);
objectToScale.transform.localScale = scale;
}
Note that the "orthographic size" of a Camera in Unity is the HALF size. So if you set Camera.main.orthographicSize=120, an object needs to be twice that large (=240) to exactly fit the vertical height of the camera view.
Your answer
Follow this Question
Related Questions
Formula for the scale value of an object based on ortographic camera size? 1 Answer
how to scale a cameras FOV in real time. 1 Answer
Possible to use an oblique frustum camera and still be able to change the fov? 1 Answer
How do I get an off-center camera view, without breaking FOV & aspect ratio? 1 Answer
auto scale camera error when upgrading unity4.3 into 4.5 0 Answers