2
\$\begingroup\$

I experienced some problems with SpriteBatch in XNA when I tried to mix two different SpriteSort modes. (Begin with deferred, end, begin with BackToFront wasn't sorting properly.) I've settled on just giving everything I draw with SpriteBatch a depth value.

To that end, what is the smallest increment I can use on the floating point variable that will be used as the depth parameter without running into rounding problems? The valid values for this float are between 0.0 and 1.0. It is a float, not a double, and the language is C#.

UPDATE: My testing has shown that 0.008f is the smallest amount I can change my depth value by and be certain the sorting will work. I'm guessing there is something else at play in BasicEffect or SpriteBatch.

What I am trying to use this for is the rendering of my game's map grid. I want to make heavy use of transparency and overlapping to create a specific visual look for the tiles, the doodads, and the characters.

Essentially, I am using a painter's algorithm to draw the tiles in order from the back row to the front. I have them numbered so I know the ordering is correct. The doodads I am trying to draw on top of the map are not appearing when my depth decrementor is set to a low enough number to draw everything I will need. It only works properly in a range where I can only access about 100 unique depths.

UPDATE about SpriteSortMode problems (as requested):

Thus far in my project I have got by just fine using SpriteSortMode.Deferred for everything. I have all of the standard UI components you would expect, windowing, clipping, etc. working.

Now I am attempting to draw my map grid. Like windowing/clipping this requires things to be done in a specific order to look right. The grid tiles draw in a certain order. To accommodate overlapping, doodads/characters draw on top of a tile before the next tile is drawn.

No matter what I did, my doodads/characters would always be drawn under the tiles. Changing the order of draws had no impact. Even drawing all of the tiles and then drawing the doodads resulted in them showing up underneath the tiles. Especially odd was that my UI elements continued to appear above the problem tiles as I scrolled around the map.

I switched to SpriteSortMode.BackToFront and with a high enough depth difference (~0.01) I see things drawing on the grid as expected, but this depth difference is way too high to support every visible tile of the map. I am searching for a better solution than to write code that will End and Begin a new SpriteBatch every time ~100 pieces are drawn.

\$\endgroup\$
4
  • \$\begingroup\$ Can you describe your problems when mixing different sorting modes? There's a good chance that fixing those would be easier than trying to deal with floating-point epsilons. \$\endgroup\$ Commented Aug 9, 2012 at 6:17
  • \$\begingroup\$ where's your far- and near clipping plane at? \$\endgroup\$
    – bummzack
    Commented Aug 9, 2012 at 7:06
  • \$\begingroup\$ The min and max depth on the Viewport are set to 0.0 and 1.0. \$\endgroup\$
    – Jim
    Commented Aug 9, 2012 at 22:34
  • 1
    \$\begingroup\$ Was able to solve my problem by reverting to Deferred mode and changing the screen that draws the grid. Deferred essentially gives me a number of layers equal to the number of draw calls I make, which is way more than depth sorting supports by default. I still have no idea why things drew in the wrong order. The drawing code in my map class hasn't changed. At one point I had two screens rendering the grid at the same time. One rendered the correct order. One rendered incorrectly. Both were bare-bones screen objects. Nearly identical, added in the same way. Same paint logic. O.o \$\endgroup\$
    – Jim
    Commented Aug 10, 2012 at 0:39

3 Answers 3

1
\$\begingroup\$

The answer is:

5.9604644e-8

Be very, very careful with it. This is the minimum representable distance between 1 and the next number below it in IEEE 754 single-precision binary floating point, and nothing else. I cannot guarantee that SpriteBatch will actually honor it (though it probably will). Also, any mathematical operations on two numbers that differ by this much have the possibility to screw you over. (For example: don't halve the depths of two layers that differ by this and expect the two depths to still be different.)

If you use this epsilon religiously, you get 16,777,218 different evenly-spaced layer depths. You probably don't need that many layers. You can use a much, much larger value and have much less chance of breaking anything.

If you want a slightly less magic number, you could write this:

1.0f / (float)((1 << 24) + 1)
\$\endgroup\$
6
  • \$\begingroup\$ Thank you for the answer, but I am uncertain of its correctness. My tests show that the smallest number I can decrement the float value by and see proper sorting is ~0.008. Either your calculation is not restrained to the range of 0.0 to 1.0 or there is some hideous rounding going on in the BasicEffect shader. \$\endgroup\$
    – Jim
    Commented Aug 9, 2012 at 5:58
  • \$\begingroup\$ @Jim Your depth buffer, as well as the shader, affect this precision if it goes that far. This answer would only work if SpriteBatch was sorting beforehand. There may not be a good answer in the general case, because it depends on the depth buffer range (which should be enough to cover this distinction, but may not be). \$\endgroup\$ Commented Aug 9, 2012 at 6:03
  • \$\begingroup\$ It could depend on the graphics card, as well. If this doesn't work, I wouldn't suggest trying to experimentally arrive at a minimum—it probably won't work across all cards. Find the number of layers you actually use, and split the 0–1 range up into that many slots. \$\endgroup\$ Commented Aug 9, 2012 at 6:05
  • \$\begingroup\$ I'm using a GTX 670. I don't think it is the video card. Sadly, the number of layers I want to use is around 10,000. \$\endgroup\$
    – Jim
    Commented Aug 9, 2012 at 6:20
  • \$\begingroup\$ 10.000 layers? So you're going to call Spritebatch.Draw() more than 10.000 times per frame? That could be terrible for performance. Have you tried in a test case how much time this will take each frame? What makes you think you need 10.000 layers anyway? I would be amazed if there was a legitimate use for more than 50 layers in a 2D game. \$\endgroup\$
    – Roy T.
    Commented Aug 9, 2012 at 9:05
0
\$\begingroup\$

I think your problem is related to the depth-buffer. The depth-buffer goes from the near- to the far clipping plane of your camera (or front-clipping plane and back-clipping plane). If this is a large distance (say 1 .. 1000) and your objects are placed somewhere at 100 .. 101, then you're using the depth-buffer very inefficiently.

I'm not entirely sure how the depth-buffer behaves with an orthographic projection, but in case of a perspective projection, it has a much higher accuracy close to the near-plane, where accuracy of far away objects diminishes.

To optimally use the range of your depth-buffer you should set your near- and far-planes as close to your object depth-range as possible. So if your objects are all positioned at z: 100 .. 101 (distance to camera), then setting the near plane to something like 99.9 and the far-plane to 101.1 should help tremendously.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The min and max depth on the Viewport are set to 0.0 and 1.0. \$\endgroup\$
    – Jim
    Commented Aug 9, 2012 at 23:51
  • 1
    \$\begingroup\$ Also, SpriteBatch turns off depth reads. \$\endgroup\$ Commented Aug 10, 2012 at 8:46
-2
\$\begingroup\$

The smallest increment you can do on a float in C# and have a significant value to be different from 0 is float.Epsilon.

From MSDN Single.Epsilon

Represents the smallest positive Single value that is greater than zero.

The value of the Epsilon property reflects the smallest positive Single value that is significant in numeric operations or comparisons when the value of the Single instance is zero

Single is the equivalent of float in .NET type.

\$\endgroup\$
2
  • \$\begingroup\$ This is incorrect. Single.Epsilon is not the equivalent of FLT_EPSILON. Single.Epsilon is, actually, the smallest positive non-zero float possible (it's a subnormal number, for the interested). You can subtract Single.Epsilon from 1 and not change it at all—it is only the epsilon very, very near to zero (less than 1.17e-38). \$\endgroup\$ Commented Aug 9, 2012 at 5:39
  • \$\begingroup\$ Just a note. You have to be very careful with Single.Epsilon. It works for 0 + eps != 0 but it might be that 10000000 + eps == 10000000 because the smallest change floats can represent gets bigger when the numbers get bigger. Single.Epsilon only works very close to 0. (edit: John beat me to it, note that even FLT_EPSILON can have this problem when the numbers get larger, should be fine between 0 and 1 though) \$\endgroup\$
    – Roy T.
    Commented Aug 9, 2012 at 5:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .