Processing 案例 | 扑面而来的满天繁星
文章目錄
- 引言
- 效果展現
- 原理分析
- 代碼實現
- 確定代碼執行流程
- 星星繪制在屏幕上
- 讓星星動起來
- 點擊鼠標,星星的速度發生改變
- 視角改變
- 完整代碼
- 結語
??
引言
清明時節雨紛紛,路上行人欲斷魂”,每到清明,你總能從那如絲如雨縷的春雨綿綿中感到心上繚繞的那點憂愁。
很少有這樣一個節日,像清明這樣的矛盾。當你放歌踏青時,它是一個輕盈的日子,是節氣;當你慎終追遠的時候,它是一個悲愴的日子,是節日。
在每一個清明冰涼的夜,你是否也曾仰望滿天的繁星,追憶那已故的人?他們就像那滿天的繁星,在你生命的漫漫長夜中匆匆劃過,照亮了一段時光。
現在就讓我們一起用 Processing 來模擬那片星域,讓記憶中的人和事翻上心頭,細細評味。
效果展現
??
原理分析
首先通過觀看視頻,我們可以發現在鼠標所在的點附近的星星是最小的,離該點越遠,星星的面積越大,移動的速度越快。就好像星星都從這個點里出來一樣,我們暫且把這個點叫做消失點。并且觀察可得,消失點把屏幕分成了四個區域A,B,C,D(如圖一所示),同時把星星也分成了四類。其中A中的星星向左上方移動,B中點向右上方移動,C中的點向左下方移動,D中的點向右下方移動。
接下來我們來看一下代碼實現的大概思路。
我們在腦海里假想一個長方體,以這個長方體的一個頂點為原點,建立世界坐標系。然后選取一個面作為Processing 程序的窗口,建立屏幕坐標系。然后在屏幕上任選一個點,作為消失點,該點在屏幕坐標系下的坐標為(x_endpoint,y_endpoint )。
接著在這個長方體里隨機生成一系列的星星,每個星星由一個三維向量(x_world,y_world,z_world)表示,這是它在世界坐標系下的坐標。
接下來我們要做的就是把每一個星星的在世界坐標系的坐標,轉換成在processing窗口里面的坐標(x_screen,y_screen),這樣我們就可以在屏幕上把星星繪制出來。
首先需要計算星星在世界坐標系下相對于消失點(endpoint)x,y方向的偏移量,接下來將這個偏移量根據星星的z_world進行放縮,最后再將放縮完成的偏移量加回消失點的坐標,得到星星在屏幕上的坐標。
公式如下:
x s c r e e n = ( x w o r l d ? x e n d p o i n t ) / z w o r l d ? s c a l e + x e n d p o i n t x_{screen}=(x_{world}-x_{endpoint})/z_{world} *scale+x_{endpoint} xscreen?=(xworld??xendpoint?)/zworld??scale+xendpoint?
y s c r e e n = ( y w o r l d ? y e n d p o i n t ) / z w o r l d ? s c a l e + y e n d p o i n t y_{screen}=(y_{world}-y_{endpoint})/z_{world} *scale+y_{endpoint} yscreen?=(yworld??yendpoint?)/zworld??scale+yendpoint?
其中scale是放縮比例,用來控制控制星域的范圍。
分析公式可以發現z_world越大對應的 x_screen、y_screen 越大,也就是說離消失點越遠。也就說我們只要將z_world 初始設為一個比較大的值,然后在不斷減小它,這樣就會出現星星離消失點越來越遠,離屏幕越來越近的效果。同時我們根據 z_world 的大小設置星星的直徑 diam 設置,z_world 越大,diam 越小,這樣就符合近大遠小的透視規律。
??
代碼實現
首先我們來確定代碼的結構,創建三個 Processing 文件,分別為 main.pde、StarField.pde、Star.pde。其中main.pde 里面是程序執行的主流程。StarField.pde 里面主要定義了 StarField類、Star.pde 主要定義了 Star 類。
?
確定代碼執行流程
在 main.pde 中輸入如下代碼:
/*這個是一個由星星構成的粒子系統 擁有星星的所有狀態和行為*/ StarField sf; void setup(){ size(400, 400); sf = new StarField(); } void draw(){ background(0); /*運行星域系統 更新星星的狀態和繪制星星*/ sf.run(); } /*鼠標按下時調用的函數 用來改變星域的速度*/ void mousePressed(){ } /*鼠標移動的時候調用的函數 用來改變視角*/ void mouseMoved(){ }?
星星繪制在屏幕上
首先在StarField.pde中輸入如下代碼:
以上代碼我們確定了該類的四大組成部分,接下來我們在將構造函數替換為如下代碼:
StarField(){ /*將初始消失點設置在鼠標最開始的位置*/ endpoint = new PVector(mouseX, mouseY); /*初始化所有的星星*/ stars = new ArrayList(); for(int i = 0; i < STAR_COUNT; i++){ stars.add(new Star()); } }接著在StarField類的成員函數中定義run函數:
void run(){ for(Star s : stars){ /*對星星進行坐標變換 獲得星星在屏幕坐標系的坐標 用于之后的星星的渲染*/ s.transform(endpoint); /*對屏幕外的星星進行裁剪 同時生成一個新的星星 使得星星可以源源不斷的出現*/ s.checkEdge(); /*依據屏幕坐標系渲染星星, 使得星星在屏幕上出現*/ s.display(); } }接下來我們來定義出現在 StarField 的構造函數中和 run 函數中的變量和常量。
/*該粒子系統包含的星星的個數 星星越多畫面越密*/ final int STAR_COUNT = width / 2; /*數據類型為Star object的動態數組 存儲該星域中所有的星星*/ ArrayList<Star> stars; /*消失點 用來控制視角*/ PVector endpoint;之后我們來定義Star對象,輸入如下代碼:
class Star{ //常量聲明 //變量聲明 //構造函數 //成員函數 }將 Star 類的構造函數替換為:
Star(){ /*在一個長方體區域內隨機生成一個點 返回它在世界坐標系下的坐標*/ worldPosition = new PVector(random(0, width), random(0, height), random(0, MAX_DEPTH)); }接下來在Star類成員函數部分加入 transfrom 函數,這個函數是本作品最關鍵的地方,希望大家能好好體會一下。
void transform(PVector endpoint){ /*將星星的坐標從世界世界坐標系變換到消失點坐標系 以下代碼等同于: viewPosition.x = (worldPosition.x - endpoint.x) / worldPosition.z * SCALE; viewPosition.y = (worldPosition.y - endpoint.y) / worldPosition.z * SCALE; */ viewPosition = PVector.sub(worldPosition, endpoint).div(worldPosition.z).mult(SCALE); /*將星星的坐標從消失點坐標系變換到屏幕坐標系 以下代碼等同與: screenPosition.x = endpoint.x + viewPosition.x; screenPosition.y = endpoint.y + viewPosition.y; */ screenPosition = PVector.add(endpoint, viewPosition); /*根據世界坐標z的大小來確定星星的直徑 z越小,說明越靠近屏幕,所以星星越大*/ diam = map(worldPosition.z, 0, MAX_DEPTH, MAX_DIAM, 0); }??接著在Star類的成員函數部分加入checkEdge函數的定義
void checkEdge(){ if(screenPosition.x <= 0 || screenPosition.x >= width || screenPosition.y <=0 || screenPosition.y >= height){ /*如果這個點已經在屏幕外了,那么將其裁剪, 同時在相同的長方體區域隨機生成一個新的點*/ worldPosition.set(random(0, width), random(0, height), MAX_DEPTH); } }然后繼續在Star類的成員函數部分Star類的display函數。
void display(){ /*在屏幕上繪制該星星 每一個星星是一個白色的、沒有邊的圓*/ fill(255); noStroke(); ellipse(screenPosition.x, screenPosition.y, diam, diam); }在第二步的最后我們把Star類需要的一些成員變量和常量加上,把它們添加到Star類的常量和變量部分。
/*星星在屏幕坐標系直徑的最大值 用于控制星星在屏幕上的整體大小*/ final float MAX_DIAM = 16; /*星星里屏幕最遠的距離 用來控制星域的立體感*/ final float MAX_DEPTH = width / 2; /*進行坐標變換時候的 用來控制星星在屏幕上的分布范圍*/ final float SCALE = MAX_DEPTH; /*星星在三個坐標系下的坐標 記錄它們在不同參考系下的位置*/ PVector worldPosition, screenPosition, viewPosition; /*星星在屏幕坐標系下的直徑 確定星星在屏幕上的大小*/ float diam;?
讓星星動起來
首先我們在 StarField 的 run 方法中,給每一個星星添加如下的行為:
接著在StarField的構造方法中初始化 speed 這個值。
/*初始化星星的移動速度 讓移動速度不用太快和太慢*/ speed = (MAX_SPEED + MIN_SPEED) / 2;然后在 StarField 的變量和常量部分,定義新增加和星星速度有關的常量和變量。
/*分別代表星星移動的最大速度,最小速度, 用來控制星星移動速度的范圍*/ final int MAX_SPEED = 11, MIN_SPEED = 1; /*速度改變的步長 每一次增加或者減小速度的時速度改變的最小值*/ final int SPEED_STEP = 1; /*星星移動的速度 控制星星移動快慢*/ int speed;這之后我們在 Star 的成員函數部分加入如下代碼,然后第三階段就到此結束。
void move(float speed){ worldPosition.z -= speed; /*限制星星世界坐標系的位置 防止出現星星移動到屏幕外的情況*/ worldPosition.z = constrain(worldPosition.z, 0, MAX_DEPTH); }?
點擊鼠標,星星的速度發生改變
直接在 main.pde 的 mousePressed 中加入如下代碼:
然后在 StarField 的成員函數中定義 speedUP 和 speedDown 函數:
void speedUP(){ speed += SPEED_STEP; /*限制speed在最大和最小值之間 防止速度太快*/ speed = constrain(speed, MIN_SPEED, MAX_SPEED); } void speedDown(){ speed -= SPEED_STEP; /*限制speed在最大和最小值之間 防止速度小于零*/ speed = constrain(speed, MIN_SPEED, MAX_SPEED); }&enmsp;
視角改變
首先在 mouseMoved 函數里面加入如下代碼:
到了現在我們就完成了代碼的所有編寫。
??
完整代碼
我的github
我的openprocessing
?
結語
我們有很多可以擴展的地方,比如改變 Star 類里的 MAX_DEPTH 來改變星域的立體感,改變 StarField 類的 STAR_COUNT,Star 類 SCALE 的來改變星域的密度和范圍。
目前為止對于每一星星我們只是簡單的畫了一個白色的小圓,其實這里有很大的發揮空間。比如用 Noise 函數根據星星在屏幕坐標系的位置計算星星的顏色,或者用你思念的人的名字或者其中的字母來替代圓圈,創造出那一片獨一無二,只屬于你的燦爛的星域!
最后,祝大家清明節快樂!
總結
以上是生活随笔為你收集整理的Processing 案例 | 扑面而来的满天繁星的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软Win10彻底封杀exFAT/FAT
- 下一篇: java 手机动态口令_动态密码TOTP