Hi,
I was using the following lines to display a string using the SpriteBatch in a 3D environment in the previous versions of MonoGame (and XNA):
SpriteBatch.Begin();
SpriteBatch.DrawString(...);
SpriteBatch.End();
GraphicsDevice.BlendState = BlendState.AlphaBlend;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
It was working correctly, even when "mixing" 2D and 3D drawings. It now seems that I must draw the 2D after having drawn all the 3D, which is not why I want, because it can obviously quickly multiply the number of draw calls.
Do you have any idea?
Thank you
in the previous versions of MonoGame (and XNA):
as in MG 3.5? or 3.4?
It now seems that I must draw the 2D after having drawn all the 3D
I think that's the default behavior with XNA, but I might be mistaken.
Some screenshots with what you are trying to do would be helpful. You want the text in front of the 3D model, right? Do you use the depth parameter in DrawString()?
It was fine in 3.5, and I am setting the depth as 1.
A screenshot would not really mean anything but maybe this example can help:
-I draw a 3D terrain
-I draw a string
-and finally a 3D spherical skybox (with the camera being at the centre always)
(I know that in this specific example I can just change the order and it would work, but it was working like that before with similar drawings).
The result of that is: the terrain and string are drawn correctly, the skybox is invisible if the camera is inside the sphere (which is the problem), and visible if I am outside.
Cool, can you check the value of graphicsdevice.RenderMode after spriteBatch.End() and before you draw the sky?
Any diference between 3.5/3.6?
Looks like there is nothing called RenderMode?
Maybe you're asking for DisplayMode, which is not changing, seems to be some "constants" anyways (changed only when window events are triggered).
Sorry, I meant to say render state.
I am sorry but there is no RenderState too.
Edit: seems that the depth value of 1 is not considered when drawing the string.
You can see here as I am trying to draw "123", a terrain quad being drawn after the one drawing the string makes an overwrite:

And I don't know how that can be related to the sphere problem either...
Oh, wrong documentation. It's now graphicsDevice.RasterizerState.
Meanwhile, try to set graphicsDevice.RasterizerState = RasterizerState.CullNone just before you draw the skyBox.
That worked, thanks. What about the problem with the screenshot?
Edit: actually, the skybox is now visible, but we have the same depth problem.
That worked, thanks. What about the problem with the screenshot?
Edit: actually, the skybox is now visible, but we have the same depth problem.
Well, just let this issue open until we figure out what is going on (...and probably add a couple of unit tests).
By the way, which overload of spriteBatch.Begin() and spriteBatch.DrawString() did you use and with which parameters? Did you see what was the value of RasterizerState before you set it to CullNone?
I am giving all the parameters to the DrawString, this is the exact line:
Manager.SpriteBatch.DrawString(Font, Text, Position, Render, 0, Vector2.Zero, 1, SpriteEffects.None, 1);
These are the properties of the RasterizerState before drawing the skybox (or after drawing the text):
CullMode: CullCounterClockwiseFace
DepthBias: 0
DepthClipEnable: true
FillMode: Solid
MultiSampleAntiAlias: true
ScissorTestEnable: false
SlopeScaleDepthBias: 0
And when set to CullNone, nothing changes besides the CullMode.
Ok, Thanks a lot @torshid !
Note (to anyone interested):
MSDN says that End() method.. Flushes the sprite batch and restores the device state to how it was before Begin was called.. Did MonoGame used to do that on v3.5 or is it just coincidence that it worked before?
Also I know that's not the case with XNA. I had to set the states again after an .End() on XNA 4.0.
I always thought of SpriteBatch that it leaves the device in a sort-of undefined state.
https://blogs.msdn.microsoft.com/shawnhar/2010/06/18/spritebatch-and-renderstates-in-xna-game-studio-4-0/
"... SpriteBatch changes several device states to values that may not be appropriate for drawing in 3D."
@torshid
I think that's the expected behavior, but it's strange that it used to work on 3.5 and on XNA 4.0.
It shouldn't!
as for the "123" + quad, did you reset the states before you draw the 3D quad?
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
I plan to setup a few test for Sprites + 3D anyways... so, will see if there's any issue there.
Yes I did as mentioned in the first post (no change between AlphaBlend and Opaque by the way).
It is quite problematic if we want to draw text on models or whatever. I was able earlier to display any debug informations about the terrain in real time.
When you draw text in front of everything you must use layerDepth =0 (the default).
A layerdepth of 1 will draw on the far plane, behind everything else, and in your case wont be visible, hidden by the quad.
Still the same result, the layerDepth seems to have no effect at all. I don't understand why the same lines were working perfectly on 3.5 (and on XNA, as a search a Google shows).
Just for reference.
begin states
https://blogs.msdn.microsoft.com/shawnhar/2010/06/18/spritebatch-and-renderstates-in-xna-game-studio-4-0/
end states
https://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.spritebatch.end(v=xnagamestudio.40).aspx
separated spritebatch calls.
http://stackoverflow.com/questions/5820987/layerdepth-in-different-begin-and-end-block-of-spritebatch
I suspect the depth buffer is on when the text is drawn and some clipping is happening.
If you look carefully you can literally see a clipping line.
Setting this prior to your begin call should draw it.
GraphicsDevice.BlendState = BlendState.AlphaBlend;
GraphicsDevice.RasterizerState = RasterizerState.CullNone;
// actually just setting this alone should draw it.
GraphicsDevice.DepthStencilState = ds_depthtest_none ;
were the state is declared like so...
DepthStencilState ds_depthtest_none = new DepthStencilState()
{
DepthBufferEnable = false,
};
That really should draw your text if its not then basic effect is overriding it or your passing in a matrix to spritebatch.Begin(...) or a basic effect and rotating the text past the clipping plane. At least that's what it looks like to me.
Still the same result, the layerDepth seems to have no effect at all. I don't understand why the same lines were working perfectly on 3.5 (and on XNA, as a search a Google shows).
@torshid Can you provide a sample project that demonstrates the problem between XNA and MG?
MSDN says that End() method..
Flushes the sprite batch and restores the device state to how it was before Begin was called.
My guess is that the docs on MSDN for XNA 4.x is wrong. I am sure that pre-4.x when the state block system wasn't in place that SpriteBatch did this. I suspect the docs didn't get fixed when 4.0 shipped.
It should be easy to unit test and verify this behavior.
Alright so I did some tests on 3.6, 3.5, and XNA 4.0, and I was unable to realise what I was stating in my first post (alternating 2D and 3D draw calls, without 3D overwriting 2D -> 2D always visible). I even started to have doubts about if it was actually working... _although I am pretty sure it was_.
So maybe I need to reorient the problem: is it possible to have SpriteBatch draw calls always visible, even if some model draw calls are made after?
If not, then what are the solutions? Render the DrawString to a texture, and then display that with some vertices, for example?
Isn't it possible to make that work by only playing with the layerDepth?
I would like to give another concrete example of the usage of that (maybe it will help understand better): I have some players (3D models), and I want to draw 2D labels on top of each of them, with their names let's say. The labels must be on top of everything (never overwritten by anything), this is easily possible when all labels are drawn after all models are drawn (as stated before). The problem is that it is much better to draw both the model and the label in the same player draw call, when we have thousands of objects for example, or even in terms of organisation.
alternating 2D and 3D draw calls, without 3D overwriting 2D -> 2D always visible
The way this could work is with a depth buffer. It wouldn't stop models in front of the '2D' drawing to be drawn over it, but pixels that would fall behind it would be culled. However if you draw your 2D stuff on the near plane with a depth buffer it will always be the closest and not get drawn over. I'm not sure, but I think the default layerDepth for SpriteBatch is 0, which would mean 2D would be always on top when a depth buffer is used.
The problem with that is that spritebatch would set the depth buffer for the while quad not just the alpha testing parts. So you would see the whole rectangle and not just the text pixels. You'd need a custom shader using clip() to handle that.
The best thing to do is to just create a 'stringsToDrawLast' collection add each item as you draw each 3d model and then actually draw them all in one go at the end. Its far, far more efficient to draw all your strings from the same texture atlas in one go that interleave them with the 3d model drawing too.
@Jjagg
The way this could work is with a depth buffer. It wouldn't stop models in front of the '2D' drawing to be drawn over it, but pixels that would fall behind it would be culled.
@torshid
In SpriteBatch.Begin() use DepthStencilState.Default to let spritebatch write to the depth buffer.
@theZMan
The problem with that is that spritebatch would set the depth buffer for the while quad not just the alpha testing parts. So you would see the whole rectangle and not just the text pixels. You'd need a custom shader using clip() to handle that.
@torshid
Also BasicEffect will do the job.
There's an article on XNA how to do exactly that, no need to change anything for MG.
Just remember to use DepthStencilState.Default instead of DepthStencilState.DepthRead as @Jjagg said above.
https://blogs.msdn.microsoft.com/shawnhar/2011/01/12/spritebatch-billboards-in-a-3d-world/
@theZMan
Can't we force the GPU to cull certain pixels when the resulting pixel is transparent?
Some blending state perhaps?
Yes - you use the clip() method in your shader - thats why you need a custom shader and why even BasicEffect won't do what you want here. There's no mode to do that.
@torshid wants the text to work like a billboard but always be in front. So either use clip to be able to discard the alpha pixels and set the depth buffer to be the near plane. Or simply draw them last - honestly if you have any number of these switching state betwen every model and its label is not a good way to draw things anyway.
Ya and know you understand why i was writing my own text drawing methods tearing up spritefont and all rehashing all the draw methods. outside of spritebatch.
The shortcut for him here if you can call this a shortcut.
Is to use drawstring outside of begin end but im pretty sure you cant.
Soo copy the entire drawstring out of spritebatch stick it in a class have it call to regular old spritebatch.draw when it drops in the glyphs to item set since he can't call it directly.
At this point use your own shader or basic effect.
Use basic Effect as if it is a regular effect and outside of begin end.
Draw the 3d stuff save the text for last.
flip off the depth buffer no cliping will occur
On the graphics device flip on alphablend or additive it will deal with the blending.
Oh god that's awful.
Actually i made a class that handles this but its slower then spritebatch by a good margin and i never picked it back up. ill find the link.
Took a minute to find it ya that was you talking with me in that thread nkast.
I never did pick this back up and finish it but maybe this guys impatient and i can get him to do it for me lol. http://community.monogame.net/t/a-silly-question/8740
How about AlphaTestEffect then?
honestly if you have any number of these switching state between every model and its label is not a good way to draw things anyway.
I agree with that. I like the one described above about drawing all the names at the end.
[@nkast]
How about AlphaTestEffect then?
I always forget about that one... yes I think that works. Been a long time since I looked at the source but it probably uses clip()
[@willmotil]
The shortcut for him here if you can call this a shortcut.
Is to use drawstring outside of begin end but im pretty sure you cant.
The shortcut here is to do what very game engine has done since the beginning of time and batch things by state. That means not drawing a 3d model follwed by its 2d label. You store a reference to the 'set of things drawn with BasicEffect' and 'the set of things using spritebatch with this atlas' and then you draw each 'set of things' in a group.
I'm gonna close this because the originally reported issue is expected behavior. SpriteBatch by default does not read/write from/to the depth buffer.
To get 2D text in front of 3D stuff, either draw your text after your 3D stuff or draw it before and pass a depth state that writes depth and a custom effect that clips transparent pixels to your SpriteBatch begin call.
Most helpful comment
The problem with that is that spritebatch would set the depth buffer for the while quad not just the alpha testing parts. So you would see the whole rectangle and not just the text pixels. You'd need a custom shader using clip() to handle that.
The best thing to do is to just create a 'stringsToDrawLast' collection add each item as you draw each 3d model and then actually draw them all in one go at the end. Its far, far more efficient to draw all your strings from the same texture atlas in one go that interleave them with the 3d model drawing too.