Camera3D support for Looking Glass displays

Recently I had the opportunity to experiment with a Looking Glass Go display. I used the opportunity to extend Camera3D’s functionality to create images in the correct format for displays made by this company.

These displays require you to render a few dozen images of the same scene at slightly different camera angles. The images are then arranged in a grid called a “Quilt”.

The screen then rearranges the pixels inside the lenticular display so that each eye gets a slightly different view of the scene. This creates the 3D effect.

Camera3D takes care of the camera positioning and image arrangements for you, making it easy for Processing and py5 users to create content for these displays.

An example sketch is below:

import camera3D.Camera3D;

Camera3D camera3D;

float rotX = 0; 
float rotY = 0;
float rotZ = 0;

void setup() {
  // default for Looking Glass Go is 11 columns by 6 rows and a frame size of
  // 372 by 682. This makes the quilt size 4092 by 4092.
  size(372, 682, P3D);
  pixelDensity(displayDensity());

  camera3D = new Camera3D(this);
  camera3D.setBackgroundColor(color(192));

  // the default quilt dimensions are 11x6 with an aspect ratio of 0.5625
  // these are the suggested settings for the Looking Glass Go
  // set the output location to an appropriate directory on your computer
  camera3D.renderLookingGlassQuilt().setQuiltDimensions(11, 6, 0.5625).setOutputLocation("/tmp/frames/");
  // 30 second recording at 30 fps
  camera3D.setFrameLimit(30 * 30);

  strokeWeight(2);
  stroke(0);
  fill(255, 255, 255);
}

void preDraw() {
  rotX += 0.5;
  rotY += 0.1;
  rotZ += 0.3;
}

void draw() {
  translate(width / 2, height / 2, 0);
  
  push();
  rotateX(radians(rotX));
  rotateY(radians(rotY));
  rotateZ(radians(rotZ));
  box(125);
  pop();

  int xSpacing = 100;
  int ySpacing = 200;
  int zSpacing = 100;
  for (int x = -xSpacing; x <= xSpacing; x += 2 * xSpacing) {
    for (int y = -ySpacing; y <= ySpacing; y += 2 * ySpacing) {
      for (int z = -zSpacing; z <= zSpacing; z += 2 * zSpacing) {
        push();
        translate(x, y, z);
        rotateX(radians(rotX));
        rotateY(radians(rotY));
        rotateZ(radians(rotZ));
        box(25);
        pop();
      }
    }
  }
}

Just curious, can you do that in Python? Resolution might be better.

Yes, Camera3D works in py5 also. You can find information about how to do that here:

The full sized quilt image is 4092x4092 pixels so I didn’t want to add it to my post. If you want to see that image you can find it with the link at the bottom of this page:

Is it my imagination or is python graphics resolution just plain sharper? To test we’d have to compare the java mode P3D version of your py5 code.

Hmmm, it shouldn’t be different, but if it is, please let me know!

Here’s a java mode version. Run the two side by side; there is a difference to my eye.

import camera3D.Camera3D;

Camera3D camera3D;

float rotX = 0;
float rotY = 0;
float rotZ = 0;

void setup() {
  size(400, 400, P3D);
  frameRate(30);
  strokeWeight(8);
  stroke(0);
  noFill();

  camera3D = new Camera3D(this);
  camera3D.setBackgroundColor(color(192));
  camera3D.renderDefaultAnaglyph().setDivergence(1);
}

void preDraw() {
  rotX += 0.8;
  rotY += 0.3;
  rotZ += 0.5;
}

void draw() {
  translate(width / 2, height / 2, -200);
  rotateX(radians(rotX));
  rotateY(radians(rotY));
  rotateZ(radians(rotZ));
  box(250);
}

Output:

Yes, that looks different to me too. The anti-aliasing is different.

You are using mac computer, right? If so, can you try adding pixelDensity(2); to your setup() function? That might make a difference.

I’m using MacOS Tahoe 26.5 M4.

Here it is with pixelDensity(2) added to setup():

Hmmm, it doesn’t look like it did anything. Did you add pixelDensity(2) to the Sketch right after the call to size()? If that isn’t it, I’m going to have to investigate more. I’m glad py5 gives great results, but I want to undrestand why!

I didn’t add it right after size(); it was about 2 or 3 lines down. However, I moved it to the second line in setup() and the result is the same. To avoid confusion I’m posting the code that I ran as well as the output that goes with that code. I’ve seen this before with other 3D demos that I created first in java mode and then translated to py5; they always seem to look better in py5.

Source code:

import camera3D.Camera3D;

Camera3D camera3D;

float rotX = 0;
float rotY = 0;
float rotZ = 0;

void setup() {
  size(400, 400, P3D);
  pixelDensity(2);
  frameRate(30);
  strokeWeight(8);
  stroke(0);
  noFill();

  camera3D = new Camera3D(this);
  camera3D.setBackgroundColor(color(192));
  camera3D.renderDefaultAnaglyph().setDivergence(1);
}

void preDraw() {
  rotX += 0.8;
  rotY += 0.3;
  rotZ += 0.5;
}

