Monday, February 11, 2019

camera - 3D zooming technique to maintain the relative position of an object on screen


Is it possible to zoom to a certain point on screen by modifying the field of view and rotating the view of the camera as to keep that point/object in the same place on screen while zooming ? Changing the camera position is not allowed.


I projected the 3D pos of the object on screen and remembered it.



Then on each frame I calculate the direction to it in camera space and then I construct a rotation matrix to align this direction to Z axis (in cam space).


After this, I calculate the direction from the camera to the object in world space and transform this vector with the matrix I obtained earlier and then use this final vector as the camera's new direction.


And it's actually "kinda working", the problem is that it is more/less off than the camera's rotation before starting to zoom depending on the area you are trying to zoom in (larger error on edges/corners).


It looks acceptable, but I'm not settling for only this. Any suggestions/resources for doing this technique perfectly? If some of you want to explain the math in detail, be my guest, I can understand these things well.



Answer



Expanding on my comment, the basic approach for a single zoom-step would be:



  1. Project object on screen and remember coordinates

  2. Reduce fov

  3. Unproject remembered screen coords (raycast into view space)


  4. Rotate camera to align ray with the camera-to-object vector (in view space)


Step 4


When unprojecting the point using the inverse projection matrix, the ray will already be in view space (camera space) by default. To get the camera-to-object vector (let's call it toObj) in view space, it's easiest to transform the object position using the VIEW * MODEL transform. The camera is at [0, 0, 0] in view space, so that transformed vector is toObj.


The following assumes the Y-axis is up/down in view space. This may or may not be the case in your engine; verify this.


Get rotation that aligns ray with toObj:


ray = normalize(ray)
toObj = normalize(toObj)

angle = acos(dotProduct(ray, toObj)) // NOTE: may need to negate this

if (angle < THRESHOLD) exit // THRESHOLD is a small value, like 0.001
axis = normalize(crossProduct(ray, toObj))
rotationRay = { angle, axis }

This would be the rotation to rotate the camera with, but in many cases, we'd get a funky orientation because the camera will be rolling as well (rotating up vector around forward axis).


To fix this, we first determine the rotated up-vector to get the right-vector when considering toObj as the forward vector. Then we determine the desired right-vector and rotate around the toObj axis to align them (Using the right-vectors is just to save an extra calculation that would occur to get the desired up-vector):


upRotated = rotate(UP_VECTOR, rotationRay) // UP_VECTOR is (0, 1, 0)
right = normalize(crossProduct(upRotated, toObj))
rightDesired = crossProduct(UP_VECTOR, toObj)


angle = acos(dotProduct(right, rightDesired)) // NOTE: may need to negate this
rotationRoll = { angle, ray }

The last statement rotates around the ray axis, because we will do this transform after rotating the camera with rotationRay, so from the camera's point-of-view, ray will remain unchanged.


Now we have to combine these rotations and transform the combined rotation back to world space (since your camera transform is probably defined in world space).


// combine rotations with quaternion product
q = quaternion(rotationRoll) * quaternion(rotationRay)
rotationView = q.toAngleAxis()

// transform rotation to world space

axis = inverse(VIEW).transformNormal(rotationView.axis)
rotationWorld = { rotationView.angle, axis }

I've used a fictional transformNormal method to emphasize that the transform disregards translation and should strictly also be done with the transpose of the inverse of the matrix in case of scaling, but that probably doesn't occur.


Now you have the camera's rotation to perform in world space. If the camera already has an orientation, you can combine both rotations using the quaternion product like demonstrated earlier.


Keep in mind this was all written without testing or sketching anything, so maybe I messed something up along the way. Regardless, I hope it points you in the right direction. Good luck :)


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...