5
\$\begingroup\$

I have a spritebatch where I draw some textures transformed in 3d space.

The easy way was just Begin with the transformation matrix, draw, and end it right after.

But that is silly, so I'm trying with a vertex shader.

My results however aren't the desired ones. After the first draw&transformation, the rest of the textures stop apprearing, that hints me that I cant change the world matrix between draws.

How should I approach this?

I set the projection matrix like this, before the sp.Begin:

shader.Parameters["Projection"].SetValue(Matrix.CreateOrthographicOffCenter(
                                         0, sp.GraphicsDevice.Viewport.Width,
                                         sp.GraphicsDevice.Viewport.Height, 0, 0, 1)
                                         );
sp.Begin(SpriteSortMode.Deferred, null, null, null, null, shader);

afterwards:

    rotM = Matrix.CreateFromYawPitchRoll(MathHelper.ToRadians(startRotations.Y), MathHelper.ToRadians(startRotations.X), MathHelper.ToRadians(startRotations.Z))
               //scale after rotation so I can destroy the z value after rotation
                * Matrix.CreateScale(endScale.X, endScale.Y, 0)
                * Matrix.CreateTranslation((Origin.X + PositionRec.X + Offset.X) * endScale.X, (Origin.Y + PositionRec.Y + Offset.Y) * endScale.Y, 0);

        shader.Parameters["World"].SetValue(rotM);
        shader.CurrentTechnique.Passes[0].Apply();
        sp.Draw(BackgroundTexture, Vector2.Zero, PositionRec, BackgroundColor * alpha);
        rotM = Matrix.Identity;

Vertex shader:

float4x4 World;
float4x4 Projection;

struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;

};

struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    float4 worldPosition = mul(input.Position, World);

    output.Position = mul(worldPosition, Projection);

    output.TexCoord= input.TexCoord;

    return output;
}

pixel shader:

float4 PixelShaderFunction(VertexShaderOutput input, bool front:  VFACE) : COLOR0
{
    float4 Color = tex2D(ColorMapSampler, input.TexCoord);  
    return Color;

}

Why is this happening? It really looks like that the world matrix that I use is only changed the first time, why?

Thanks in advance, Roger

EDIT:

Ok since I wanted to reuse spritebatch to render quads in 3d, I had to addapt 2 classes in monogame to get this to work.

in spritebatch.cs i had to include some new methods:

     public void Draw(Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, Matrix transform)
            {

                Draw(texture, position, sourceRectangle, color, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, transform);
            }

public void Draw(Texture2D texture,
        Vector2 position,
        Rectangle? sourceRectangle,
        Color color,
        float rotation,
        Vector2 origin,
        float scale,
        SpriteEffects effect,
        float depth,
        Matrix transform)
        {
            CheckValid(texture);

            var w = texture.Width * scale;
            var h = texture.Height * scale;
            if (sourceRectangle.HasValue)
            {
                w = sourceRectangle.Value.Width * scale;
                h = sourceRectangle.Value.Height * scale;
            }

            DrawInternal(texture,
                new Vector4(position.X, position.Y, w, h),
                sourceRectangle,
                color,
                rotation,
                origin * scale,
                effect,
                depth,
                transform);
        }

    internal void DrawInternal(Texture2D texture,
             Vector4 destinationRectangle,
            Rectangle? sourceRectangle,
             Color color,
             float rotation,
             Vector2 origin,
              SpriteEffects effect,
              float depth,
                Matrix transform)
            {
                var item = _batcher.CreateBatchItem();

                item.Depth = depth;
                item.Texture = texture;

                if (sourceRectangle.HasValue)
                {
                    _tempRect = sourceRectangle.Value;
                }
                else
                {
                    _tempRect.X = 0;
                    _tempRect.Y = 0;
                    _tempRect.Width = texture.Width;
                    _tempRect.Height = texture.Height;
                }

                _texCoordTL.X = _tempRect.X / (float)texture.Width;
                _texCoordTL.Y = _tempRect.Y / (float)texture.Height;
                _texCoordBR.X = (_tempRect.X + _tempRect.Width) / (float)texture.Width;
                _texCoordBR.Y = (_tempRect.Y + _tempRect.Height) / (float)texture.Height;

                if ((effect & SpriteEffects.FlipVertically) != 0)
                {
                    var temp = _texCoordBR.Y;
                    _texCoordBR.Y = _texCoordTL.Y;
                    _texCoordTL.Y = temp;
                }
                if ((effect & SpriteEffects.FlipHorizontally) != 0)
                {
                    var temp = _texCoordBR.X;
                    _texCoordBR.X = _texCoordTL.X;
                    _texCoordTL.X = temp;
                }

                item.Set(destinationRectangle.X,
                        destinationRectangle.Y,
                        -origin.X,
                        -origin.Y,
                        destinationRectangle.Z,
                        destinationRectangle.W,
                        (float)Math.Sin(rotation),
                        (float)Math.Cos(rotation),
                        color,
                        _texCoordTL,
                        _texCoordBR, transform);

                if (_sortMode == SpriteSortMode.Immediate)
                    _batcher.DrawBatch(_sortMode);
            }

in SpritebatchItem.cs

        public void Set(float x, float y, float dx, float dy, float w, float h, float sin, float cos, Color color, Vector2 texCoordTL, Vector2 texCoordBR, Matrix transform)
        {
            // TODO, Should we be just assigning the Depth Value to Z?
            // According to http://blogs.msdn.com/b/shawnhar/archive/2011/01/12/spritebatch-billboards-in-a-3d-world.aspx
            // We do.
            vertexTL.Position.X = x + dx * cos - dy * sin;
            vertexTL.Position.Y = y + dx * sin + dy * cos;
            vertexTL.Position.Z = Depth;
            vertexTL.Position = Vector3.Transform(vertexTL.Position, transform);
            vertexTL.Color = color;
            vertexTL.TextureCoordinate.X = texCoordTL.X;
            vertexTL.TextureCoordinate.Y = texCoordTL.Y;

            vertexTR.Position.X = x + (dx + w) * cos - dy * sin;
            vertexTR.Position.Y = y + (dx + w) * sin + dy * cos;
            vertexTR.Position.Z = Depth;
            vertexTR.Position = Vector3.Transform(vertexTR.Position, transform);
            vertexTR.Color = color;
            vertexTR.TextureCoordinate.X = texCoordBR.X;
            vertexTR.TextureCoordinate.Y = texCoordTL.Y;

            vertexBL.Position.X = x + dx * cos - (dy + h) * sin;
            vertexBL.Position.Y = y + dx * sin + (dy + h) * cos;
            vertexBL.Position.Z = Depth;
            vertexBL.Position = Vector3.Transform(vertexBL.Position, transform);
            vertexBL.Color = color;
            vertexBL.TextureCoordinate.X = texCoordTL.X;
            vertexBL.TextureCoordinate.Y = texCoordBR.Y;

            vertexBR.Position.X = x + (dx + w) * cos - (dy + h) * sin;
            vertexBR.Position.Y = y + (dx + w) * sin + (dy + h) * cos;
            vertexBR.Position.Z = Depth;
            vertexBR.Position = Vector3.Transform(vertexBR.Position, transform);
            vertexBR.Color = color;
            vertexBR.TextureCoordinate.X = texCoordBR.X;
            vertexBR.TextureCoordinate.Y = texCoordBR.Y;
        }

I don't know about the performance, are these calculations costly?

What do you guys think?

I think the rotation isn't working properly, going to investigate further

