Page 1 of 2

Depth buffer and slanted sprites?

Posted: Sat Apr 20, 2019 10:02 pm
by kraybit
Hello!

Saw episode 364 of Handmade Hero where they use slanted sprites to let the GPU handle all the z-ordering for a 2D-game. Here's basically what it looks like. It's explained more in-depth earlier in the same video.

Thought it was a quite clever technique. No need to set z-positions, and no sorting. Lower y-position just magically means "further back". Though it does require 3D transforms.

Then I saw the Löve config file has a depth setting:

Code: Select all

function love.conf(t)
	...
	t.window.depth = nil                -- The number of bits per sample in the depth buffer
	...
end
Interesting. Questions:
  1. What's the depth setting for?
  2. Would anything like the Handmade Hero technique be possible in Löve2D?
  3. If not, is it possible to draw with a z-position? Or some other technique using the depth buffer?
  4. If not, then no worries : )
Thanks for reading
Cheers!

PS. Brand new to Löve2D, really like it!

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 1:31 am
by pgimeno
Hi, welcome to the forums!
  1. It defines the number of bits per sample in the depth buffer. I believe 24 is the standard. 32 does not work in my system.

    This is necessary if you want to use the default depth buffer for the screen. I think you can use a canvas as a depth buffer, and in that case you don't need to define that config option. But the one for the screen is convenient.

    To use the depth buffer, take a look at love.graphics.setDepthMode.
  2. Yes, but the technique is not going to be too simple. You basically need to bypass LÖVE's usual workflow and draw all your images as meshes, pretty much as you do for 3D rendering. Maybe there's an alternative using a vertex shader and uniforms, though, but autobatching in LÖVE-11.0 could get in the way.

    As an advantage, LÖVE's default transformation matrix is orthographic, which makes it simple to just change the Z coordinate of the top of the sprite without altering the projected result.
I've attached a proof of concept. I've adapted my half-baked 3D lib to this, so there may be some unnecessary code remaining.

Handling of alpha is a separate issue, not covered in the demo.

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 8:14 am
by Nelvin
Is there actually a way to handle alpha (well at least fully opaque/transparent pixels) in combination with the depth buffer, i.e. is there an option to only write to the depth buffer if a pixel is opaque?

This would allow to render sprites out of order and doing so, improve batching a lot in games with lots of sprites but no way to sort them into sheets based on their usage/rendering order.

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 12:21 pm
by pgimeno
I don't have time now to write a PoC (maybe later today) but for full transparency handling (edit: by "full" I meant "binary", i.e. fully opaque or fully transparent), see https://learnopengl.com/Advanced-OpenGL/Blending - the section titled "Discarding fragments".

It all boils down to use a pixel shader with something like this:

Code: Select all

    if(texColor.a < 0.1)
        discard;

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 12:41 pm
by zorg
You can already use stencils to "sort" sprites realtime though... at least i think it's possible.

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 1:46 pm
by raidho36
There is no way to use both alpha and depth buffer in such fashion: alpha blending relies on elements being rendered in particular order, and if you're using depth buffer it's probably because you draw them in random order (if you drew them back to front you wouldn't need the depth buffer). Using alpha in conjunction with depth testing and random element drawing order will result in graphical artifacts on transparent pixels. Video games usually handle this by drawing transparent objects back to front - their rendering order is sorted on the CPU (or using compute shaders). Other games use "stipple alpha" - fake transparency where certain proportion of pixels is not rendered to the screen, with probability of it depending on alpha value of the pixel. This is about the only way you could go about using both transparency and deferred lighting. You could use a (moving) noise map instead of bayer matrix to get visually smoother results. Finally, you can use no transparency altogether and use "cutout cardboard" drawing, where during rendering you would fill each pixel with either 100% background color or 100% sprite color, usually by checking against certain alpha value - as pgimeno suggested above.

Until certain point, Z-sorting sprites on the CPU is a trivial operation, so no need to use special tricks to make the GPU do the work. Particularly, existing sprites are unlikely to change their order between frames, and when they do it's usually by a single slot at a time, so you can use cocktail sort (bidirectional bubble sort) to keep it sorted at very little expense, and use insertion sort when adding more sprites.

That said, I'm not really sure you can even realistically approach a point where sprite sorting becomes so intense on the CPU that GPU accelerated Z-sorting would be necessary.

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 2:23 pm
by pgimeno
Here's the PoC mentioned which handles (binary) alpha. It's almost the same program above; the only change is the addition of a pixel shader to main.lua.

@zorg wouldn't that imply you have to draw everything twice?

@raidho36 Nice recap. One issue I see with CPU sorting is that batches need to be regenerated every frame, which is inconvenient from a performance standpoint as compared to static batches (e.g. from a map).

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 2:50 pm
by Ref
Gee!
Didn't know life was really this complex.
For those who don't know what they are talking about (like me), this is an example of what they think is very inefficient (and probably off topic).
Be interest in seeing what they come up with.

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 3:37 pm
by zorg
@pgimeno maybe it does, i'm with Ref with the don't know what they're talking about exactly group :3

(Assumptions were that stencil tests can be done in a variety of ways, so if that part was done correctly, and each sprite was only added to the stencil buffer with their own z value depended on the depth of them and any previously added value, basically testing against smaller ones and only drawing the area where the pixels' values are larger... or the exact opposite, whichever, then you basically have set up a neat depth buffer that you could use to render sprites in any order... then again i'm pretty sure it doesn't work like this and my approach probably has tons of issues, and it would indeed take multiple draws to do this, so it's probably not usable at all.)

Re: Depth buffer and slanted sprites?

Posted: Sun Apr 21, 2019 3:56 pm
by Nelvin
Thanks for the discard tip and sample.

I know the issues with alphablending.
CPU sorting in simple samples is of course easy but it is a much bigger problem and a performance issue in my case as it's a isometric game with a typical view of at least a few 1000 of tiles/objects and potentially hundreds of those constantly moving. Sorting in an iso view with moving objects and objects bigger than a single tile sadly does not work with a trivial sort based on a single Z value. There's also no way I know off, where I could create a stable sort order and just move objects around a bit (that's they way how I typically sort in games with a Stardew Valley alike perspective which I mostly used when developing for J2ME phones).

But sorting by itself is not the reason I'm asking (I've sovled this for my needs), my problem is that, on a typical map, on average, I can barely batch more than 8-10 quads in a draw call. My game uses lots objects, tiles, characters and the sorting simply depends on how the user builds his city, so there's no best way to group my sprites into my texture atlasses.
BUT if I could render just my terrain tiles and tileoverlays (roads etc.) first and then everything else, I could get rid of probably 50% of drawcalls as the constant switch between terrain and any random object/charcter is one of my biggest problems. For this to work, I'd need a depth buffer created for my rendered terrain tiles as these have a height and can cover objects/chars rendered in a second pass.

I'll have to think about this some more and see if discard(ing) fragments may help me to find a solution. As an additional benefit, this would actually allow me to have some static geometry for my tiles as only objects/chars move/animate each frame.