# **隨機化石頭的生成**
* * * * *
## **零、需求分析**
**H子**:**AOI**老師,最近有想法,想要制作一款獨立游戲了,但是美術素材量并不少,特別是一些瑣碎的資源,比如石頭,樹木之類的東西。
**AOI**:嗯。有考慮過到各種資源市場購買資源嗎。
**H子**:有是有過,不過考慮到預算限制,以及資源風格,數量等等的問題,靠購買也并不能解決所有問題。
**AOI**:那么,有考慮過想要制作什么樣的美術風格嗎?
**H子**:現在考慮到人力資源方面的壓力,果然還是簡單明快的風格更好吧。所以這次先考慮LowPoly風格了。
**AOI**:這個確實算是一種明智的選擇。在獨立團隊,個人開發者等人力資源不足的情況下,選擇更簡單明快的風格,往往比需要龐大工作量的風格更有優勢。
**AOI**:其次,盡可能實現程序化生成,也是可以提升開發效率的方法。
**H子**:程序化生成?
**AOI**:對目標進行分析,并且找出其中的規律和邏輯,以此構建程序并自動化的產出素材。此后,就可以進一步的在自動產出的素材中篩選和修改,就可以更輕松的產出經過主觀處理的素材。
這次就先用隨處可見的石頭舉個例子吧。
想想看,Low Poly的石頭有什么特點呢。
**H子**:我想想......隨便拿粘土和硬卡,把幾個面壓平壓出棱角的感覺?
**AOI**:你說的好像也沒什么錯......
首先,要生成隨機的石頭,可以先分析造型。一般的鵝卵石等的路邊小石,多數因為自然風化等作用下,都是近似球形。先把受到外力破損的造型作為特例剔除。
也就是說,要生成隨機的石頭,需要的是以下幾個基本步驟:
1. 在一定范圍內的隨機頂點
2. 把隨機頂點最外層頂點轉化成模型
生成一塊石頭所需要的,大體上來說就是以上的兩個個基本步驟。
然后按照需求,逐步的完成石頭的生成并且將流程自動化。
**H子**:那么!我們用什么方法來進行這個自動化生成的工作呢?
**AOI**:那就是慣例的Blender吧。這次我們會使用一款叫Sverchok的可視化節點建模插件來進行這次的工作。
**H子**:好!我先去裝好插件。
* * * * *
## **壹、生成位置的頂點【Random Vector MK2】節點的運用**
**AOI**:先切換到Sverchok編輯界面吧。切換到節點編輯器,你會見到節點類型那里多了一個選項。

點擊即可切換到Sverchok節點編輯界面。
**H子**:好了!我找到了。
**AOI**:首先進行第一步。
生成一定范圍內的隨機頂點,在Sverchok插件的內建節點庫里面,有一個名為**Random Vector MK2**的節點可以使用。
按Crtl+空格鍵彈出的搜索節點窗口內輸入節點名稱即可搜索和創建該節點。
對了,創建節點前記得先創建節點樹,雖然你不記得創建,在空白的節點編輯器界面直接創建節點,軟件也會自動創建節點樹。但是最好還是養成習慣比較好。
如下圖所示:

創建后,將會在畫布上得到一個新的節點:

節點有三個入口:
* Count:產生的隨機Vector數量
* Seed:隨機種子
* Scale:縮放
**H子**:也就是說,根據需要,調整Seed和Count值就可以獲得需要的頂點了嗎?
**AOI**:根據實際情況輸入Count的值,用于產生石頭所需的頂點,頂點數量越多,越接近球形。因為隨機頂點是在Scale值為半徑的空間內產生隨機頂點。而Scale值可以決定最后產生石頭的大小的最大值,也就是產生頂點的區間的最大半徑。
Seed可以隨意設置,值的變化會產生不同的隨機頂點,因此可以利用Seed進行造型的隨機化,也是程序化石頭批量生成的關鍵。
**H子**:那么?我們應該怎么把這些頂點變成最后的模型呢?
**AOI**:先不用著急,我們可以先觀察一下頂點的效果。
先通過搜索創建**Viewer Draw**節點:

創建之后,即可再畫布見到該節點:

可以見到這個節點,有三個入口:
* vertices:頂點
* egd_pol:邊/面
* matrix:矩陣
**H子**:啊,我明白了,也就是說再對應的節點里面傳遞進去對應的數據,就可以在場景上畫出對應的內容了吧。而且看入口的顏色就可以分辨出數據類型了。這么說的話,在**Random Vector MK2**節點的出口的顏色和**Viewer Draw**節點的vertices入口的顏色是一樣的呢。
**AOI**:沒錯,所以可以直接吧這一對出入口鏈接在一起。那么,就連上去看看效果吧。
**H子**:連!

