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;
}
```