Post

Scrivere Un 3d Engine Da Zero Tutorial 2

Scrivere Un 3d Engine Da Zero Tutorial 2

Proiezioni 3D in p5.js

Cosa Facciamo

In questo tutorial creiamo la proiezione 2D di un cubo 3D. Prendiamo 8 punti nello spazio tridimensionale (i vertici di un cubo) e li proiettiamo sullo schermo.

Il Codice Spiegato

I Vertici del Cubo

1
2
3
4
5
6
7
8
9
10
let points = [
  [-1, -1, -1], // P1
  [1, -1, -1],  // P2
  [1, 1, -1],   // P3
  [-1, 1, -1],  // P4
  [-1, -1, 1],  // P5
  [1, -1, 1],   // P6
  [1, 1, 1],    // P7
  [-1, 1, 1]    // P8
];

Definiamo gli 8 vertici di un cubo centrato nell’origine. Ogni punto ha coordinate [x, y, z].

Setup

1
2
3
function setup() {
  createCanvas(500, 400);
}

Creiamo un canvas di 500×400 pixel.

Il Calcolo della Profondità

1
2
3
4
5
6
7
8
const zNear = 0.1;
const zFar = 1000;

function computeDepth(z_vertex) {
  let z = z_vertex * -(zNear + zFar)/(zNear - zFar);
  z += (2 * zNear * zFar)/(zNear - zFar);
  return z;
}

Questa funzione calcola la profondità del punto usando i piani near e far, utile per il depth clipping (decidere cosa è visibile).

La Proiezione

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function draw() {
  background(220);
  
  for(let i = 0; i < points.length; i++) {
    const aspectRatio = height/width;
    
    // Trasliamo il cubo nello spazio
    let translate_x = 1;
    let translate_y = 1.5;
    let translate_z = 5;
    
    let x = (points[i][0] + translate_x) * aspectRatio;
    let y = (points[i][1] + translate_y) * -1;
    let z = points[i][2] + translate_z;

Per ogni vertice:

  • Calcoliamo l’aspect ratio per mantenere proporzioni corrette
  • Trasliamo il cubo nella posizione desiderata (soprattutto in z=5, davanti alla camera)
  • Invertiamo y perché lo schermo ha coordinate invertite
1
2
3
4
5
6
7
8
    let zDepth = computeDepth(z);
    
    // Proiezione prospettica
    if(z != 0) {
      x /= z;
      y /= z;
      zDepth /= z;
    }

Dividiamo x e y per z: questa è la proiezione prospettica. Gli oggetti più lontani (z maggiore) risultano più piccoli.

1
2
3
4
5
6
7
8
9
10
11
    // Convertiamo da spazio normalizzato (-1,1) a coordinate schermo
    x = map(x, -1, 1, 0, width);
    y = map(y, -1, 1, 0, height);
    
    // Disegniamo solo se il punto è visibile
    if(zDepth < 1) {
      strokeWeight(5);
      point(x, y);
    }
  }
}

Convertiamo le coordinate normalizzate in pixel e disegniamo solo i punti visibili (zDepth < 1).

Concetti Chiave

Proiezione Prospettica

Dividere x e y per z crea l’effetto prospettico: oggetti lontani appaiono più piccoli, come nella realtà.

Trasformazioni

  • Traslazione: spostiamo il cubo nello spazio 3D
  • Aspect Ratio: correggiamo la proporzione per canvas non quadrati
  • Mapping: convertiamo da coordinate 3D a coordinate schermo

Depth Clipping

Il test if(zDepth < 1) determina se un punto è visibile o fuori dal frustum della camera.

Provalo

Vai su editor.p5js.org, copia il codice e osserva gli 8 vertici del cubo proiettati sullo schermo!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
let points = [
  [-1, -1, -1], // P1
  [1, -1, -1], // P2
  [1, 1, -1], // P3
  [-1, 1, -1], // P4
  [-1, -1, 1], // P5
  [1, -1, 1], // P6
  [1, 1, 1], // P7
  [-1, 1, 1] // P8
];


function setup() {
  createCanvas(500, 400);
}

const zNear= 0.1;
const zFar = 1000;

function computeDepth(z_vertex){
  let z = z_vertex * -(zNear + zFar)/(zNear - zFar);
  z += (2 *zNear * zFar)/(zNear - zFar);
  return z;
}

function draw() {
  background(220);
  for(let i = 0; i< points.length; i++){
    const aspectRatio = height/width;
    
    let translate_x = 1;
    let translate_y = 1.5;
    let translate_z =5;
    
    let x = (points[i][0]  + translate_x) * aspectRatio;
    let y = (points[i][1] + translate_y) * -1;
    let z = points[i][2] +translate_z;
    
    let zDepth = computeDepth(z);
    
    if(z!=0){ // normalizzazione -> -1,1
      x/=z;
      y/=z;
      zDepth/=z;
    }
    
    
    
    x = map(x,-1,1,0,width);
    y = map(y,-1,1,0,height);
    
    if(zDepth< 1){
      strokeWeight(5)
      point(x,y)
    }
    
  }
 
  
}
This post is licensed under CC BY 4.0 by the author.