啊!場景上出現頂點了。
這么說,**Viewer Draw**節點上面的三個白色和藍色的應該就是設置顯示顏色了吧,分別對應點線面。
**AOI**:沒錯,從圖標就確實的可以理解到其功能。
**H子**:那么,現在頂點有了,也可以顯示出來了,那么,下一步就是該轉換成模型了吧。
* * * * *
## **貳、將隨機頂點變成模型吧【Convex Hull】節點的運用**
**AOI**:好,我們先搜索找到Convex Hull節點吧。
**H子**:**AOI**老師!我搜出來兩個結果了!一個叫**Convex Hull**一個叫**Convex Hull MK2**。他們有什么不同呢?

**AOI**:Convex Hull節點顧名思義就是創建一個凸面包裹體的。
先創建兩者的節點看看吧。

可以看到,兩者光從外觀上來看,差別還是挺大的,但是仔細觀察接口的話,會發現兩者的入口和出口都是完全一致的。
首先可以見到兩者的入口都只有一個Vertices,從顏色即可看出,這個是接受頂點數據的傳入。而輸出則變成的兩個,一個是Vertices,一個是Polygons。
由此可見,這兩個節點的作用都是通過傳入一組頂點數據,并且基于這一組頂點數據創建一個凸面物體。
而MK2和普通版的特點在于,MK2進一步的提供了包括2D的凸面形狀的創建等的功能,而普通版的節點僅僅針對3D物體的創建。我們要創建的石頭是3D物體,所以只需要使用普通版本即可。
把節點添加到節點樹上試試看把。
**H子**:好!我試試看。

我看到了!
出來了!好簡單!
好,**AOI**老師你先不要說!我往下做做看。
點一下BAKE就能得到模型了對吧!對吧!對吧!
**AOI**:冷靜!冷靜!
**H子**:我點了!

好!然后右鍵選擇模型,拖出來。一模一樣!成功了!
**AOI**:對吧,要生成石頭還是很簡單的對不對。只要改變**Random Vector MK2**節點的**Seed**的值,就可以改變造型。當然現在的造型還很粗糙,想要進一步的進行更多的優化,自然要添加更多的節點對產生的數據進行控制。
* * * * *
## **叁、批量產生石頭吧 【Frame Info】節點的運用**
**H子**:那么下一步就是開始批量化的產生石頭了吧。首先果然是要不斷的變化**Seed**值呢,但是有什么方法可以不斷的改變呢。手動點果然是不現實的呢,最討厭這種重復的工作了。
**AOI**:仔細想想,在Blender或者一般的3D軟件里面有什么值,是會不斷的變化的呢?或者說,什么值是會自動遞增的呢?
**H子**:自動遞增的值嘛?我想想看......啊!是時間!時間軸的值!
**AOI**:對了,嚴格來說,就是幀數。當你開始播放的時候,這個是一個會不斷遞增的值。而在Sverchok的節點里面,有一個名為**Frame Info**的節點可以獲得當前幀,并且作為值輸出。

搜索Frame Info就可以得到一個唯一結果。創建節點即可。
創建一個**Stethoscope MK2**節點,并且分別吧**Frame Info**節點的每個出口輸出到**Stethoscope MK2**節點的Date入口看看有什么效果吧。
**H子**:好,我試試看!




啊,我懂了,就是當前幀,起始和結束幀,以及當前幀位于起點到終點的百分比吧。
**AOI**:對的,就是這樣。所以實際上會變化的數據只有兩個,也就是CurrentFrame和Evaluate。
因此我們可以選擇吧**CurrentFrame**出口直接連到**Random Vector MK2**節點的**Seed**入口。即可每一幀都改變產生的隨機值了。當然,根據需要,可以使用**Math**節點對**CurrentFrame**輸出的數據進行處理,然后再使用。
**H子**:好的!我連好了!但是總不能每次創建模型都要手動創建吧。
**AOI**:當然不是的,接下來,就給你說說看怎么進行自動化的批量生產吧。
* * * * *
## **肆、自動化產生石頭吧!AnimationNode和腳本的運用**。
**AOI**:已經裝好AnimationNode插件了吧,激活AnimationNode插件之后,就可以在節點編輯器窗口里面切換到AnimationNode界面了。
就是如圖所示的小圖標,點擊切換過去就好了。