void draw() {
  translate(width / 2, height / 2, -200);
  rotateX(radians(rotX));
  rotateY(radians(rotY));
  rotateZ(radians(rotZ));
  box(250);
}

Output:

Oh! this is amazing. I have a Looking Glass Portrait and I’ll try soon. Thanks!

Fascinating. I just looked at py5’s code and Processing’s code and I don’t see any relevant differences. I do note that py5 will call pixelDensity(displayDensity()); on your behalf, but to the best of my knowledge adding that to a Java Sketch should then yield results identical to py5. I’ll have experiment with this some more on my laptop to see what’s going on. Thanks for pointing this out.

Oh wonderful! It makes me very happy to hear that someone will benefit from this!

The source code was run through different editors: java mode was run by Processing IDE and py5 was run by Thonny. Possibly that makes a difference? Resolution still good when py5 source code is run from command line:

Can you share the code you used for this Python version? You previously posted your Java code.

For my MacOS computer here I see I get the same image for Python and Java. But for this machine I get a warning when I call pixelDensity(2) because this display doesn’t do that. You didn’t mention getting a warning, so your computer must have one of the high density displays.


This is really cool — great to see more Processing support for Looking Glass!

Coincidentally, I’ve also been working on a Processing 4 library for Looking Glass:

https://discourse.processing.org/t/processing2hologram-a-processing-4-library-for-looking-glass-holographic-displays/49169

It’s aimed at real-time, interactive sketches, and includes a few examples and a preview mode for development without the display connected.

Really nice to see another approach to this, especially with py5 support as well! I imagine there are quite a few different challenges when handling the same multiview/quilt pipeline across C++/Java and Python — looking forward to more discussion and exchange around this!

I got the py5 code from How to Use Camera3D — py5 documentation

import py5
from camera3D import Camera3D

rot_x = 0
rot_y = 0
rot_z = 0

def setup():
    py5.size(400, 400, py5.P3D)
    py5.frame_rate(30)
    py5.stroke_weight(8)
    py5.stroke(0)
    py5.no_fill()

    camera3D = Camera3D(py5.get_current_sketch())
    camera3D.setBackgroundColor(py5.color(192))
    camera3D.renderDefaultAnaglyph().setDivergence(1)
 
def pre_draw():
    global rot_x, rot_y, rot_z
    rot_x += 0.8
    rot_y += 0.3
    rot_z += 0.5
    
def draw():
    py5.translate(py5.width / 2, py5.height / 2, -200)
    py5.rotate_x(py5.radians(rot_x))
    py5.rotate_y(py5.radians(rot_y))
    py5.rotate_z(py5.radians(rot_z))

    py5.box(250)
    
py5.run_sketch()

Wow, really? This is awesome! Can you tell me more about how this works? Camera3D can’t do real-time Sketches for Looking Glass displays. I thought that to do so I’d need complex shaders. I looked at your source code and I see some C++ code but no shaders. Can you tell me more about your approach? What kind of frame rate can you get now?

I don’t think the editor should matter here. But I don’t have much of a clue why the renderer works better with py5 compared to Processing. I poured over the code and the only thing I noticed that is different is that with py5, the pixelDensity() call takes place before the call to size(), not after. It is also in a separate settings() method. Can you create a separate settings() function and move the pixelDensity() call and size() calls there?

void settings() {
  pixelDensity(displayDensity());  // I assume displayDensity() returns 2?
  size(400, 400, P3D);
}

My monitor is a big screen LG (and high density I thought) but it returns pixelDensity = 1 so that would explain the poor resolution in java mode apparently. I tried setting the pixel density to 2, but it raised an exception and said it was not available. My desktop setup is a mac mini with a bring your own monitor; it is not a notebook or iMac.

What I cannot understand is why the same thing doesn’t happen with py5. It runs ok ‘as is’ with py5 source code and I don’t have to mess with pixelDensity (you have stated that your code took care of that). Why is it that you can set it but I can’t using java mode in the Processing IDE (P3D, openGL renderer)?

Console output:

display count needs to be implemented for non-AWT
pixelDensity = 1
AWT disabled, displayWidth/displayHeight will be 0
display count needs to be implemented for non-AWT
display count needs to be implemented for non-AWT
pixelDensity(2) is not available for this display

Source code:

import camera3D.Camera3D;

Camera3D camera3D;

float rotX = 0;
float rotY = 0;
float rotZ = 0;

void settings() {
  println("pixelDensity =", displayDensity());
//  pixelDensity(displayDensity());  // I assume displayDensity() returns 2?
  pixelDensity(2);
  size(400, 400, P3D);
}

void setup() {
  frameRate(30);
  strokeWeight(8);
  stroke(0);
  noFill();

  camera3D = new Camera3D(this);
  camera3D.setBackgroundColor(color(192));
  camera3D.renderDefaultAnaglyph().setDivergence(1);
}

void preDraw() {
  rotX += 0.8;
  rotY += 0.3;
  rotZ += 0.5;
}

void draw() {
  translate(width / 2, height / 2, -200);
  rotateX(radians(rotX));
  rotateY(radians(rotY));
  rotateZ(radians(rotZ));
  box(250);
}

Output: