Calculate Quaternion upward ? to keep the rotation of camera

May be its the old problems, but I still deeply troubled by this for few days long.

I working on the project to control the camera follow the target(player), those objects without any hierarchy relationship. which mean independent objects in the scene.

let’s make it short, Imagine a spaceship to move around and camera following it. {Pic01}

when the camera on-top/at-bottom of that spaceship {pic02} camera will start fliping, it is because the upwards reference is based on world’s upwards {in this case Vector3.up}, at that point Quaternion cannot calculate the upward of camera, both x & z axis are zero. I call this zero-point, correct me because I didn’t know the name of this point.

therefore after camera get pass zero-point the camera will rotate 180 degree, but this is not the result that we want, we want it keep the camera’s head point to the ground{Vector3.down}

so here is the question, can we calculate the upward ??
in my program I already have following values

  • Polar, Elevation, Distance related to chasing target.
  • target forwards, target upwards

here is the video to show the real issue.

Video here. !!!

Video here. !!!

Video here. !!!

First off, I believe the key problem you’re encountering is Gimbal Lock, an incidental result of Euler Angles to calculate rotation (i.e. 0-360 degrees).

Essentially, there are multiple ways of representing the same rotation. Rotating 180 degrees on the Y axis can also be achieved by rotating 180 degrees on both the X and Z axes. Unity doesn’t know which is intended, since they both look the same to it and are fundamentally being calculated as Quaternion rotations first.

With that out of the way… If I follow your description correctly, you’re using transform.LookAt to follow the character. One simple approach to this would be to make use of the overloaded function transform.LookAt(Target, WorldUp). You could use something like

// C#
float orientationFix = Mathf.Abs(Vector3.Dot(targetPosition - cameraPosition, Vector3.Up));
Vector3 newUpVector = Vector3.Lerp(Vector3.up, Vector3.forward, orientationFix);
cameraObj.transform.LookAt(playerObj, newUpVector);

as a simple remedy to the problem. The use of Vector3.forward could be retooled based on the needs for your game.

That said, there are much more elegant and effective solutions to this problem (using much more thorough implementations of Quaternion rotations), but this should provide a smooth transition between perspectives, where the view would lean in and out based on the camera’s current position.

Edit: I apologize if my example doesn’t fit your orientations as intended, but I had a tough time following exactly how this is all being handled.

This is a fully working space-orientation orbit cam. When you don’t assign a target it acts like a freelook cam.

using UnityEngine;
using System.Collections;

public class OrbitCam : MonoBehaviour
    public Transform target;
    public float dist = 10f;
	void LateUpdate ()
        var r = transform.rotation;
        if (Input.GetMouseButton(0))
            r = Quaternion.AngleAxis(Input.GetAxis("Mouse X"), transform.up) * r;
            r = Quaternion.AngleAxis(-Input.GetAxis("Mouse Y"), transform.right) * r;
        r = Quaternion.AngleAxis(Input.GetAxis("Horizontal"), transform.forward) * r;
        transform.rotation = r;
        if (target != null)
            transform.position = target.position - transform.forward * dist;

Since you rotate around local axes you can already end up with any orientation even when you only rotate around two axes. That’s why i added the rotation around the forward axis as well. You might want to use a different input than Horizontal for that ^^. Very common keys are “e” and “q” (like in space engineers as far as i remember).