同樣是先創建一個節點樹吧。
**H子**:老師我做好了!但是,為什么要在這里使用另外一個插件呢。
**AOI**:在這里使用AnimationNode的主要原因,自然就是為了使用他的腳本節點了。使用腳本節點,可以根據需要執行腳本。
**H子**:啊,我懂了,也就是說,只要把BAKE按鈕執行的命令,在AnimationNode的腳本節點里面運行就可以了對吧。
**AOI**:沒錯,你理解得很快呢。那么我們就開始操作吧。
首先,創建一個腳本節點。
對了,AnimationNode的搜索快捷鍵是**Ctrl+A**,要好好記住。

創建好腳本節點之后如圖所示:

可以在這里選擇或者新建一份腳本

可以在此處修改腳本節點的名稱,名稱非常重要,因為之后對腳本節點的調用也是以名字為依據。

在這里,我們就吧他改成AutoBake好了
然后,點擊NewInput按鈕即可創建不同的數據類型輸入接口

Script節點的運用,我們就在后面一步一步的細說吧。
為了利用腳本,首先必須有一份腳本。可以通過腳本選擇框旁邊的+按鈕創建,也可以在文本編輯器窗口創建,總之方法很多,無需拘泥。
在這里,就直接點擊旁邊的+按鈕創建吧。
**H子**:我創建好了,程序自動的就根據我的節點名稱創建了同名的腳本了阿

**AOI**:然后在界面上劃分出文本編輯器的窗口,并且選擇剛剛創建好的腳本。
**H子**:好的。

是一份空白的腳本呢。然后我就可以往里面編寫代碼了嗎。
**AOI**:對的,就是這樣。
接下來,就去獲得BAKE模型的時候使用的命令吧。
切換回到Sverchok界面,并且點擊一下BAKE按鈕,拉出信息欄,即可見到BAKE操作對應的命令了。

右鍵選擇該行命令,同時按Ctrl+C組合鍵復制命令。
然后再把命令粘貼到腳本里面。基本的操作就大功告成了。

**H子**:那么老師,我有個問題。Script腳本,可以允許沒有輸入和輸出接口,如果這樣的話,不就是只要創建了腳本節點,并擺在畫布上,不管有沒有其他節點流程鏈接進來,都會每一幀都會執行一次了嗎。
**AOI**:你說的很對,所以我們必須進行一些操作,確保不會自動的就執行代碼,并且產生一大堆無用而重復的數據。
首先,切換到AnimationNode節點模式,按T打開工具欄,并切換到AnimationNodes工具欄

在Auto Execution面板下面,去掉Always的勾,并勾上Frame Chaneged。這樣的話動畫節點樹就不會總是執行,而只會在當幀有改變的時候才執行一次。
**H子**:等等老師,你這話的意思不就是在說,原本的動畫節點樹,是每一幀都在運行的嗎?
**AOI**:你說的并不完全對。
你看到AnimationNode節點編輯器左上角有個毫秒數的數字嗎,那個就是執行完一次動畫節點樹消耗的毫秒數了。之所以一開始是0ms,是因為并沒有任何節點在運行,所以也并沒有任何時間的消耗。但是當你添加任意一個節點進入到畫布的時候,時間就會有變動,也就是說,腳本被執行了。
也由此可見,實際上在AnimationNode中,每個節點的執行本質上是獨立的,他們都會被主動調用,而通過出入口起來,只是為了讓節點流的數據得以按照一個從左到右的方向傳遞和計算。
所以,關閉了Auto Execution的Always的意義,就在于讓腳本不要時刻不停的自動執行下去。
因此,動畫節點樹并非每幀執行一次,而是不斷的執行,與幀無關,只要全部執行完一次,就會自動開始第二次。
**H子**:那么為什么明明添加了Script節點,但是卻沒有執行呢。
**AOI**:這個問題問得好。其原因在于,因為Script節點,其實分成了兩部分。一部分是用于定義,而一部分則是用于執行。創建Script節點的時候,實際上是創建了用于定義部分。只有把用于執行部分的節點創建到畫布上的時候,才會執行。
**H子**:那么,我們該怎么添加執行部分的節點呢。
**AOI**:點擊菜單欄的Subprograms按鈕,再彈出的菜單欄即可見到剛剛創建和和命名為AutoBake的選項了。點擊該選項,即可在畫布上創建一個執行Auto Bake腳本


可以看到,我們現在創建了一個既沒有入口,也沒有出口的名為AutoBake的**Invoke Subprogram**節點,點擊中間的AutoBake按鈕,即可在彈出的菜單選擇并切換成其他的Script節點定義。
那么**H子**,我問你一個問題:
假設,我想讓腳本每隔3幀執行一次,有什么辦法可以做到呢?
**H子**:我想想看......首先是要傳遞一個幀數據進來,然后在腳本里面添加判斷,如果當前幀除以3并且余數為0的時候,就執行一次腳本......對吧。
**AOI**:沒錯,基本的思路就是這樣子......
**H子**:欸?!奇怪了老師,為什么沒有FrameInfo節點呢。
**AOI**:因為在AnimationNode里面傳遞幀的節點更簡單,只傳遞當前幀,而且叫做**Time Info**

好吧,然后接下來為AutoBake的Script節點增加一個float型的數據入口吧。這樣就可以把時間數據傳遞到Script節點內部,并且以變量的形式被代碼調用。
如圖所示,點擊Script 的 New Input按鈕,在彈出的搜索框內輸入Float,即可搜到Float和Float List兩個結果,Float List就是Float數據類型的列表,在此處,我們只需要使用一個數據即可,所以無需使用數組類型。

點擊選擇Float即可完成一個Float入口的創建。

完成Float型入口的創建之后,默認的入口命名是Float,此處將其改成TimeInfo,入口的名稱同時也是在腳本里面進行調用的變量名。
**H子**:老師我做好了,得到了變量之后,就是說我可以吧**Time Info**節點的輸出值連到這個入口吧。然后只要進一步在代碼里面設置判斷條件就可以了吧?
**AOI**:對的,那么,你試試看把代碼完成看看。
**H子**:我已經寫好了,老師看看這個對不對。
~~~
if(TimeInfo > 0 and TimeInfo % 3 == 0):
bpy.ops.node.sverchok_mesh_baker_mk2(idname="Viewer Draw", idtree="NodeTree")
~~~
**AOI**:嗯,不錯,連幀值為0的時候也考慮到了,考慮得挺全面得。
這樣直接執行就可以每隔3幀Bake一次模型了。只要打開時間軸,就可以得到大量得隨機產生的石頭了。
**H子**:我運行了一下,看起來是這樣子的。

確實達成目的了,每3幀執行一次,并且得到了模型。但是都疊在了一起了。
那不是只能一個個手動拖開了嗎?
**AOI**:那就交給你自己思考了。實際上在Sverchok的節點里面,有著很多可以操作各種數據類型的節點。可以考慮運用這些節點,對隨機產生的數據進行操作。并且得到矯正過的數據。最后再轉化成模型。
**H子**:好的!我想想去!
* * * * *
## **終、**H子**的努力成果**
**H子**最后在邊查文檔邊記錄操作腳本等一系列的努力下,把程序化石頭生成器最終版本完成到了如下的程度:
* 把Z軸高度小于0的頂點高度都設置成0,確保的石頭底部扁平
* 烘焙出的模型自動進行對象和數據重命名
* 自動從色表上選取隨機顏色,并添加到石頭的頂點顏色上。
* 把烘焙的模型隨機的分布在場景上,以避免模型的堆疊。
最終版本節點圖:
Sverchok節點圖。節點樹名稱為【NodeTree】:

Clamp節點圖:

AnimationNode節點圖。節點樹名稱為【BakeStone】:

AutoBake腳本:
~~~
FinObject = None
if(TimeInput > 0 and TimeInput % 10 == 0):
#執行Bake命令
bpy.ops.node.sverchok_mesh_baker_mk2(idname="Viewer Draw", idtree="NodeTree")
#取消全選
bpy.ops.object.select_all(action='DESELECT')
#obj獲取對象名稱為"Sv_0"的模型。因為Bake命令得到的模型默認為Sv_0
obj = bpy.data.objects["Sv_0"]
#將模型設置為選中狀態
obj.select = True
#按照創建時的幀數為模型重命名,重命名包括對象名和多邊形名
name = "Rock" + str(TimeInput)
obj.name = name
obj.data.name = name
#將模型對象返回給輸出節點,以便用于后續的操作
FinObject = obj
~~~
最終完成的效果如圖:


* * * * *
*附注:因為插件本身并沒有中文支持,如果使用中文Blender界面,插件的文本有部分會因為Blender自身的多語言支持特性而被翻譯成中文,但是會使得插件本身的搜索功能變差,甚至無法通過搜索找到想要的節點。所以建議使用插件的時候盡量使用英文界面。*