Transparency fails looking at backside of 3D shape

Hi

In 3D (WEBGL) I’m drawing a rect with a color that includes an alpha. When I look at that shape from the “front” the alpha works as expected… and I can adjust the rect’s transparency. However, when I rotate the rect so I’m looking at from behind, the alpha stops working, and it’s a solid square.

Does anyone know how I might be able to fix this?

Thanks,

david,

Hello, @dy1, and welcome to the Processing Forum!

That’s interesting. Could you please post your code, so we can test it?

Here’s the code…

let windowSize = 0; // the width & height of the window


function setup() {
  // Grab the smaller of the window sizes and use that as the canvas size.
  windowSize = windowWidth < windowHeight ? windowWidth : windowHeight;
  print("windowSize: " + windowSize);
  createCanvas(windowSize, windowSize, WEBGL);
  
  let fov = PI/3.0;
  let cameraZ = (height/2.0) / tan(fov/2.0);
  perspective(fov, float(width)/float(height), cameraZ/2.0, cameraZ*2.0);
}


let rr = 0; 
let rrShift = 0.005;
let aa = 128; 
function draw() {
  rectMode(CORNER)

  translate(-width/2, -height/2, -400);
  
  ambientLight( 255, 255, 255);
  normalMaterial();
  background(0);
  fill( 255, 0, 0, aa);
  noStroke();

  rotate(rr, [1,1,0]);
  rr += rrShift; 

  rect( 100, 100, 200);

  translate( 0, 0, 50);
    
  rect( 150, 100, 200);
}

Thanks for the code.

To make the issue more obvious, I added this line prior to the drawing of the second rectangle:

  fill(0, 255, 0, aa);

Following is a screen capture of a portion of a front view scene:

front

Following is a screen capture of a portion of a back view scene:

back

Yes, the existence of the problem is quite evident, but I’m still trying to figure out how to fix it.

1 Like

This could be related: Semi-transparent objects in WebGL mode make the background behind them transparent · Issue #5451 · processing/p5.js · GitHub

Well, it’s not related to the backface/z-order issue, but it does make the colors appear more correct for the background.

2 Likes

I believe this is a problem that is fundamental to OpenGL/WebGL and how translucent colors are blended: Transparency Sorting - OpenGL Wiki

I think the problem has to do with the draw order. If you were to flip the order:

  rect( 150, 100, 200);
  translate( 0, 0, -50);
  rect( 100, 100, 200);

You’ll notice that the problem is reversed…now it’ll only look right when viewed from behind. I experienced this for one of my projects where I created a forest of 2D transparent planes: Fantastical Terrarium - Optimized - OpenProcessing

Specifically this is the code I used to sort, you’ll need to figure out the camera position:

    // Sort clones by distance
    const sortedTrees = trees.map(tree => {
      const point = createVector(tree.x, tree.y, tree.z)
      return {
        tree,
        point,
        // Calculate the distance from the current object to the camera
        dist: point.copy().sub(camPos).dot(camDir), 
      }
    })
    .sort((a, b) => b.dist - a.dist)

    // Trees are now sorted by distance
    sortedTrees.forEach(obj => obj.tree.draw())

It’s kind of a complicated problem and I think it would require restructuring the way your code works but basically with transparent objects in p5 (in my experience) you’ll need to draw them in order of distance from camera

2 Likes

Hello, @Metamoar, and welcome to the Processing Forum!

Thanks for the Fantastical Terrarium. It is indeed fantastic!

Also thanks for the observation regarding the draw order. Shall we consider the demonstrated solution to be a workaround for a bug, or shall we consider WebGL to be working as intended?

I believe this is Web GL working as intended. When Web GL draws a fragment, it checks the depth buffer value for that fragment, if a previously drawn fragment is closer to the camera then it will take precedence even if it is transparent.

2 Likes

I tried implementing @Metamoar 's sort code – but it doesn’t seem to work. But maybe I’m doing something wrong… Here’s my code…

let windowSize = 0;

let rectangles = [];

function setup() {
  windowSize = windowWidth < windowHeight ? windowWidth : windowHeight;
  print("windowSize: " + windowSize);
  createCanvas(windowSize, windowSize, WEBGL);
  
  let fov = PI/3.0;
  let cameraZ = (height/2.0) / tan(fov/2.0);
  perspective(fov, float(width)/float(height), cameraZ/2.0, cameraZ*2.0);

  rectangles[ 0] = new Rectangle( 100, 100, 100, 200, 255, 0, 0, 128);
  rectangles[ 1] = new Rectangle( 150, 150, 150, 200, 0, 255, 0, 128);
}

class Rectangle {
  constructor( x, y, z, width, r, g, b, alpha) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.width = width;
    this.r = r;
    this.g = g;
    this.b = b;
    this.alpha = alpha;
  }

  tick() {
    fill( this.r, this.g, this.b, this.alpha);
    noStroke();
    translate( 0, 0, this.z);

    rect( this.x, this.y, this.width);
  }
}


let rr = 0; 
let rrShift = 0.005;

function draw() {
  rectMode(CORNER)

  translate(-width/2, -height/2, -400);
  
  ambientLight( 255, 255, 255);
  normalMaterial();
  background(0);

  rotate(rr, [1,1,0]);
  rr += rrShift; 

  const sortedRects = rectangles.map(rectangle => {
      const point = createVector(rectangle.x, rectangle.y, rectangle.z)
      return {
        rectangle,
        point,
        // Calculate the distance from the current object to the camera
        dist: point.copy().sub(0, 0, (height/2) / tan(PI/6)).dot(0,0,0), 
      }
    })
    .sort((a, b) => b.dist - a.dist)

    // Trees are now sorted by distance
    sortedRects.forEach(obj => obj.rectangle.tick())
}


I think the problem is that they’re not actually being sorted, although I’m not sure why. I think this might be wrong as the distance is always 0: point.copy().sub(0, 0, (height/2) / tan(PI/6)).dot(0,0,0)

However based on @KumuPaul comment I noticed that adding this to setup seemed to fix it:

drawingContext.disable(drawingContext.DEPTH_TEST)

I don’t know if this is a good solution or if this will cause other problems, but at least for your sketch it should work.

2 Likes

Yes! I had to use the following line instead

  canvas.getContext('webgl').disable(canvas.getContext('webgl').DEPTH_TEST)

But overall it seems to fix the issue!

And you’re right - the sort was unnecessary.

You need to take your translation and rotation into account when sorting your geometry and sort things in terms of distance to the camera. You also need to undo your translations when drawing the rectangles (using push and pop). Here’s the code fixed and with a bit of debugging:

One thing to notice is that in some cases there is a brief moment where the red square appears opaque (may only happen for certain window sizes). I believe that this is because there is a point where the center of the red square is actually further away than the center of the green square, however some portion of the red square is overlapping & in front of some portion of the green square. This is going to be a potential problem for any geometry: it is impossible to sort geometry perfectly whenever the discrete piece of geometry you are sorting isn’t perfectly flat in the perceived z dimension (i.e. relative to the camera).

3 Likes