Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411505 Posts in 69374 Topics- by 58429 Members - Latest Member: Alternalo

April 25, 2024, 08:06:59 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)OpenGL translucency, I programmed myself into a corner
Pages: [1]
Print
Author Topic: OpenGL translucency, I programmed myself into a corner  (Read 894 times)
Deckhead
Level 1
*



View Profile WWW
« on: September 23, 2018, 05:21:40 AM »

In the strictly 2D engine I've made I'm doing something like this:

  • Draw calls are submitted to the engine in the order they should be drawn
  • The engine is keeping an ongoing depth value that it updates after every call
  • All of the submitted calls, including the current depth at the time, are batched together
  • The batching is done on whether it's a color rect, a texture rect, or a request to draw text
  • All of these batches are then submitted to OpenGL. All the color rects are a single call, every texture and it's related draw calls is it's own call, and finally all the text drawing is submitted in one call

This means I'm only making a few large draw calls per frame.

This was working well for me, but now I've hit a problem with translucency. Because all of the color rects are drawn, followed by each texture etc, none of the colours can have any alpha blending if they're to be drawn over a texture.

I know I could re-engineer the engine to not batch everything up and draw as requested, but I really liked the idea of a couple large draw calls. I'm wondering if anyone has any idea of another way to handle the translucency.

Is there some creative way of using offscreen buffers and potentially a couple of passes to handle this? I thought maybe for each set of draw calls (one for colours, followed by one for each texture) could result in a buffer, the buffer could contain the resulting colour+alpha plus some depth information; then I guess all of these buffers could be combined. I can't quite get it right in my head, and it strikes me that if there's a lot of textures, I'm going to create too many offscreen buffers.
Logged

qMopey
Level 6
*


View Profile WWW
« Reply #1 on: September 23, 2018, 11:17:59 AM »

Why do you have a rolling depth? That part sounds really odd to me. In my own personal code I just submit draw calls to GL, and that's it. It's 100% up to the user to decide what goes into the draw call. Each sprite has depth, but text does not (I just submit text draw calls last most of the time). The sprite depths are only used for CPU sorting.

This was working well for me, but now I've hit a problem with translucency. Because all of the color rects are drawn, followed by each texture etc, none of the colours can have any alpha blending if they're to be drawn over a texture.

Why not? What's wrong with drawing some rects, followed by some textures, followed by some more rects (that would appear in front of the textures)?

Perhaps you're relying too heavily on the depth-buffer on the GPU? I'm still not really sure what your problem is.
Logged
Deckhead
Level 1
*



View Profile WWW
« Reply #2 on: September 23, 2018, 06:19:51 PM »

What's being drawn isn't being drawn in the order that the user requested the items to be drawn (because they are batched by the engine).

The rolling depth is so the resulting batched draws renders the textures and rects in the same "depth order" as they were submitted. I use it to indicate what order the rects were originally submitted.

Because blending is order dependent (not depth dependent), and the batching has changed the order, the results of semi-transparent rects being drawn "on top" of others in the submission order is wrong (submission order is the users draw order, which is changed for the batching).

I hope that clears it up.
Logged

qMopey
Level 6
*


View Profile WWW
« Reply #3 on: September 23, 2018, 06:59:44 PM »

OK thanks for explaining that. I think I understand your situation a little better.

Have you considered performing batch sorting operations more than once, issuing more than one group of draw calls? This is the way my own code is setup. For games typically we want to submit thing to draw into a big buffer. Then we sort this buffer, which can result in multiple draw calls.

Then one more layer of abstraction can be placed onto this system, where the game can push geometry into different buffers. Each "buffer" is sorted, and then cut up into necessary batches. Each batch is rendered with a draw call. With this scheme you can fit in transparent geometry into whichever buffer you like, and they will get sorted into their own batch and submit after the previous batches. Each "buffer" could be for a major feature of the game, like the in-game UI, the pause menu, the game geometry itself, the background, and potentially other stuff as well.

This is pretty much how my own personal code works. From what I gather, your setup is similar but only has one "buffer".

The way my graphics API works is by exposing a draw call structure that contains a shader, texture references, uniforms, and the geometry to draw. These draw calls map one to one with something like glDrawPrimitives. The draw calls can be filled out in any manner of methods. The rest of the game abstracts the draw call API with a few different systems. Each system performs sorting and batching as necessary. There is not one "global system to submit geometry" to. There are a few different systems and they control 100% exactly how each draw call is formed. Basically I'm saying it's not a good idea to try and force your entire engine to generate renderable geometry through a single generic API, and instead you should expose low level APIs that get consumed by higher level APIs to expose specific features. This is called API layering. The point is to avoid generic APIs and instead expose custom and very specific ones for each feature needed.

I have one of these draw call generating systems for sprites and sprite batching, a bunch of random smaller ones for very specific shader effects, and one for full-screen post-processing effects. Each system generates draw calls very differently, and each one performs some kind of sorting + batching internally. Each one knows how to handle transparency in their own special way.

Does this help? If not I'm happy to discuss more to try and help but would need a little more info Smiley
« Last Edit: September 23, 2018, 07:08:47 PM by qMopey » Logged
Deckhead
Level 1
*



View Profile WWW
« Reply #4 on: September 24, 2018, 05:53:51 AM »

Your engine sounds a lot more in-depth than mine. All I've got is some basic "draw a coloured rectangle", "draw a texture region" and "draw some text" functions.

Internally for example, the Blit(const Color& c, const Region& r) calculates the four vertices needed for this quad, including the current depth, the requested color and position; and also calculates the 6 indices and adds it all to a buffer. The currentColorRectIndex is then incremented so I know later how many rects there are.

The Blit(const TextureKey& t, const Region& src, const Region& dst) does the same thing, but also stores some UV coords. These are stored in a separate buffer.

Then, when the user has submitted all their draw calls they call Render on the engine. This then takes these buffers, the vertex buffer + the depth buffer + the indices buffer and DrawElements the requested number of items.

So all the color rects are drawn. Then all the textures are drawn. So if a semi-transparent color is draw by the user AFTER a texture, the underlying texture color won't come through.

One thing I didn't mention above, is that the texture rect calls are submitted per texture. So if I requested 16 blits of TextureA, all 16 are submitted to the GPU at once. I do it this way because all renders using a shader are done without changing back to the shader again later in the same frame, and all calls for a given texture are made at once, so textures aren't swapping back and forth.

With all this in mind, I'm not sure how I could internally sort these buffers as they are all unique for the type of rendering required (color rect vs texture rect).
Logged

Columbo
Level 0
***


View Profile
« Reply #5 on: September 24, 2018, 10:07:40 AM »

1. You could let go of the batching, i.e. if you render two things in a row with the same texture then you batch them, but if you switch texture you always start a new batch rather than add to an old one. Your draw call batching might be a bit of a premature optimization (particularly if you're targeting desktop) and the increase in draw call count might not be a problem.
2. You could do the above but try to be cleverer about it by tracking bounding boxes of areas of the screen that are covered. So if you render a quad with Texture1, then Texture2, then Texture1 again, you can batch the two Texture1 quads together as long as the Texture2 quad doesn't overlap the second Texture1 quad. I suspect the complexity of this is not worth it (both the runtime cost and the code complexity).
3. You could do #1 but mitigate the increase in draw call count by carefully combining your textures into large atlases in a configuration that minimizes swaps.
4. You could do #1 but have all of your sprite textures atlassed into a single massive texture array. If you can live with only targeting hardware with array texture support (probably not hard), you could render everything in one single batch.
Logged

qMopey
Level 6
*


View Profile WWW
« Reply #6 on: September 24, 2018, 10:16:26 AM »

If you have the time, could you post a couple images, one showing the problem, and one showing what you wan to do?
Logged
Deckhead
Level 1
*



View Profile WWW
« Reply #7 on: September 24, 2018, 10:31:50 PM »

Hopefully this illustrates the problem better than words.

Logged

qMopey
Level 6
*


View Profile WWW
« Reply #8 on: September 25, 2018, 12:21:50 AM »

That’s a great picture. I’m pretty sure the only thing you can do is to somehow use more draw calls.
Logged
Deckhead
Level 1
*



View Profile WWW
« Reply #9 on: September 25, 2018, 04:03:26 AM »

That’s a great picture. I’m pretty sure the only thing you can do is to somehow use more draw calls.

That's what I was afraid of. I went ahead and updated so that everything draws as the user requests, rather than batching. So everything is working as expected.

But I got curious and so read a bit about Order-Independent Blending, apparently it's a thing, see http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html. I don't really have any interest in checking it out right now, but it does look interesting.
Logged

Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic