3D with P3D
Unlock Processing's P3D renderer to build, light, texture, and animate a 3D scene with a spinning planet, orbiting moon, and OBJ model loading.
Processing’s default renderer is a 2D canvas. Passing P3D as the third argument to size() switches to OpenGL-backed 3D rendering and unlocks a completely different set of drawing primitives, camera controls, and lighting APIs. This tutorial covers every major 3D feature, ending with a textured spinning planet and orbiting moon.
Enabling P3D
void setup() {
size(900, 600, P3D);
}
That one change gives you a Z axis, depth sorting, perspective projection, and hardware-accelerated rendering. Everything you already know — fill(), stroke(), translate(), rotate(), pushMatrix() — works the same way but now moves in three dimensions.
Primitive Shapes
void draw() {
background(0);
lights(); // default lighting — needed for 3D shading
translate(width/2, height/2, 0);
// Box: width, height, depth
pushMatrix();
translate(-160, 0, 0);
rotateX(frameCount * 0.01);
rotateY(frameCount * 0.013);
fill(180, 100, 80);
noStroke();
box(100, 80, 60);
popMatrix();
// Sphere: radius
pushMatrix();
translate(160, 0, 0);
rotateY(frameCount * 0.02);
fill(80, 140, 200);
noStroke();
sphereDetail(40); // set polygon count before drawing
sphere(70);
popMatrix();
}
sphereDetail(n) sets the number of longitudinal and latitudinal segments. Higher values give smoother spheres at the cost of performance.
Transforms: rotateX, rotateY, rotateZ
The three rotation functions take an angle in radians and rotate around the corresponding world axis at the current matrix position. Always wrap related transforms in pushMatrix() / popMatrix() so they do not bleed into other objects:
pushMatrix();
translate(x, y, z); // move to object position
rotateY(angle); // spin around vertical axis
scale(2, 1, 1); // stretch along X
box(50);
popMatrix(); // restore matrix
Lighting
Without lights in P3D, every surface is flat-shaded. Processing provides four light types:
// Soft uniform fill
ambientLight(60, 60, 70);
// Directional (like the sun) — direction vector, not position
directionalLight(255, 240, 200, // colour
-1, 0.5, -1); // direction (pointing toward neg-X, down, neg-Z)
// Point light — radiates from a position
pointLight(255, 200, 100, // colour
mouseX, mouseY, 200); // position in world space
// Spot light — cone
spotLight(200, 220, 255, // colour
width/2, 0, 400, // position
0, 1, -1, // direction
PI / 4, 2); // cone angle, concentration
Mix multiple lights for realistic illumination. Call them all before drawing geometry; Processing accumulates all active lights into the current frame.
Camera and Perspective
The default camera sits at (width/2, height/2, cameraZ) looking at the centre, where cameraZ = height / (2 * tan(PI/6)). Override it with camera():
camera(
eyeX, eyeY, eyeZ, // camera position
centerX, centerY, centerZ, // look-at point
upX, upY, upZ // up direction (usually 0, 1, 0)
);
Change the field of view and near/far clip planes with perspective():
perspective(
PI / 3, // 60-degree field of view
(float) width / height, // aspect ratio
0.1, // near clip
10000 // far clip
);
For 2D-style flat rendering without perspective, use ortho() instead.
PShape and OBJ Files
loadShape() imports Wavefront OBJ files (and SVG in 2D mode). Place the .obj and its .mtl alongside it in the data/ folder:
PShape model;
void setup() {
size(800, 600, P3D);
model = loadShape("spaceship.obj");
}
void draw() {
background(5, 5, 20);
lights();
translate(width/2, height/2);
rotateY(frameCount * 0.01);
scale(0.5);
shape(model);
}
You can also build PShape objects programmatically with createShape() for reusable, pre-compiled geometry — much faster than calling beginShape() every frame when geometry does not change.
Texturing
Load an image and apply it to geometry using texture() inside a beginShape() block. You must also call noStroke() or texture seams will show.
PImage tex;
void setup() {
size(800, 600, P3D);
tex = loadImage("earth.jpg");
}
void drawTexturedSphere(float r) {
int detail = 40;
sphereDetail(detail);
noStroke();
textureMode(NORMAL); // UV coordinates in 0.0–1.0 range
// Processing's built-in sphere() does not support texturing directly.
// Build a UV sphere manually:
for (int lat = 0; lat < detail; lat++) {
float theta1 = map(lat, 0, detail, -HALF_PI, HALF_PI);
float theta2 = map(lat + 1, 0, detail, -HALF_PI, HALF_PI);
beginShape(TRIANGLE_STRIP);
texture(tex);
for (int lon = 0; lon <= detail; lon++) {
float phi = map(lon, 0, detail, 0, TWO_PI);
float u = (float) lon / detail;
vertex(r*cos(theta1)*cos(phi), r*sin(theta1), r*cos(theta1)*sin(phi), u, (float)lat/detail);
vertex(r*cos(theta2)*cos(phi), r*sin(theta2), r*cos(theta2)*sin(phi), u, (float)(lat+1)/detail);
}
endShape();
}
}
Full Example: 3D Spinning Textured Planet with Orbiting Moon
Download or create earth.jpg and moon.jpg (any 2:1 ratio image works) and place them in data/.
PImage earthTex, moonTex;
float earthRot = 0;
float moonOrbit = 0;
void setup() {
size(900, 600, P3D);
earthTex = loadImage("earth.jpg");
moonTex = loadImage("moon.jpg");
noStroke();
}
void draw() {
background(2, 2, 12);
// Star field (just random points drawn at setup would be better;
// here we use a seeded approach)
randomSeed(42);
stroke(255, 180);
strokeWeight(1.2);
for (int i = 0; i < 300; i++) {
point(random(width), random(height));
}
noStroke();
// Lighting: sun from upper-right
ambientLight(30, 30, 40);
directionalLight(255, 240, 200, -1, 0.3, -1);
perspective(PI / 3.5, (float) width / height, 1, 5000);
camera(0, -100, 700, 0, 0, 0, 0, 1, 0);
// ── Earth ────────────────────────────────────────────
pushMatrix();
rotateX(radians(23.5)); // axial tilt
rotateY(earthRot);
drawTexturedSphere(earthTex, 150, 40);
popMatrix();
// ── Moon ─────────────────────────────────────────────
pushMatrix();
float moonX = cos(moonOrbit) * 260;
float moonZ = sin(moonOrbit) * 260;
translate(moonX, 0, moonZ);
rotateY(moonOrbit * 0.5);
drawTexturedSphere(moonTex, 42, 24);
popMatrix();
// ── Orbit ring (wireframe) ────────────────────────────
pushMatrix();
rotateX(HALF_PI);
stroke(80, 100, 140, 120);
strokeWeight(1);
noFill();
ellipse(0, 0, 520, 520);
noStroke();
popMatrix();
earthRot += 0.006;
moonOrbit += 0.008;
}
void drawTexturedSphere(PImage tex, float r, int detail) {
textureMode(NORMAL);
for (int lat = 0; lat < detail; lat++) {
float t1 = map(lat, 0, detail, -HALF_PI, HALF_PI);
float t2 = map(lat + 1, 0, detail, -HALF_PI, HALF_PI);
beginShape(TRIANGLE_STRIP);
texture(tex);
for (int lon = 0; lon <= detail; lon++) {
float phi = map(lon, 0, detail, 0, TWO_PI);
float u = (float) lon / detail;
float v1 = (float) lat / detail;
float v2 = (float) (lat + 1) / detail;
vertex(r*cos(t1)*cos(phi), r*sin(t1), r*cos(t1)*sin(phi), u, v1);
vertex(r*cos(t2)*cos(phi), r*sin(t2), r*cos(t2)*sin(phi), u, v2);
}
endShape();
}
}
The moon’s orbit speed is independent of Earth’s rotation speed. The axial tilt of 23.5 degrees applied before the Y-axis rotation means the entire rotation happens around the tilted axis, just like the real Earth.
Key Takeaways
- Pass
P3Das the third argument tosize()to enable OpenGL 3D rendering. - Wrap every object’s transforms in
pushMatrix()/popMatrix()to prevent leakage. - Call lighting functions before drawing geometry; they accumulate per-frame.
camera()andperspective()override the default view frustum.loadShape()imports OBJ files; build UV spheres manually withbeginShape(TRIANGLE_STRIP)when you need texture mapping.