[SOLVED] Invisible triangles in orthographic projection? (Gimbal Lock)

Dear all, I have come encountered this problem a few times now without ever solving it. So maybe you can help me understand where the actual issue lies:

I have a very simple 3D project and would like to just display a single triangle with the BasicEffect class and a custom VertexDeclaration. However, when using an orthographic projection, the triangle becomes invisible as soon as I am looking from above. A tiny offset solves the problem.

The VertexDeclaration is as follows:

public readonly static VertexDeclaration VertexDeclaration
        = new VertexDeclaration(
            new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
            new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
            new VertexElement(sizeof(float) * 6, VertexElementFormat.Color, VertexElementUsage.Color, 0)
            );

My display vectors are:

View = Matrix.CreateLookAt(Position, Target, Vector3.Up);
Projection = Matrix.CreateOrthographic(Settings.ScreenWidth, Settings.ScreenHeight, Settings.NearPlane, Settings.FarPlane);

with Position = (0, 10, 0) and Target = (0, 0, 0).

The “Model” / triangle is defined as

VX.Add(new VertexPositionNormalColor(new Vector3(0, 0, 0), new Vector3(0, 1, 0), Color.DarkOrange));
VX.Add(new VertexPositionNormalColor(new Vector3(100, 0, 0), new Vector3(0, 1, 0), Color.DarkOrange));
VX.Add(new VertexPositionNormalColor(new Vector3(0, 0, 100), new Vector3(0, 1, 0), Color.DarkOrange));

The drawing routine is

// effect
Eff.World = Matrix.Identity;
Eff.Projection = Cam.Projection;
Eff.View = Cam.View;
Eff.VertexColorEnabled = true;

// draw
foreach (EffectPass pass in Eff.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.SetVertexBuffer(VB);
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, VX.Count);
}

The result is invisible. Only by adding a tiny tilt in the camera angle I can see the triangle as planned: Position = (0, 10, 0.1f).

Any ideas?

What are your nearplane farplane settings

Check if the view matrix is right. Position and Vector3.Up are parallel. This might produce a wrong view matrix.

this is the correct answer,

Use this instead
View = Matrix.CreateLookAt(Position, Target, Vector3.Forward);

Ya you have gimble lock thier is two ways to cause it that is one of them.
Alternately you can do the following check and switch temporarily.
Mind you this is pesudo code you might need to tweek it.

lets say we have a object in our world it could be any of them.
it has a matrix that describes its world position and orientation well call it Camera.
you move it around in your world like any other object maybe using the CreateWorldFunction( pos , target , up).

Now from this it is normally what you pass to the LookAtFunction
by LookAt( Camera.Translation, Camera.Forward + Camera.Translation , Vector3.Up);

Note the CreateWorld and CreateLookAt Target and Forward have differences

the vector3.Up is a system space vector not part of your camera’s local orientation
which is a problem that can create gimble lock

So you make a alteration
we put this our own vector 3 as the standard to use Up
typically it will be Vector3.Up to give us a stable fixed camera
however you may need to override it temporarily from time to time.
so well do this first.

Vector3 up = Vector3.Up;

how do we know when we need to override the Up to avoid a gimble lock ?

The dot of the forward and up will approach 1 or -1 as they become parallel

float result = Vector3.Dot(Camera.Forward, Vector3.Up);

in the case of a negative we simply flip the sign for testing this later on.

if( result < 0){ result = -result; } // abs

we test to see if the forward and the Vector3 are becoming parrellel.
if so the result will approach 1
if it actually reaches 1 or near that, we risk nan infinite or gimblelock

if( result > .98f)
{

what can we do ?
well one thing is certain, we sure don’t want to use that forward with that up.
if you have a world matrix for your camera.
you can grab the right or left and cross them to get a up.
i think cross forward and left is up if its not just use the right i forget off the top of my head.

Up = Vector3.Cross(Camera.Forward, Camera.Left);

Now you have a temporary alternative to the systems Vector3.Up
your camera will be in free float for a bit like a space sim momentarily
but its a quick fix a better fix is to lerp to the temporary up but this will do

ViewMatrix = Matrix.CreateLookAt(pos, target, up);

}
else
{

In the case there is no danger of gimble if the result was not near 1.
We just use the standard way of getting the look at.
It may also be wise to do something similar for your create world function.

ViewMatrix = Matrix.CreateLookAt(pos, target, Vector3.Up);
}

if your dot approaches 1 you just use your cameras up, which is the cameras cross (forward left) or its right i forget;
You could directly use your camera’s up but take care it might already be messed up.

Thanks @kosmonautgames, you are right (and so is @Kwyrky): The Vector3.Forward solved the issue.

However, I do not understand why this should result in a wrong view matrix when the Position-to-Target Vector is parallel to the CameraUpVector.

Thanks, I had to look up Gimbal Lock, but I think I got the idea.

Also check out how the view is calculated. I posted it here

Ill comment the original to clarify what it does.

However, I do not understand why this should result in a wrong view
matrix when the Position-to-Target Vector is parallel to the
CameraUpVector.

The reason you get gimble lock is as follows…

Within CreateLookAt( …)

The first thing it does is creates a inverted forward from the pos - target; and normalizes it.

That is the camera objects backwards vector. The next thing it does is creates a right from the up and forward. It does this by using the cross product of the two vector3’s the forward it just found from your target and position you passed and the up you passed. However if both of those vector 3’s point up because Vector3.Up is a system space up it never changes…

Then in truth you don’t have orthagonal orientations i.e.
forward = (0,1,0)
up = (0,1,0)
When you cross product that you get garbage basically 0’s
Because mathematically the cross requires at least partially orthogonal vectors to find the right.

So now what happens when you insert 0,0,0 in the Right vector of your matrix
the second zero is your m22 Y scalar, its going to scale every point and other matrixs it encounters to 0 y its also no longer unit length and over some iterations it will wind the entire matrix to all 0’s.

Remember that the View matrix is a inverse matrix…i.e. A Inverted World matrix.
Meaning if you have a camera object at some position in your world. Which could be any of your world objects. Its just that a object in the world that has a orientation matrix until you use its values to create some viewmatrix.

Note
This can also occur with your world matrix and even using quaternions it can occur as well.

So the simple proper fix is…
As the forward approaches the Vector3.Up or the Vector3.Down to simply use the actual free floating Camera.Up within your current world orientation matrix. E.G. the world orientations you are using as the camera to create the viewmatrix.
That bit of code basically temporarily changes your camera to a free look camera in truth it should lerp as it approaches 1 so you dont get a jerking feeling.
The > .98f value is arbitrarily picked you can tweek that value lower or slightly higher the closer to 1 though it gets, the more chance you risk of a really fast spin round or gimble lock anyways when you look up.
If you are not keeping a camera matrix, you should be keeping at least one other value to orient your camera object other then the system vector3.Up. You use this to find a right and forward then create a Up to pass to your lookat function.
I keep a world matrix on all objects to use as a camera to create a view from any of them.

Ya those functions are correct the problem is that the forward and target in CreateWorld and CreateLookAt are not to be thought of as the same, and if you don’t realize that, it will cause you hell.

Okay, I think I got it. Thanks for going through the pain to explain it in such detail, it’s highly appreciated.