How to have a flat sprite in a 3D environment?

Thanks a lot!

I have implemented an FPS camera style according to your advice, and I calculate the direction of the camera to deal with the movements:

Example:

        if (Keyboard.GetState().IsKeyDown(Keys.Z))  // forward
        {
            Matrix forwardMovement = Matrix.CreateRotationY(camDirection);
            Vector3 v = new Vector3(0, 0, -speed);
            cameraPosition.Z += v.Z;
            cameraPosition.X += v.X;
        }

And my view is now:

        Matrix rotationMatrix = Matrix.CreateRotationY(camDirection);
        Vector3 forwardNormal = rotationMatrix.Forward;

        view = Matrix.CreateLookAt(
            cameraPosition,
            cameraPosition + (forwardNormal * 10f),
            Vector3.Up
            );

I have fixed vertices and texture coordinates:

        quadVertices[0].Position = new Vector3(-1, -1, 0);  // Lower left
        quadVertices[1].Position = new Vector3(-1, 1, 0);   // Upper left
        quadVertices[2].Position = new Vector3(1, -1, 0);   // Lower right
        quadVertices[3].Position = new Vector3(1, 1, 0);    // Upper right

        quadVertices[0].TextureCoordinate = new Vector2(0, 1);
        quadVertices[1].TextureCoordinate = new Vector2(0, 0);
        quadVertices[2].TextureCoordinate = new Vector2(1, 1);
        quadVertices[3].TextureCoordinate = new Vector2(1, 0);

Then I removed the culling:

        RasterizerState rasterizerState = new RasterizerState();
        rasterizerState.CullMode = CullMode.None;
        graphics.GraphicsDevice.RasterizerState = rasterizerState;

And my quad appears (with the billboard applied as world) !

And I can note that it is reversed (My picture is mirrored). It explains why it was not visible with the culling. Why?

If the word is Matrix.Identity, the quad is on the right side, but with the Billboard matrix, wrong side…

Here is my code for the billboard:

mySpriteEffect.World = Matrix.CreateBillboard(Vector3.Zero, cameraPosition, Vector3.Up, null);

I tried to tink the 4th parameter (cameraForwardVector) but it seems ignored.

This is the last step I believe. I’m wondering what I’m doing wrong.

Edit: I tried to use CreateBillboard on a 3D model, and same result, I then see the back of the model…

CreateBillboard interally negates the forward vector. Which means the entire world matrix for the billboarded world object is spun around towards the camera. Also this method really expects all the fields to be inputed, except in simple cases. It also manually normalizes the forward you pass it. So it takes a direction.
One way to handle this is to just use your inverted texture coordinates or put a bool on your create quad that lets you flip the uv to whichever way you want it.

            // raw usage.
            var billboardPos = new Vector3(0, 0, -5f);
            var cameraPos = Vector3.Zero;
            var up = Vector3.Up;
            var forward = billboardPos - cameraPos;
            // this forward one passed ends up being negated so it will be -forward or backward.
            // this means the quad will be fliped around towards you.
            // that can affect the culling direction of the quad.
            Matrix objsBillboardMatrix = Matrix.CreateBillboard(billboardPos, cameraPos, up, forward);

           // To demonstrate in a gif.
           // How i would use it practically to ensure that in all rotations it will work.
           // If that is indeed desired here i task the billboards up to be the cameras up.

            var billboardPos = wObjRandomthing.Position + new Vector3(1f,0f,0f);
            var cameraPos = wObjCamera.Position;
            var up = wObjCamera.World.Up;
            var forward = billboardPos - cameraPos;
            Matrix objsBillboardMatrix = Matrix.CreateBillboard(billboardPos, cameraPos, up, forward);
            // set this objects world matrix to be a billboard.
            wObjRandomthing.world = objsBillboardMatrix;

            Helpers.DrawQuad(GraphicsDevice, effect, viewSpaceCameraTransformer.GetWorldViewProjection(wObjRandomthing), textureOrient);

In the below image you can see that while both arrows in the world are undergoing severe rotations and position changes in all axis in relation to the camera, the one arrow texture stays billboarded to the camera.
It’s not billboarded to some terrain’s up, ( and camera to object positions, forward) (which in your case you would probably prefer to do) you would use the terrains up, to make it keep that upwards orientation, in the case of the camera rolling.

Thanks!

I’m still confused, I still do not understand why it’s not “facing” the camera :(.
I don’t understand when you write “Which means the entire world matrix for the billboarded world object is spun around towards the camera”. Does it mean it’s normal that the result show the back of the quad? Does not sound like the behavior expected by the function. Is my quad wrong? But I got the same result with a 3D model…

I tried your code for calculating the forward and the result is the same (forward = 0,0,-10), I see the back of my quad. If I pass the inverted value of forward (forward = 0,0,10)… no change. It seems the forward parameter do not change the orientation.

The only way I found is:

        mySpriteEffect.World = Matrix.CreateBillboard(Vector3.Zero, cameraPosition, Vector3.Up, forward);
        mySpriteEffect.World *= Matrix.CreateRotationY(MathHelper.Pi);

EDIT : Ignore this last workaround :slight_smile: it’s working only if the quad is at 0,0,0 of course…

But I feel like I workaround something I do wrong on the first place.

Ok sorry i was thinking you could invert the forward to fix that well.

Does it mean it’s normal that the result show the back of the quad?

Normally id say no in fact im not sure why the function is negating the forward direction honestly i would do this myself with create world. Either way it’s sort of trivial for a billboard since you want the quads rotation locked to the cameras backwards then again the CreateLookAt swaps the forward as well.

I wouldn’t worry too much about the texture being off though just flip the u or v or both.
Just keep a seperate quad for this specifically.
I build a quad just for billboarding and then adjust the uv’s (u,v) as needed.

If you want to flip a texure horizontally or vertically…
just flip the whole columns x or y from 0 to 1 or 1 to 0.
for example. To flip this around so its upside down.

change this (u,v)

  quadVertices[0].TextureCoordinate = new Vector2(0, 1);
  quadVertices[1].TextureCoordinate = new Vector2(0, 0);
  quadVertices[2].TextureCoordinate = new Vector2(1, 1);
  quadVertices[3].TextureCoordinate = new Vector2(1, 0);

to this

    quadVertices[0].TextureCoordinate = new Vector2(0, 0);
    quadVertices[1].TextureCoordinate = new Vector2(0, 1);
    quadVertices[2].TextureCoordinate = new Vector2(1, 0);
    quadVertices[3].TextureCoordinate = new Vector2(1, 1);

if you had instead wanted to flip it so its left was its right.
change this (u,v)

  quadVertices[0].TextureCoordinate = new Vector2(0, 1);
  quadVertices[1].TextureCoordinate = new Vector2(0, 0);
  quadVertices[2].TextureCoordinate = new Vector2(1, 1);
  quadVertices[3].TextureCoordinate = new Vector2(1, 0);

to this.

  quadVertices[0].TextureCoordinate = new Vector2(1, 1);
  quadVertices[1].TextureCoordinate = new Vector2(1, 0);
  quadVertices[2].TextureCoordinate = new Vector2(0, 1);
  quadVertices[3].TextureCoordinate = new Vector2(0, 0);

The Matrix.CreateBillboard is like CreateWorld in that it gives you a world matrix but like CreateLookAt.
When the camera rotates or moves around in your world, you need to update these billboards.

It just takes a bit of practice.

Thank you :slight_smile:
Inverting the texture means I need to keep the culling off. And I guess it’s not performance friendly right?

Perhaps I should do the math by myself, calculate the right angle and rotate the quad accordingly.

Inverting the texture means I need to keep the culling off. And I guess it’s not performance friendly right?

You have plenty of ways to deal with culling.
You can switch the culling direction order on the graphics device before you draw.
(not performance freindly to switch state, but there is nothing to cull its a billboard)
You can change the actual vertice order passed i.e. the order of the vertices you make the quad from.
(zero penaltys)
You can use a vertex position normal texture structure and assign the normal direction yourself per vertice to point in either direction forward or backwards. (only penalty is its 12 bytes bigger)

The way culling works is like so you have a triangle like this.
A
|_\B
C

If you make the triangle vertice position A B C for vertice [0] [1] [2]
That is a clockwise triangle it has a clockwise winding order.

If you send in the vertices in the opposite order C B A for vertice [0] [[1] [2]
Then you get a counter clockwise triangle.

The culling you set on the graphics device has the options for both clockwise and counterclockwise as well as none.

The way these windings work is that they relate to the direction of a surface normal extending out from a triangle.
A cross product creates a normal from the found from 3 vertices, the order that you send them in to be crossed creates either a forward or backward surface normal to the perpendicular of the plane of the that the triangle lies upon, (the plane that all three vertices form in space).

Perhaps I should do the math by myself, calculate the right angle and rotate the quad accordingly.

Once you fully understand how the matrix parameters are interacting with the quad.
You will start to more clearly see how to set a matrix yourself without using createbillboard at all.

Ouaw… OK. Lot of concepts to digest when you deal with 3D, I need to improve myself. I’m impressed by your knowledge.

My need is to display creatures in a dungeon, and be sure that the quad is facing the camera when the player encounter one of them.

I try to have a code very simple.

I tested Matrix.CreateLookAt considering the quad is the camera, and the camera is the target, but cannot get something from that, the rotation was inverted…

Here is my try:

I just rotate the quad according to the cam rotation, then I move the quad to its position.

        mySpriteEffect.World = Matrix.Identity;
        mySpriteEffect.World *= Matrix.CreateFromAxisAngle(Vector3.UnitY, camDirection);
        mySpriteEffect.World *= Matrix.CreateTranslation(quadPosition);

I did a test and it seems to work. What do you think?

Im going to make a little image and post it so you can visualize it.

Your getting warmer but createfromaxis angle would need to use the terrains up. For the first term the second term in that function is just the radians of spin around that first angle so it may work but its not very flexible and is easily broken.

Hopefully i wrote that out right im pretty tired.

Amazing.
By the way, I looked at CreateBillboard code and the forward vector is used only when the distance is very closed and a round error can occurs:

        float num = vector.LengthSquared();
        if (num < 0.0001f)
        {
            vector = cameraForwardVector.HasValue ? -cameraForwardVector.Value : Vector3.Forward;
        }
        else
        {
            Vector3.Multiply(ref vector, (float)(1f / ((float)Math.Sqrt((double)num))), out vector);
        }

If I am not mistaken, CreateBillboard() assumes a quad with a normal that’s facing forward.
So you might want to test it with a camera positioned forward at (0,0,-10) that looks back at (0,0,0). Then swap your vertices left-right or rotate it 180.

That’s an interesting trail!

I assume that Monogame is using a right hand coordinates system:

http://i.imgur.com/AXMtUaI.gif

So my camera seems correct right?

I thought about the quad being facing the wrong way, but if I do not apply the billboard, my quad is oriented to the camera and the texture is correct. I don’t understand why I need to invert/rotate something being at the right place :frowning:

I found this:

https://social.msdn.microsoft.com/Forums/sqlserver/en-US/5de82565-3f54-4d96-9d0d-f0b4fc4a9b87/matrixcreatebillboard?forum=xnaframework

The user explained how he compared manual calculation with CreateBillboard and noticed that the Right.Z is negated. I’ll make a try as soon as I’m on my computer (I’m on my phone and literally obsessed about this :)).

EDIT:
It works! Inverting the Right vector fix the front/behind issue. I can now have the culling on.

Here is my code:

        Matrix billMatrix = Matrix.CreateBillboard(quadPosition, cameraPosition, Vector3.Up, pForward);
        billMatrix.Right = -billMatrix.Right;
        mySpriteEffect.World = billMatrix;
        mySpriteEffect.Texture = pTexture;

Should I do something with the normals of my quad instead of this? Like in this sample : https://github.com/CartBlanche/MonoGame-Samples/blob/master/TexturedQuad/Quad.cs ?

You had your camera facing origin from [0,0,10] (= [0,0,10Backward] or [0,0,-10Forward]) and your quad normal pointing backward.

if you rotate everything by 180 you will still be able to see it with you culling ON.
This time the camera facing origin from [0,0,-10] (= [0,0,-10Backward] or [0,0,10Forward]) and your quad normal pointing Forward.

Either way will work.
you can either (1) rotate 180 your quad with a matrix (rotQuad180Matrix * billMatrix ) which is cleaner code but slower,
or (2) negate billMatrix.Right which will have the same result as the rotQuad180Matrix,
or (3) hardcode your vertices to face forward instead of backward.

personally I prefer (3).
I think that’s also the standard with 3D models, you have to place the camera in-front of a model looking back at [0,0,0], that is if you want to see it face on. If you put the camera behind you will see the back of the model.

Well not to rain on the parade but since he’s having fun with this.
I should point out though i sort of hinted at the fact but didn’t directly elaborate on it, that you will soon run into another problem if you encounter a special case scenario.

Now that you have all this in your mind fresh its probably good to bring it up, there are two types of billboarding.

The one i showed in the gif is like completely billboarded no matter what that quad is billboarded but this isn’t desirable most of the time.
The one i described in the illustration is not reliant on the camera up.

Here’s a scenario to imagine.

Lets say you take your camera with you on a plane and fly over your virtual world. Imagine you are directly above a tree looking down.

If that tree is billboarded to the cameras up and not the terrains up what will you see?
2)
If it is billboarded to the terrains up what will you see?

In both cases you will see a messed up tree in the first it will look like the tree fell over on the ground. In the second case it will appear as thin line as you fly directly over it and look down from your plane.

What is normally done is this second case billboard most of the time, but for each tree object you have two billboards. One is for that special case which is the other type of billboarding. Or its another second case billboard were its forward is the terrains up its up is the terrains forward. That only starts to become visible as your cameras forward and the tree’s up when Dot(c.Forward, t.Up) start to return a result near 1.0f.

1 Like

This thread is fun, and I hope it is helpful for the community too, as a lot of information regarding billboarding is in there :slight_smile:

I’m in!

Here is my try (but not working):

        quadVertices = new VertexPositionNormalTexture[4];

        quadVertices[0].Position = new Vector3(-1, -1, 0);  // Lower left
        quadVertices[1].Position = new Vector3(-1, 1, 0);   // Upper left
        quadVertices[2].Position = new Vector3(1, -1, 0);   // Lower right
        quadVertices[3].Position = new Vector3(1, 1, 0);    // Upper right
        for (int i = 0; i < 4; i++)
        {
            quadVertices[i].Normal = Vector3.Forward;
        }

        quadVertices[0].TextureCoordinate = new Vector2(0, 1);
        quadVertices[1].TextureCoordinate = new Vector2(0, 0);
        quadVertices[2].TextureCoordinate = new Vector2(1, 1);
        quadVertices[3].TextureCoordinate = new Vector2(1, 0);

        Matrix billMatrix = Matrix.CreateBillboard(quadPosition, cameraPosition, Vector3.Up, pForward);
        //billMatrix.Right = -billMatrix.Right;

        mySpriteEffect.World = billMatrix;
        mySpriteEffect.Texture = pTexture;

        // No need for this, but it will work with advanced effect with multiple passes
        foreach (var pass in mySpriteEffect.CurrentTechnique.Passes)
        {
            pass.Apply();

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTexture>(
                PrimitiveType.TriangleStrip,
                pQuad,
                0,
                2);
        }
    }

I tried with backward and forward, but no change.

Shouldn’t this be lower-left → upper-left → upper-right → bottom-right since you’re using a triangle-strip? LL, UL, LR, UR would spit out a bow-tie I’d think since the last edge is LL and LR, making the next triangle LR, LL, UR.

Not 100% certain since I don’t touch those old pit functions. Pretty sure Intel’s still convert those to indexed triangle lists in software on the driver.

Edit: Nope, my bad, I’m wrong. BC edge not CA edge for the next triangle edge.

Removed using wrong account : - D

I’ll jump in to this interesting thread : - D My Billboard implementation which for my 2.5D games ^_^y
To get the final entity billboard WORLD you should multiply the SCALE * ROTATION * BILLMATRIX and compute the billboard forward by using the camera final world matrix, camera.World.Forward

MY RULES :

  1. Billboard cannot resize on Z Axis
  2. Billboard can only rotate at Z Axis
  3. You pass the final camera.World.Forward when creating the Billboard
  4. Compute only when necessary __IsTo* and multiply by using Matrix with ref
  5. For a 2.5D games limit the camera when panning, but otherwise use willmote implementation

THE VERTICES:

            #region VERTICES
            //!!
            //!

            __VerticesPNT = new Z3D.Core.VertexPNT[4];
            //
            __VerticesPNT[0].Position.X  =  1.0f;
            __VerticesPNT[0].Position.Y  =  1.0f;
            __VerticesPNT[0].Position.Z  = -1.0f;
            __VerticesPNT[0].TextureUV.X =  0.0f;
            __VerticesPNT[0].TextureUV.Y =  0.0f;
            //
            __VerticesPNT[1].Position.X  = -1.0f;
            __VerticesPNT[1].Position.Y  =  1.0f;
            __VerticesPNT[1].Position.Z  = -1.0f;
            __VerticesPNT[1].TextureUV.X =  1.0f;
            __VerticesPNT[1].TextureUV.Y =  0.0f;
            //
            __VerticesPNT[2].Position.X  =  1.0f;
            __VerticesPNT[2].Position.Y  = -1.0f;
            __VerticesPNT[2].Position.Z  = -1.0f;
            __VerticesPNT[2].TextureUV.X =  0.0f;
            __VerticesPNT[2].TextureUV.Y =  1.0f;
            //
            __VerticesPNT[3].Position.X  = -1.0f;
            __VerticesPNT[3].Position.Y  = -1.0f;
            __VerticesPNT[3].Position.Z  = -1.0f;
            __VerticesPNT[3].TextureUV.X =  1.0f;
            __VerticesPNT[3].TextureUV.Y =  1.0f;

            //--> Normal
            __VerticesPNT[0].Normal = Vector3.Cross((__VerticesPNT[1].Position - __VerticesPNT[0].Position), (__VerticesPNT[2].Position - __VerticesPNT[0].Position));
            __VerticesPNT[0].Normal.Normalize();
            //
            __VerticesPNT[1].Normal = __VerticesPNT[0].Normal;
            __VerticesPNT[2].Normal = __VerticesPNT[0].Normal;
            __VerticesPNT[3].Normal = __VerticesPNT[0].Normal;

            //!
            //!!
            #endregion VERTICES    

BILLBOARD ENTITY WORLD FORMULATION :

internal void _M_Update( BaseCamera pCamera )
{


        //*>> Translate entity position
        //
        if ( __IsToTranslate  )
        {
            XF.Matrix.CreateTranslation(ref __Position, out __WPosition);                
        }

        //*>> S C A L I N G |  S I Z E
        //
        if ( __IsToScale )
        {
                __Scale.Z = 0.0f; 
                XF.Matrix.CreateScale(ref __Scale, out __WScale);   
        }

        //*>> R O T A T I O N
        //
        if ( __IsToRotate )
        {
                __Rotation.Y = 0.0f;
                __Rotation.X = 0.0f;              
                //
                XF.Matrix.CreateFromYawPitchRoll( 0.0f, 0.0f, XF.MathHelper.ToRadians(__Rotation.Z), out __WRotation);
         }

        //*>> W O R L D  M A T R I X
        //
        if ( __IsToTranslate || __IsToScale || __IsToRotate )
        {
        
                XF.Matrix m_MatrixToFaceCam = XF.Matrix.Identity;

                XF.Matrix.CreateBillboard
                (
                   ref __Position, 

                   ref pCamera.__Position, 
                   ref pCamera.__UpVector, 
                   pCamera.World.Forward, 

                   out m_MatrixToFaceCam
                );
                    
                //--> Final entity WORLD to pass on effect
                //
                ZH.Math.MatMul3( out __World, ref __WScale, ref __WRotation, ref m_MatrixToFaceCam );
    
        }   

}

Cheers ^_^y

1 Like

I did it!

Actually, crossing DexterZ code and other advices, I ended by changing the vertices, and the texture coordinates order from:

        quadVertices[0].Position = new Vector3(-1, -1, -1); // Lower left
        quadVertices[0].TextureCoordinate = new Vector2(1, 1);
        quadVertices[1].Position = new Vector3(-1, 1, -1);  // Upper left
        quadVertices[1].TextureCoordinate = new Vector2(1, 0);
        quadVertices[2].Position = new Vector3(1, -1, -1);  // Lower right
        quadVertices[2].TextureCoordinate = new Vector2(0, 1);
        quadVertices[3].Position = new Vector3(1, 1, -1);   // Upper right
        quadVertices[3].TextureCoordinate = new Vector2(0, 0);

To:

        quadVertices[0].Position = new Vector3(1, 1, 0);    // Upper right
        quadVertices[0].TextureCoordinate = new Vector2(0, 0);
        quadVertices[1].Position = new Vector3(-1, 1, 0);   // Upper left
        quadVertices[1].TextureCoordinate = new Vector2(1, 0);
        quadVertices[2].Position = new Vector3(1, -1, 0);   // Lower right
        quadVertices[2].TextureCoordinate = new Vector2(0, 1);
        quadVertices[3].Position = new Vector3(-1, -1, 0);  // Lower left
        quadVertices[3].TextureCoordinate = new Vector2(1, 1);

And don’t even need to set the Normal property (no effect anyway), everything is facing camera with the billboard matrix applied.

So the vertices order + the texture coordinates are the key to create a quad oriented in the right direction. No need to tweak the culling too.

My order was inspired on http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series3/Triangle_strip.php

the vertices are all declared in a clockwise manner relative to the camera

Also from here:

But I understand that the quad vertices should be created with anticlockwise coordinates, as I read that it’s the way OpenGL is dealing with. Am I right? Or because XNA is DirectX based, it’s a different order?

What I don’t understand it’s how the texture is mapped in the DexterZ sample.

I learned that:

http://www.real3dtutorials.com/images/img00018.png

OpenGL.glTexCoord2d 1, 0
OpenGL.glVertex3d 1, 1, 1
OpenGL.glTexCoord2d 0, 0
OpenGL.glVertex3d -1, 1, 1
OpenGL.glTexCoord2d 0, 1
OpenGL.glVertex3d -1, -1, 1
OpenGL.glTexCoord2d 1, 1
OpenGL.glVertex3d 1, -1, 1

But DexterZ is mapping the upper left corner of the texture to the upper right corner of the quad, etc. so applying the texture in a horizontal reversed position.

I cannot see the logic in this, except that “Ho, the texture is reversed, I will map it in the other side and it will work”. I would like to understand why :).

1 Like

The crate UV diagram you posted is in post-corrective space for non-compliance with OpenGL load order. TexCoords are naturally 0,0 at bottom-left in all APIs. In OpenGL coordinates will appear flipped because the texture was loaded incorrectly, the GL API expects to receive data from bottom-up, not top-down.

I cannot see the logic in this, except that “Ho, the texture is reversed, I will map it in the other side and it will work”. I would like to understand why :).

The order of the positions and the UVs in the code above isn’t correct for a CW front-face, pedantically (it’s UVs so … it’s all wishy-washy whatever you want really). For a CCW front-face that’s right, just vertically flipped.