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

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:

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.

Really? That should explain a lot!
Can you point me on a documentation? I cannot find any reference to that, every diagram found on google shows 0,0 upper-left:
https://www.google.com/search?q=texcoords+directx&rlz=1C1CHBD_frFR702FR703&oq=texcoords+directx&aqs=chrome..69i57.4024j0j7&sourceid=chrome&ie=UTF-8

Is this because of the bottom-up texture you’re talking?

OK, that’ what I thought, but it’s the only way to have the CreateBillboard function showing the face to the camera :frowning: I cannot understand, in a simple way, why creating a quad and applying a billboard Matrix is so confusing.

Because I derped out and bumbled the corners :\ . Top-left for both:

The perception of the APIs having different systems comes from: glTexImage, notably it clearly dictates that data is to be fed from the bottom-left - which is opposite of DX. PBuffers/RTs being that same layout doesn’t help the confusion.

I cannot understand, in a simple way, why creating a quad and applying a billboard Matrix is so confusing.

I don’t get why you’re using a billboard matrix for a quad rather than cross the view direction wth the up direction. Seems like overkill.

Hey Rockford, I’m glad that it’s working for you ^ _^ y

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

I need the normals because my billboard implementation can use BasicEffect or custom effect, I can apply any custom effect on my billboard, Normal mapping effect is a best example which needs normal ^ _^ y if you don’t need normal you problly only using PT VertexPositionTexture ? as I’ve use PNTTB = VertexPosotionNormalTangentBinormal ^ _^ y my code above is only tream down sample.

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

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

My billboard is only using 4 vertices which is rendered by a TriangleStrip
__GD.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);

You said it right, because it works : - D , every game engine or framework needs to tweak until you are satisfied on the result, it’s the best UV coordinates that work on TriangleStrip with 4 vertices on my implementation and on my game engine settings.

I’m also interested to see others idea and their implementation : - D

Cheers ^_^y

1 Like

Because I don’t know how :slight_smile: so the billboard function seemed a good idea… but I’ll give a try. Any help welcome.

I’m also interested to see others idea and their implementation : - D

I’ve been using some variation of this block for the past few years (HLSL - done in geometry shader to turn a point into a billboard [just the math bits, centroid-as-origin version]):

float2 minUV = codedTexCoords.xy;
float2 maxUV = codedTexCoords.zw;
float2 partSize = boardSize.xy;

float2 partUV[] = {
    float2(minUV.x, maxUV.y),
    minUV,
    maxUV,
    float2(maxUV.x, minUV.y),
};

// twist around pole-axis pointing out from centroid
float s;
float c;
sincos(boardSize.z, s, c); // z in rads
float3 upAxis = c * float3(1, 0, 0) - s * float3(0, 1, 0);
float3 rightAxis = s * float3(1, 0, 0) + c * float3(0, 1, 0);
upAxis *= partSize.y;
rightAxis *= partSize.x;

float3 partOffset[] = {
    -upAxis + -rightAxis,
    -upAxis + rightAxis,
    upAxis + -rightAxis,
    upAxis + rightAxis,
};

Then I use a billboard mat on that twisted 2d axis. If it’s not a trivial look-at camera billboard than I just do the usual 3x3 built as the premult before offset and world transform:

float3 right = normalize(cross(bbFaceDir, cameraDir));
float3 up = normalize(cross(bbFaceDir, right));
float3x3(right, up, bbFaceDir)

The actual expansion into a billboard is pretty route.

1 Like

Zup @AcidFaucent,

That’s an awesome way to implement billboard using an effect bro! ^ _^ y

ATM I’m fine using a quad vertices for billboarding, since I can set it to be transparent, additive, solid and can use BasicEffect or all other possible custom shaders effect I can think of easily.

I ended up with something like just creating a World in the direction of the camera:

        Vector3 directionVector = Vector3.Normalize(quadPosition - cameraPosition);
        Matrix lookAt = Matrix.CreateWorld(quadPosition, directionVector, Vector3.Up);
        mySpriteEffect.World = lookAt;

Working perfectly! (not optimized I guess, but I won’t have a lot of quads to rotate).