2D custom quad drawing: SpriteEffect vs BasicEffect

Hi

My project consists of an endless world of tiles, divided into chunks.
I’m drawing my tile chunks using custom quads because it’s faster than calling spritebatch.draw thousands of times. I’m currently using a BasicEffect to match the spritebatch 2D view (using world, view & projection matrices).

I actually have two questions about this topic:

1) Y-axis camera transformations have the opposite effect on my quad drawing vs spritebatch drawing. Is there an easy way to reverse the Y-axis for quad drawing, or do I need to use two separate transformation matrices for my camera (normal 2d sprites drawn using spritebatch and the world tiles drawn using custom quads).

2) There’s also a SpriteEffect class. The latest MonoGame development build allows access to the TransformMatrix variable inside the class. Is it possible to use this effect instead of the BasicEffect class if I just need a 2D view?
I already tried using the sprite effect class by setting the matrix variable to the camera transformation matrix but that didn’t work. I noticed that SpriteEffect uses the Matrix.CreateOrthographicOffCenter function, but the ‘bottom’ Y-value parameter is higher than the ‘top’ Y-value. I use the same projection for my basic effect but with bottom & top switched, otherwise I wouldn’t see anything.

Thanks in advance :slight_smile:

1 Like

If you need a fast way to draw tilemaps you can have a texture or rendertarget as the tilemap and use a shader to draw it. You can skip the constraction of thousand of quads, dividing the map into chucks for culling and rendering thousands of Vertices. You can see an example of a shader in my repo here.

Now…

  1. Spritebatch is using the so called Screenspace coordinates.It constructs the Projection by calling Matrix.CreateOrthographicOffCenter(left:0, right:vp.Width, bottom:vp.Height, top:0, zNearPlane:0, zFarPlane:-1);
    notice how [top -> bottom] is defined as [0 -> vp.Height], resulting in reversing Y-axis.
    Normally you would call
    Matrix.CreateOrthographicOffCenter(left:0, right:vp.Width, bottom:0, top:vp.Height, zNearPlane:0, zFarPlane:-1);
    to constructs a right-handed world space projection.

Therefore, you can do the same in your Projection/BasicEffect to draw primitives in screenspace just as spriteBatch.
(in 3.7.1 and prior you also need to add halfpixel for OpenGL platforms)
…or you can pass a BasicEffect in SpriteBatch.Begin(…) if you want to draw sprites in world space.
( just set BasicEffect.Projection/.View with you proj/view).

Personally I prefer to use SpriteBatch’s Screenspace only for GUI.
For game asset’s, sprites, 3D models, etc I use normal right-handed world space coordinates.

  1. SpriteEffect.TransformMatrix is what you pass at SpriteBatch.Begin(). It would be null if you don’t set it yourself.
    I suppose you could use SpriteEffect to draw primitives but it’s best to avoid it. It’s a legacy class and it’s behavior keep changing, actually nowadays SpriteBatch could have used BasicEffect internally as it’s exactly the same shader code. Unfortunately someone made it public down the road for no good reason. Just pretend that it’s not there… as it doesn’t add anything in comparison to BasicEffect.

That’s probably due to culling/Winding.

If you want to draw quads in screenSpace (spriteBatch projection) you have to reverse the order of the vertices in a triangle or switch to RasterizerState.CullClockwise.

If you want to draw using SpriteBatch but with a BasicEffect and a normal projection, you have to switch the default RasterizerState and possibly Flip the sprite. (spritebatch generates quads in a way that are pre-flipped to work with reverse-Y and the default CullMode).

The fastest way to fix it is to set gd.RasterizerState = RasterizerState.CullNone.
Culling is used mostly in drawing 3D models and skip triangles that are facing away from the camera.

Culling is used when rendering anything, everything is in 3D, the sprite batch ultimately render to a quad.

CullMode = CCW; // This is the default cull mode, for XNA/MonoGame, it’s counter clock wise.
CullMoce = CW; // This is to cull the other way, so to show the other side of the triangle.
CullMode = None; // This is show both sides of the triangle, more expensive as you are rendering 2 each draw call.

If it’s any help, and you are using a quad, this is what I use as a screen space quad when rendering.

Thanks for the replies!

It’s probably the CullMode…

@nkast Thank you for the shader information. Didn’t even think of rendering the tiles that way.

I have quad batch source code and tile memory-pan/scrolling on my AlienScribbleInteractive site under design - maybe it could give you some ideas too? I believe all the actual drawing is typically done all at once with the End() call for most batchers[ie: don’t use immediate] and if you use source-rects and big sprite-sheets - it’ll be much more efficient than texture switching. Maybe you know all this already - just putting it out there. I always thought cullmode.none would be faster since there’s no DP test in the culler and thought it only actually draws once (just the texture would be backward if viewed from behind). Please correct me if I’m wrong ( I probably am XD )

@AlienScribble Yeah I already create a sprite sheet at runtime. I use custom quads because the draw call for each tile using spritebatch takes more time than a manual primitive triangle draw per chunk.

Did you get it working ? I think charles and nkasts are saying is right.

I think i had a post on here that setup a quad and the matrices like spritebatch you could try that. There are two examples i think its the second one i just slopped in the matrix code but i think it works.

1 Like

Yeah. I got it working. I just didn’t know what the default culling mode was :slight_smile:

Interesting. I’m curious if it’s the parameter passing for the draw call that’s impacting the draw call speed in SpriteBatch? I’m interested cuz I wouldn’t mind speeding up my QuadBatch some more which works in a similar way. It just adds triangles/vertices to be drawn in End. In my version if rotation is 0 or scaling is 1 then those calculations are skipped. I’m not sure how one could make it much faster other than to simply not use a draw method and simply add quad data directly - I’m assuming that’s what you mean by “draw” (since you’d proly want to do actual drawing all at once). Now I’m curious.
(Btw - are you using Dynamic or static vertex buffer? )

@AlienScribble I’m currently using a static vertex buffer for each chunk. A chunk currently consists of 30 x 30 tiles and the vertex buffer holds the quad data for each tile. I’m still experimenting but my fps increased from ~150-250 to 1200 - 2000 fps for 59400 tiles.

1 Like

Ya SpriteBatch is really fast for what it is supposed to be used for. Dynamic quad drawing with extra capability.
but
For static stuff that doesn’t need to keep changing like a map. Its of course faster to pre-build a vertexbuffer and just keep drawing it every frame.

Unlike spritebatch you just build the whole vertice buffer once though, so that is of course faster.

You need a effect though when you do it without spritebatch either your own (effect from the content pipeline) or you can use one of the pre-made ones like basic effect or sprite effect or alpha effect ect… You still have to set up the matrices yourself to pass to these effects.
world Matrix.CreateWorld(,) view Matrix.CreateLookAt(,) projection Matrix.CreateOrthographic(,)
You change the world matrix to move the vertice buffer around visibly, the vertex buffer basically acts like a model. The calls he was talking about here relate to the matrices you pass to the effects.

2 Likes