p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

Fix transparency ordering in WebGL

Open Vamoss opened this issue 5 years ago • 13 comments

Nature of issue?

  • [X] Found a bug
  • [ ] Existing feature enhancement
  • [ ] New feature request

Most appropriate sub-area of p5.js?

  • [ ] Color
  • [ ] Core/Environment/Rendering
  • [ ] Data
  • [ ] Events
  • [ ] Image
  • [ ] IO
  • [ ] Math
  • [ ] Typography
  • [ ] Utilities
  • [X] WebGL
  • [ ] Other (specify if possible)

Which platform were you using when you encountered this?

  • [ ] Mobile/Tablet (touch devices)
  • [X] Desktop/Laptop
  • [ ] Others (specify if possible)

Details about the bug:

  • p5.js version: 0.8.0
  • Web browser and version: Chrome 74.0.3729.131
  • Operating System: Windows 10
  • Steps to reproduce this:

setAttributes('depth', false); doesnt seen to be working:

setAttributes('depth', false);
createCanvas(windowWidth, windowHeight, WEBGL);

depth

Work around:

let renderer = createCanvas(windowWidth, windowHeight, WEBGL);
renderer.drawingContext.disable(renderer.drawingContext.DEPTH_TEST);

depth2

Vamoss avatar May 09 '19 17:05 Vamoss

Welcome! 👋 Thanks for opening your first issue here! And to ensure the community is able to respond to your issue, be sure to follow the issue template if you haven't already.

welcome[bot] avatar May 09 '19 17:05 welcome[bot]

Actually, by disabling the Depth, it makes the transparency from images work(the example above). But when I draw some semi-transparent shapes in between the images, the depth is not right: image

I am trying to accomplish a fog effect...

Vamoss avatar May 09 '19 18:05 Vamoss

@Vamoss Can you provide a code which reproduces this bug?

vedhant avatar May 19 '19 12:05 vedhant

Hi @vedhant, sorry for the late answer, I was not notified of your answer by mail... Here is the working code: https://www.openprocessing.org/sketch/709566

Line 7 did the trick: renderer.drawingContext.disable(renderer.drawingContext.DEPTH_TEST);

Vamoss avatar Jul 03 '19 19:07 Vamoss

Is there ever a case when a user would want there to be no depth buffer and still enable depth testing? If not, we should only enable DEPTH_TEST when the 'depth' attribute is true.

stalgiag avatar Sep 02 '19 11:09 stalgiag

Hi @stalgiag. Again I was not notified about an answer in this topic, sorry for the delay.

Once in a while I got this issue again.

Here I am using 1.2 version:

renderer.drawingContext.disable(renderer.drawingContext.DEPTH_TEST); result:

I don't think it is a DEPTH_TEST issue, seems like a transparency related issue.

Link: https://www.openprocessing.org/sketch/913474

Vamoss avatar Jun 08 '20 10:06 Vamoss

I just discovered there is a name to solve it Order independent transparency.

@tsherif implemented it here https://tsherif.github.io/webgl2examples/oit-dual-depth-peeling.html

Unfortunately, it goes beyond my knowledge.

Vamoss avatar Jun 08 '20 11:06 Vamoss

Hi!

I don't now anything about p5, but I can provide the maintainers with some info that might be useful: The problem with alpha masked objects (i.e. when you're hiding pixels by setting alpha to 0, but other pixels on the same object are opaque) is that the masked pixels still write to the depth buffer. However, if you turn off depth testing, the opaque parts won't occlude each other.

There are a couple of solutions I can imagine for this. Depth peeling, mentioned by @Vamoss, might help but is probably overkill. One option would be to explicitly have an alpha cutout option that instead of rendering fragments as transparent, discards them in your shader, e.g.

if (color.a < 0.5) {
    discard;
}

Caveat is that this might make the edges look rough.

For the cylinder scene by @Vamoss where it's front-facing surfaces occluding back-facing surfaces, you could draw the surfaces in two passes using face culling to turn off one side or the other. In pure WebGL, this would look like:

gl.enable(gl.CULL_FACE);
gl.cullFace(gl.FRONT);
// Draw
gl.cullFace(gl.BACK);
// Draw

Hope that helps!

tsherif avatar Jun 08 '20 11:06 tsherif

I've been going through consolidating a number of related issues into this one. Some suggestions from the various issues to help address this issue:

  • Create documentation around 3D transparent texture techniques
  • Use discard in shaders to prevent occlusion from fully transparent pixels (partial transparency will still be an issue)
  • Implement an order-independent transparency algorithm such as depth peeling (a bigger system-level change. may also have performance implications, and may need a WebGL version upgrade to support writing out multiple buffers at once)
  • Use CULL_FACE to draw back faces first (transparency between multiple objects will still be an issue)

Let me know if there are more suggestions on how to help mitigate this issue!

davepagurek avatar Jan 16 '23 11:01 davepagurek

@davepagurek Any updates on this one?

rdsilver avatar Jul 14 '23 06:07 rdsilver

@rdsilver The biggest update is that p5 1.7.0 now supports WebGL 2, so it could potentially now be feasible to use an order-independent transparency algorithm. To decide whether or not to do so, we probably need to test the performance tradeoff. If it looks like it'll take a big performance hit, then we'd need to evaluate whether or not it would be feasible to have it as a togglable feature without making the code too complicated or unmaintainable.

As a stopgap, we can also document the issue maybe with a Learn tutorial on the p5 reference explaining what's going on and manual workarounds one can use.

davepagurek avatar Jul 24 '23 12:07 davepagurek

@davepagurek I'm not sure if its related, but I isolated a transparency / webgl rendering issue in these 2 sketches:

https://editor.p5js.org/kevcx2/sketches/6QfXUHSa5 https://editor.p5js.org/kevcx2/sketches/E7oGGeEp8

In this case, the ball in front of the ring has an alpha value of 0, but it still renders over the ring: Screenshot 2024-01-24 at 3 28 08 PM

Edit: I did some testing, and the issue is introduced v1.0.0. The sketches above work when using v0.10.2

kevcx2 avatar Jan 24 '24 23:01 kevcx2

I think this is the same issue as above: to do proper transparency blending, you'll have to draw transparent objects last. In your case, I think drawing the ellipse after the orbiter will do the trick:

  addObject(bg)
  addObject(o1) // Previously this was the last entry
  addObject(e1)

Otherwise, the transparent object is drawn first, but WebGL depth testing just records the depth that geometry was drawn at, not whether or not it's transparent, so subsequent objects will see that something else is in front of it, and not draw.

davepagurek avatar Jan 25 '24 16:01 davepagurek