徹底學會這個案例,Processing生成藝術(shù)再也難不倒你。Processing 案例教學? — 立方矩陣

今天我們來一起學習這個案例吧:

立方矩陣

完成這個案例大體分為4個步驟:

  1. 正交視角下繪制一系列的正方體
  2. 將box指令替換為單獨畫正方體的每個矩形面的方式,從而控制每個面的圖案
  3. 編排正方體的旋轉(zhuǎn)動作,并給他們的旋轉(zhuǎn)加入一點時間差
  4. 讓每個正方形的樣式略有不同


I. 在正交視角下繪制一系列的正方體



首先我們來學習一下關(guān)于正交視角的知識


正交視角下3d坐標系呈現(xiàn)一種2.5D的效果。相比于透視(perspective)呈現(xiàn)出的近大遠小,在正交視角下,相同大小的物體在不同距離上看起來是一樣的。拿游戲來舉例:魔獸世界是透視視角,而魔獸爭霸是正交視角。

正交 vs 透視
在processing中使用正交視角可以使用以下指令:
ortho(left, right, bottom, top, near, far)

其中 left, right, bottom, top 分別代表相機的左右上下邊界,near, far代表遠近截距。6個參數(shù)定義了一個虛擬空間的立方體空間,我們繪制的物體落在這個空間內(nèi)的部分才會被看到。

參考Processing官方ortho示例


讓我們開始寫代碼吧:

首先,在setup函數(shù)中設(shè)置渲染模式為P3D,并創(chuàng)建一個正交相機

void setup() {
  // 設(shè)置畫布大小為 430, 420 且渲染模式為P3D
  size(430, 420, P3D);
  // 針對RetinaDisplay高像素密度的優(yōu)化 
  pixelDensity(displayDensity());

  // 設(shè)置3d相機為正交相機
  ortho(-width/2, width/2, -height/2, height/2); 
}

至此我們創(chuàng)建了一個正交相機,其左邊界為-width/2,右邊界為width/2,上邊界為-height/2,下邊界為 height/2。width和height是窗口的寬度和高度,在這個程序里分別為430,420。

下一步,我們在draw函數(shù)中開始畫立方體

void setup(){
  ...
}
void draw(){
  // 每一幀開始時把畫布清空成黑色背景 
  // (括號里為灰度值,0是純黑255是純白,中間是灰色)
  background(0);
  
  // 在當前坐標下,畫一個大小為20的立方體
  box(200);
}

運行效果如下

運行效果1

我們發(fā)現(xiàn)在左上角有一個白色方塊,那個正是我們使用box畫出來的方塊。顯然,他的位置不對。我們需要改變它的繪畫位置。

對于3D圖形元素,我們不再能像2D元素(rect, ellipse等)一樣在指令中直接指定其畫圖的位置,而是需要通過坐標系變換來改變它的畫圖位置。

坐標系變換的順序一般是先平移(translate),然后旋轉(zhuǎn)(rotate),最后縮放(scale)。

需要注意的是,我們當前的坐標原點(0,0,0)并不在屏幕的正中央,而是在屏幕左上角的位置,我們先做一個translate(width/2, height/2),即可把坐標系移動到屏幕中央。之后再使用box指令畫圖,即可看到正方體移動到屏幕中心了。

void setup(){
 ...
}
void draw(){
 ...
 
 // 移動坐標系至屏幕中央
 translate(width/2, height/2);
 // 在當前坐標下,畫一個大小為20的立方體
 box(200);
}

運行效果如下

運行效果2

目前立方體看起來就是一個正方形,因為它的一個面正對著攝像機。接下來我們把它做一些旋轉(zhuǎn),讓它有一個立體的感覺。


我們來復(fù)習一下坐標軸旋轉(zhuǎn)的相關(guān)知識

3D繪圖中旋轉(zhuǎn)相關(guān)的指令有3個,分別是rotateX(angle), rotateY(angle), rotateZ(angle)。他們的工作方式類似,其中XYZ代表旋轉(zhuǎn)圍繞的坐標軸,而括號內(nèi)的參數(shù)代表旋轉(zhuǎn)的角度。角度按當某個坐標軸正對你時的順時針方向來測定,單位為弧度radians。

xyz軸示意圖.png
rotateX、rotateY、rotateZ旋轉(zhuǎn)正方向的示意圖.gif

為了把正方形變成下面的樣子,我們需要讓正方形做一些旋轉(zhuǎn)。可以在box(200)指令前加一些坐標軸旋轉(zhuǎn)的指令。

大家猜猜看需要旋轉(zhuǎn)哪幾個軸,又需要各旋轉(zhuǎn)多少度?

看起來像幾個2D菱形構(gòu)成的3D立方體

答案:旋轉(zhuǎn)x軸-30度,旋轉(zhuǎn)y軸-45度

void setup(){
  ...
}
void draw(){
  ...
 
  // 移動坐標系至屏幕中央
  translate(width/2, height/2);
  // 旋轉(zhuǎn)-30度,等于 -pi/6
  rotateX(-PI/6);
  // 旋轉(zhuǎn)-45度,等于 -pi/4
  rotateY(-PI/4);
  // 在當前坐標下,畫一個大小為20的立方體
  box(200);
}
運行效果3: 旋轉(zhuǎn)后的立方體,注意對比三個坐標軸現(xiàn)在的位置

至此,我們完成了單個立方體的繪制。

下一步,我們使用循環(huán)將立方體分別畫在空間的不同地方。


我們首先來規(guī)劃一下我們的網(wǎng)格。

立方矩陣

參考原圖,我們發(fā)現(xiàn)空間中的立方體個數(shù)是15x15個。對比每個立方體的坐標,我們可以發(fā)現(xiàn)他們的z坐標是相同的,只是x、y坐標分布在一個網(wǎng)格上。而由于相鄰兩個立方體之間的間隔相同,他們的x、y坐標可以用兩個等差數(shù)列來分別表示。

以10像素為間隔為例,可以構(gòu)建如下坐標系,其中每個圓點上可以放置一個立方體。

按照間隔為10來構(gòu)建的坐標系

我們一般以 i, j 來標記二維矩陣中的每個點,i 代表橫軸上的序號,也就是列號;而 j 代表豎軸上的序號,也就是行號;并且序號是從0開始;所以i = 5, j = 3代表第6行第4列的那個點(上圖黃點)。

我們來做一下計算
i=0, j = 0 -> x= -70, y = -70
i=1, j = 0 -> x= -70 + 10, y = -70
i=1, j = 1 -> x= -70 + 10, y = -70 + 10
i=2, j = 0 -> x= -70 + 20, y = -70
i=2, j = 1 -> x= -70 + 20, y = -70 + 10
i=2, j = 2 -> x= -70 + 20, y = -70 + 20
......

我們發(fā)現(xiàn)

x = -70 + i * 10
y = -70 + j * 10

更近一步的說,假設(shè)N是每行/列的點的數(shù)量,D是間隔的距離

左邊界/上邊界的坐標 = -(N-1)/2 * D = -70
x = -(N-1)/2 * D + i * D = -70 + i * 10
y = -(N-1)/2 * D + j * D = -70 + j * 10

我們只需要讓 i 和 j 分別依次等于0,1,2,3,...14即可得到所有的坐標值,寫成代碼即為

for(int j = 0 ; j < 15 ; j++){
    for(int i = 0 ; i < 15 ; i++){
        float x = -70 + i * 10;
        float y = -70 + j * 10;
    }
}

完成坐標的計算以后,我們需要:

  1. 將坐標軸從目前的位置(畫布中央)依次移動到每一個(x , y)坐標上
  2. 旋轉(zhuǎn)合適的角度
  3. 繪制正方體
  4. 將坐標軸移動回畫布的中央

需要注意的是,我們目前的位置已經(jīng)是經(jīng)過一次translate(width/2 , height/2)之后所得到的。后續(xù)的平移是疊加在先前的平移之上的。

關(guān)于使用pushMatrix()popMatrix()來管理疊加的坐標系: pushMatrix()和popMatrix()必須成對使用;從pushMatrix()到popMatrix()之間的坐標系變換在使用popMatrix()指令后被撤銷,使程序的當前坐標系恢復(fù)到使用pushMatrix()之前的狀態(tài)。

在對每個單獨的立方體進行坐標系變換前,我們可以使用pushMatrix來保存當前的坐標系;等繪制完box以后,使用popMatrix來撤銷針對于這個立方體所做的所有坐標系變換,從而回到push之前的坐標系。

針對于每個立方體的代碼即為:


...
//  保存當前的坐標系
pushMatrix();
//  1. 將坐標軸從目前的位置(畫布中央)依次移動到每一個(x , y)坐標上
translate(x,y);
//  2. 旋轉(zhuǎn)合適的角度
rotateX(-PI/6);
rotateY(-PI/4);
//  3. 繪制正方體
box(5);
// 4. 將坐標軸移動回畫布的中央
popMatrix();

將這些代碼放到循環(huán)中:

for (int j = 0; j < 15; j++) {
  for (int i = 0; i < 15; i++) {
    float x = -70 + i * 10;
    float y = -70 + j * 10;
    // 保存當前的坐標系
    pushMatrix();
    // 1. 將坐標軸從目前的位置(畫布中央)依次移動(translate)到每一個(x, y)坐標上
    translate(x, y);
    // 2. 旋轉(zhuǎn)合適的角度
    rotateX(-PI/6);
    rotateY(-PI/4);
    // 3. 繪制正方體。
    box(5);
    // 4. 將坐標軸移動回畫布的中央
    popMatrix();
  }
}

用它來替換之前單個立方體的繪圖代碼部分:

void setup() {
  ...
}

void draw() {
  ...
  
  // 移動坐標系至屏幕中央
  translate(width/2, height/2);
  ======= 以下部分被替換 =======
  // 旋轉(zhuǎn)-30度,等于 -pi/6
  //rotateX(-PI/6);
  // 旋轉(zhuǎn)-45度,等于 -pi/4
  //rotateY(-PI/4);
  // 在當前坐標下,畫一個大小為20的立方體
  //box(200);
  =============================
  for (int j = 0; j < 15; j++) {
    for (int i = 0; i < 15; i++) {
      float x = -70 + i * 10;
      float y = -70 + j * 10;
      // 保存當前的坐標系
      pushMatrix();
      // 1. 將坐標軸從目前的位置(畫布中央)依次移動(translate)到每一個(x, y)坐標上
      translate(x, y);
      // 2. 旋轉(zhuǎn)合適的角度
      rotateX(-PI/6);
      rotateY(-PI/4);
      // 3. 繪制正方體。
      box(5);
      // 4. 將坐標軸移動回畫布的中央
      popMatrix();
    }
  }
}

運行效果4:15x15立方體矩陣

至此,我們完成了第一個步驟,繪制一個立方體矩陣。

為自己鼓鼓掌??吧,你已經(jīng)完成了最基礎(chǔ)最重要的一步了!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容