Cull faces in P3D

Hi! I was wondering if it is possible to set a face, to only be visible from one angle, as it would increase performance also known as culling faces. Thanks for all your advice :slight_smile:

Im programming a voxel engine (never has been done before, i know) and this Project called AlpahBlock inspired me, it looks quiet cool and it was made with processing and the performance is also great.

-Libby

2 Likes

That is a great question I wish I had more knowledge about! Managing to cull unseen faces & edges is also very useful for people who want to export vector graphics for pen-plotters…

2 Likes

Face culling can be done by first creating triangles with consistent “winding” of each triangle. This means that the order of the three vertices in each triangle will be consistently counter-clockwise, for example.

Once you have consistent winding, then you can examine the normal vectors of each face. This is usually done by taking a cross product of two vectors. If the triangle vertices are A, B, and C, you take the cross product of AB with AC. You can then look at the sign of the z value to determine if it is front-facing or back-facing.

2 Likes

Processing doesn’t seem to use face culling anywhere internally that I can find, but, frustratingly, in its frame cleanup code, it explicitly disables face culling. You can enable it yourself as long as you do it every frame.

Note that the culling here is being done by OpenGL, so it won’t help with exporting to vector formats. And it’s also quite delicate. It’s likely that mixing in other Processing drawing calls might well, disable the culling again.

Press a key to cycle through no culling, back-face culling, and front-face culling.

PJOGL pgl;
int cullMode = 0;

void setup() {
  size( 800, 800, P3D );
  pgl = (PJOGL) beginPGL();
}

void drawSide() {
  beginShape();
  normal( 0, 0, 1 );
  vertex( -1, -1, 1.5 );
  vertex( 1, -1, 1.5 );
  vertex( 1, 1, 1.5 );
  vertex( -1, 1, 1.5 );
  endShape(CLOSE);
}

void draw() {
  background(64);
  translate( width/2, height/2 );
  scale( height/5.0 );

  lights();

  rotateX(0.01*frameCount);
  rotateY(0.013*frameCount);

  strokeWeight( 0.02 );
//  noStroke();
  fill(255);

  if( cullMode == 1 ) {
    pgl.enable( PGL.CULL_FACE );
    pgl.cullFace( PGL.BACK );
  } else if( cullMode == 2 ) {
    pgl.enable( PGL.CULL_FACE );
    pgl.cullFace( PGL.FRONT );
  }

  pushMatrix();
  for( int i=0; i<4; i++ ) {
    drawSide();
    rotateY( TAU/4 );
  }
  rotateX( TAU/4 );
  drawSide();
  rotateX( TAU/2 );
  drawSide();
  popMatrix();
  
  fill( 255, 255, 0 );
  box( 1.4, 1.2, 1.3 );

}

void keyPressed() {
  cullMode = (cullMode+1) % 3;
}
2 Likes

This one lets you check the performance difference. With N=500, this renders approx. 500*500 * 3 quads * 2 surfaces. Each quad is 2 triangles, so around 3 million triangles total. On my 7 year old machine, I get around 115 fps with no culling and 120 fps with back face culling.

Front face culling prevents the frame rate text from showing which is curious since it worked on my previous example. I would have expected it to not show since it would be text rendered onto a front-facing quad.

So culling takes it from 115 to 120 fps at the risk of other things not working. Definitely put your voxels together into PShapes rather than rendering them as separate box() calls. The speedup is huge.

int N = 500;
PShape s1, s2;

PShape genGrid( int N, float [][] data ) {
  float c = 2.6*N/32;
  PShape s = createShape();
  s.beginShape(QUADS);
  s.noStroke();
  for( int i=0; i<N; i++ ) {
    // -Y side
    float z2 = data[0][i];
    s.fill( data[0][i]/c, 0.5, 1 );
    s.vertex( i+1, 0, 0 );
    s.vertex( i+1, 0, z2 );
    s.vertex( i,   0, z2 );
    s.vertex( i,   0, 0 );
  }
  for( int j=0; j<N; j++ ) {
    float z0, z1, z2;
    // -X side
    z1 = data[j][0];
    s.fill( data[j][0]/c, 0.5, 1 );
    s.vertex( 0, j,   0 );
    s.vertex( 0, j,   z1 );
    s.vertex( 0, j+1, z1 );
    s.vertex( 0, j+1, 0 );
    
    for( int i=0; i<N; i++ ) {
      z0 = z1;
      z1 = data[j][i+1];
      z2 = data[j+1][i];

      // +Z top
      s.fill( data[j][i]/c, 0.5, 1 );
      s.vertex( i,   j,   z0 );
      s.vertex( i+1, j,   z0 );
      s.vertex( i+1, j+1, z0 );
      s.vertex( i,   j+1, z0 );

      // +X side
      s.fill( data[j][i+(z0>z1?0:1)]/c, 0.5, 1 );
      s.vertex( i+1, j,   z0 );
      s.vertex( i+1, j,   z1 );
      s.vertex( i+1, j+1, z1 );
      s.vertex( i+1, j+1, z0 );

      // +Y side
      s.fill( data[j+(z0>z2?0:1)][i]/c, 0.5, 1 );
      s.vertex( i+1, j+1, z0 );
      s.vertex( i+1, j+1, z2 );
      s.vertex( i,   j+1, z2 );
      s.vertex( i,   j+1, z0 );
    }
  }
  s.endShape();
  return s;
}

PJOGL pgl;
int cullMode = 0;

void setup() {
  size( 1600, 1200, P3D );
  frameRate(1000);
  pgl = (PJOGL) beginPGL();
  colorMode( HSB, 1, 1, 1 );
  // leave a strip of 0 on the right and bottom
  float [][] data = new float[N+1][N+1];
  for( int j=0; j<N; j++ )
    for( int i=0; i<N; i++ ) {
      float x = 2.*i/N-1, y = 2.*j/N-1,
        a = atan2( y, x ), r = sqrt(x*x+y*y);
      data[j][i] = (1.1+cos(TAU*2*r+3*a) + random(.5)) * N/32;
    }
  s1 = genGrid( N, data );
  for( int j=0; j<N; j++ )
    for( int i=0; i<N; i++ ) {
      float x = 2.*i/N-0.5, y = 2.*j/N-1.2,
        a = atan2( y, x ), r = sqrt(x*x+y*y);
      data[j][i] = (1.1+cos(TAU*2*(r+0.1*sin(5*a))) + random(.5)) * N/32;
    }
  s2 = genGrid( N, data );
}

void draw() {
  background( 0.6, 1, 0.25 );

  if( cullMode == 1 ) {
    pgl.enable( PGL.CULL_FACE );
    pgl.cullFace( PGL.BACK );
  } else if( cullMode == 2 ) {
    pgl.enable( PGL.CULL_FACE );
    pgl.cullFace( PGL.FRONT );
  }

  lights();
  camera( 0, 0, N*1.2, 0, 0, 0, 0, 1, 0 );
  perspective( PI/3, 1.*width/height, N/4, N*4 );
  push();
  rotateY( TAU/5. );
  translate( 0, 0, -N*0.35 );
  rotateX( TAU/12. );
  rotateZ( frameCount/115. );
  shape( s1, -N/2, -N/2 );
  pop();
  push();
  rotateY( -TAU/5. );
  translate( 0, 0, -N*0.35 );
  rotateX( TAU/12. );
  rotateZ( frameCount/115. );
  shape( s2, -N/2, -N/2 );
  pop();
  fill( 0, 0, 1 );
  camera();
  perspective();
  text( nf( frameRate, 0, 2 ), 10, 20 );
}

void keyPressed() {
  cullMode = (cullMode+1) % 3;
}
2 Likes