\$\endgroup\$
  • \$\begingroup\$ gamedev.stackexchange.com/questions/50369/… so I cant add new vertex information? you guys know any way to go around that? \$\endgroup\$ – Roger Martins Dec 17 '13 at 8:34
  • \$\begingroup\$ The edit code could be altered to simply accept the world matrix you were using previously. Then just create the vertex as it was before and transform the position at the end using the world matrix. This way it will work for different purposes later. \$\endgroup\$ – OriginalDaemon Dec 17 '13 at 12:30
  • \$\begingroup\$ that's what i'm doing, I just added the transform matrix to the draw/draw internal, and after the vertex information is set I transform the result vertexTL.Position = Vector3.Transform(vertexTL.Position, transform); I actualy don't need to change the world matrix because spritebatch allready does it for me, but yeah if I want a moving camera or other kinds of projection that's a good idea. \$\endgroup\$ – Roger Martins Dec 17 '13 at 12:44
  • \$\begingroup\$ My bad, thought the effect was being done with the trig bits. \$\endgroup\$ – OriginalDaemon Dec 17 '13 at 12:48
2
\$\begingroup\$

SpriteBatch.draw(...) probably isn't doing what you think it is. It doesn't immediately draw. This is supposed to be the case - especially since you are using deferred mode.

Making draw calls can be expensive so you want to do as few of them as possible. So for sprites, which are 4 vertices, you want to "batch" them. The XNA solution is the "SpriteBatch".

SpriteBatch.draw(...) actually just builds an internal buffer of sprites to draw later, with texture information and transformed vertices, and then, at an appropriate time, it draws them. This appropriate time is determined by the modes set in spriteBatch.begin().

Deferred mode basically tells sprite batch not to draw anything until the very end so the order can be sorted for all sprites. So I believe it won't actually make any real draw calls until SpriteBatch.end().

This means that your world matrix is actually only set before the last SpriteBatch.draw() call and thus is always the matrix you construct for the last sprite.

Regardless, the sprite batch will use the position information supplied in the SpriteBatch.draw() call to create a rectangle in the buffer which is already transformed correctly into world coordinates. This means the vertex is already in world coordinates when it reaches the vertex shader and shouldn't be transformed again with the world matrix. You actually just want to apply your view*projection matrix transformation in the vertex shader (and view is probably Matrix.Identity so can be ignored).

Please try;

shader.Parameters["World"].SetValue(Matrix.Identity);

By the sounds of things, your original method was working without the shader, so you should maybe just return to that, unless you actually need a pixel shader for some reason.

\$\endgroup\$
  • \$\begingroup\$ What I want to do is perform a transformation with the vertices that spritebatch creates. I sp.draw(), spritebatch is going to create my quad and store the vertices in its internal batcher array (lets ignore texture flush), the problem comes beacause the Technique.apply() of the shader is going to influence all of the vertices, so it seems I cant batch this effect (imgur.com/ihdlepa). the only possible solution i can think of is to use the matrix somehow and change the vertices position atribute before the drawuserprimitives of the spritebatcher, but i dont know how to do that. thanks \$\endgroup\$ – Roger Martins Dec 17 '13 at 11:02
  • \$\begingroup\$ maybe I can cheat my way though with a custom vertex declaration, where I recreate the matrix with some vector4's working as lines or rows, but there should be a way to transform the vertex position it self. edit: there is msdn.microsoft.com/en-us/library/bb199531.aspx , trying this will post more info later \$\endgroup\$ – Roger Martins Dec 17 '13 at 11:14
  • \$\begingroup\$ You could move the algorithm to create the matrix into the vs or just forget about batching and make it work as it was before (the inefficiency may never be an issue and you can't get more inefficient than 'not working at all'). \$\endgroup\$ – OriginalDaemon Dec 17 '13 at 12:01
  • \$\begingroup\$ check edit, any suggestions? \$\endgroup\$ – Roger Martins Dec 17 '13 at 12:20
  • 1
    \$\begingroup\$ thanks allot mate, well the problem was always the same, transform the vertices from the quads, individually. Tested further, it works without any problems, rotations also work. I just didn't want to implement a quad renderer for now. thanks for your help \$\endgroup\$ – Roger Martins Dec 17 '13 at 12:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.