在生成藝術當中,仿生是個熱門主題,也就是透過程式來模擬生物行為,從而構成自然界可見的一些美妙圖案,先前我在專欄〈淺談反應擴散系統〉談過的圖靈紋,就是以反應擴散系統進行模擬而得,文中談到《A-Life 使用Python實作人工生命模型》,該書也還有著其他仿生的演算方案可以參考。

不過,我會去研究圖靈紋如何生成,其實是看到了類似圖靈紋的圖案,該圖案是從一條線開始,不斷地扭曲並適時地增加線上的點,點與點之間不會重疊、不會相距太遠、線段之間也不會交錯,最終構成了類似圖靈紋的圖案,而且仍然是一條線,我們可以想像最後形成像是腸道蜿蜒的樣子。

許多生成藝術創作者,將這個過程稱為差別生長(Differential Growth),不過,單就差別成長這個名詞而言,主要是生物或醫學領域研究的對象,基本上是指生物組織各部位會以不同速率成長,影響成長速率的因素很多,從這點來看,反應擴散系統也是一種模擬差別成長的方式。

仿生的差別生長

相較於反應擴散系統是基於粒子的擴散與反應,生成藝術創作者所謂的差別生長,目前我找到的最早資料,是2015年左右以Floraform生成設計系統模擬介紹的文件,後續介紹差別生長演算的文件,多是基於〈SHEPHERDING RANDOM GROWTH〉提出的要素為基礎,這些要素包含:節點與邊、邊的切分時機、新增節點、節點間的引力、斥力。

前面幾個要素容易理解,節點相連之後構成邊,邊的切分時機可以是隨機選取一個邊,或者走訪每個邊,看看邊的長度是否適合切分,若適合、就新增節點,但問題在於引力、斥力如何實現?必須結合物理、化學或生物之類的相關理論或模型,才會看來具有仿生的效果,這是實現差別生長前的一大難題。

群聚演算

談到引力,很容易想到萬有引力,但節點之間還有斥力,而這就會聯想到磁力。實際上,磁力有引力與斥力之分,隨機地給予節點引力或斥力,也可以構成有趣的圖案,發展過程像是活的生物行為,實體世界也能模擬出來,有興趣的人可以參考〈The behaviour of ball bearings〉中的電擊影片。

問題在於點與點間不能重疊,又不能相離太遠,也就是說,每個節點會同時具有引力、斥力,從這點來看,力的模擬方面比較像是可拉開、可壓縮的彈簧,能分別以拉力與彈力來模擬引力與斥力。

不過,仿生方面的演算最麻煩的,在於如何簡化模型(例如簡化力的模型)以及選擇適當的參數值,往往參數值之間構成微妙平衡,才會呈現仿生的圖案。為了有實際的程式可以參考,我後來找到了〈Differential line growth with Processing〉,其中談到的群聚演算,與生物行為模擬有直接的相關。

所謂的群聚演算,最初由Craig Reynolds在1986年於〈Flocks, Herds, and Schools: A Distributed Behavioral Model〉提出,他實作了Boids程式,用以模擬羊、鳥等個體決策下會展現出何種群聚行為,而在維基百科的〈Boids〉,我們可以看到其中基本要素,包含了區隔(separation)、凝聚(cohesion),以及對齊(alignment)等指向力(steer)。

「區隔」容易理解,是避免彼此過於擁擠的指向力,相當於差別生長的斥力,「凝聚」是個體往鄰近群體移動的指向力,就像羊會選鄰近的羊群靠攏,「對齊」是找出領頭羊的指向力,這決定了各個羊群的行動方向,因此,「凝聚」與「對齊」相當於差別生成的引力。

《The Nature of Code》作者Daniel Shiffman在〈Flocking〉實現了Processing的Boids程式,可以在畫面中以滑鼠點選新增個體,新個體會朝最接近的群體靠攏,整個畫面就如多個羊群的移動模式。

實作差別生長

因為力有方向及大小,實作時,如果以向量來表現會是比較容易處理的做法,在〈Flocking〉與〈Differential line growth with Processing〉的實作中,就都是基於Processing的PVector來實現。

由於個體行為彼此獨立,每個節點會封裝計算指向力的相關資料,以及各自的位置、速度與加速度(這三者都是以向量表示),因為牛頓第二定律的力公式是F=ma,若質量m為1,那麼,力F的套用就是直接與加速度a相加減,也就是簡化為向量的加減,這部分可以參考〈力的建模〉

由於許多節點參與模擬,勢必要有系統來管理、協調節點,例如選擇邊的選擇、切分、節點生成等,以及何時該更新整個系統,這就是粒子系統,在〈粒子系統〉中,我談到粒子系統的概念與實現方式,而就〈Differential line growth with Processing〉的實作來說,Node類別代表各自獨立的粒子,DifferentialLine則是粒子系統的角色。

〈Differential line growth with Processing〉中,節點的凝聚指向力(引力)是朝向兩個鄰居節點的位置中點,這會令圖案構成彎曲,節點的區隔指向力來自與其他節點間的距離,任兩個粒子距離小於指定的值,就會產生區隔指向力,而且區隔指向力會累加,這就使得粒子不會重疊,粒子與粒子間的邊也不會交錯。

在計算區隔指向力的累加時,有個必須考量的效率議題,因為必須計算任意兩個節點的區隔指向力,直覺的實現方式下,有n個節點就會需要n*n次的計算,然而節點會越來越多,若成長到一千個節點,就會需要一百萬次計算。

所幸的是,B對A產生區隔指向力,其實,相對地也是A對B產生的區隔指向力,只是方向反了而已,因此計算出B對A的區隔指向力時,直接將向量反向,就是A對B的區隔指向力,以迴圈實作時,內層迴圈會次數會遞減,如果以這種方式實現,只需要n(n+1)/2次計算,也就是若成長到一千個粒子,只會需要五十萬次左右的計算。

差別生長的多樣性

〈Differential line growth with Processing〉的實作雖然運用了群聚演算,但這裡做了簡化,沒有實現對齊指向力,因為沒有領頭羊的效果,最後展開的圖案外圍大致上都會形成圓形,若加入領頭羊的效果,或許會展開不同的樣貌,透過雜訊(例如Perlin)來干擾力道或速度,這也是展現不同樣貌的一種方式。

最後在繪製圖形方面,可以選擇繪製線段,或者將全部的線連成多邊形來展現,若選擇繪製線段,可以在每一次影格繪製時不消除背景,這會形成線段漸變的效果,也可以選擇在每個影格時,於不同高度(z方向)繪製線段,這就構成了3D模型。

為了能夠更進一步第掌握差別線段生長的演算,我試著將〈Differential line growth with Processing〉的範例,重構為p5.js的版本,並且將Node與DifferentialLine的職責重新分配了一下,應該有助於理解如何實作,有興趣可以進一步參考〈Differential line growth with p5.js〉

專欄作者

熱門新聞

Advertisement