All chapters
Cookbook · Chapter 3

Drawing in 3D — lights, camera, action

The third dimension — primitives, lighting, textures, meshes, and moving the camera.

Understanding 3D space

Ask for a 3D-capable window with the P3D renderer. The Z axis is added to every coordinate: positive Z comes toward the viewer. translate(), rotate*(), and scale() now take three axes.

void settings() {
    size(800, 600, P3D);
}

Drawing 3D primitives

void draw() {
    background(0);

    translate(width / 2, height / 2, 0);
    rotateY(frameCount * 0.01f);
    box(160);                 // a cube; box(w, h, d) for a cuboid

    sphereDetail(24);         // facets of the next sphere
    sphere(100);
}

Wrap any 3D object in pushMatrix() / popMatrix() so its transform does not leak into the next one:

pushMatrix();
translate(232, 192, 0);
rotateY(0.5f);
box(160);
popMatrix();

Using lights

Call lights() near the top of draw() for default lighting — it must be re-issued every frame. noLights() turns lighting back off.

void setup() {
    noStroke();
    fill(255);
}

void draw() {
    background(0);
    lights();

    translate(width / 2, height / 2, 0);
    sphere(120);

    noLights();
}

Mixing 2D and 3D

2D primitives live in the same space — they just sit on the z = 0 plane. Draw a flat HUD with debug_text() over a rotating 3D scene, or push into Z before drawing a 2D shape:

fill(0);
debug_text("FPS: " + nf(frameRate, 1), 10, 10);   // flat overlay

pushMatrix();
translate(width / 2, height / 2, -200);            // 2D square, pushed back in Z
rotateX(frameCount * 0.02f);
square(0, 0, 100);
popMatrix();

Triangle and quad strips

Strip modes share vertices between adjacent faces, so a surface needs far fewer points than listing every triangle:

beginShape(TRIANGLE_STRIP);
vertex(120, 300); vertex(160, 80);
vertex(200, 300); vertex(240, 80);
vertex(280, 300); vertex(320, 80);
endShape();

beginShape(QUAD_STRIP);
vertex(120, 80); vertex(120, 300);
vertex(200, 80); vertex(200, 300);
vertex(340, 80); vertex(340, 300);
endShape();

Using textures

Map an image across the vertices of a shape with texture() plus per-vertex UV coordinates (the two extra numbers on each vertex()):

PImage* tex;

void setup() {
    tex = loadImage("grid.png");
    textureMode(NORMAL);          // UVs run 0..1
}

void draw() {
    beginShape();
    texture(tex);
    vertex(0,   0,   0, 0, 0);
    vertex(200, 0,   0, 1, 0);
    vertex(200, 200, 0, 1, 1);
    vertex(0,   200, 0, 0, 1);
    endShape(CLOSE);
}

Loading and drawing meshes (OBJ)

loadOBJ() returns vertices you load into a VertexBuffer, then draw with mesh():

#include "VertexBuffer.h"

VertexBuffer* model;

void setup() {
    hint(ENABLE_DEPTH_TEST);
    model = new VertexBuffer();
    model->add_vertices(loadOBJ("Panda.obj"));
}

void draw() {
    background(216);
    model->set_shape(isMousePressed ? LINES : TRIANGLES);

    translate(width / 2, height / 2);
    scale(75);
    mesh(model);
}

Using the 3D camera

camera() with no arguments resets to the default view. With nine arguments it places the eye, the point it looks at, and which way is up:

camera(0, cameraDistance, 0,   // eye position
       lookAtX, 0, lookAtZ,    // what to look at
       0, -1, 0);              // up vector

perspective() and ortho() switch between a perspective and an orthographic projection.