你的第一個 2D 著色器

前言

著色器是一種運行於 GPU 上的特殊程式,用來算繪圖像。現今的圖形算繪幾乎都仰賴著色器完成。若想更深入瞭解著色器,請參閱 著色器是什麼

本教學將專注於著色器實作,帶你一步步寫出同時包含頂點與片段函式的著色器。本篇適合從未寫過著色器的新手。

備註

如果你已有著色器經驗,只是想快速了解 Godot 著色器的架構,請參考 著色器參考

設定

CanvasItem 著色器 用於繪製 Godot 中所有 2D 物件,而 Spatial 著色器 則用於繪製所有 3D 物件。

要使用著色器,必須將其附加在 Material。若要讓多個物件共用同一材質,必須各自加上該材質。

所有繼承自 CanvasItem 的物件都有材質屬性。這包括所有 GUI 元件Sprite2DTileMapLayerMeshInstance2D 等等。它們也可以選擇繼承父節點的材質。如果你有大量節點要共用同一材質,這個功能會很方便。

首先,建立一個 Sprite2D 節點。你可以用任何 CanvasItem,只要它能在畫布上繪圖即可。這裡我們選用 Sprite2D,因為它是最容易上手的 CanvasItem。

在屬性檢視器中,點擊「貼圖」旁「[空白]」的地方並選擇「載入」,接著選擇「icon.svg」。新專案預設會有這個 Godot 圖示。現在你應該會在視埠看到這個圖示。

接著,在屬性檢視器 CanvasItem 區塊,點擊「材質」旁邊並選擇「新建 ShaderMaterial」。這會建立一個新的材質資源。再點擊出現的球體。Godot 目前還無法分辨你要寫的是 CanvasItem 著色器還是 Spatial 著色器,因此預覽時會顯示 Spatial 著色器的結果。所以你目前看到的是預設的 Spatial 著色器輸出。

備註

所有繼承自 Material 的材質資源(例如 StandardMaterial3DParticleProcessMaterial),都可以轉換為 ShaderMaterial,現有的屬性會轉為對應的文字著色器。要進行轉換,只需在檔案系統面板中右鍵點選該材質並選擇 轉換成 ShaderMaterial。你也可以在屬性檢視器中,對任何指向材質的屬性右鍵進行同樣操作。

然後點擊「著色器」旁邊並選擇「新建 Shader」。最後再點擊剛剛建立的著色器資源,著色器編輯器就會開啟。你現在已經可以開始撰寫你的第一個著色器了。

你的第一個 CanvasItem 著色器

在 Godot 中,所有著色器的第一行都要標明型別,格式如下:

shader_type canvas_item;

由於本例要寫的是 CanvasItem 著色器,所以第一行要指定 canvas_item,所有程式碼都寫在這行下面。

這一行會告訴引擎該提供哪些內建變數及功能。

在 Godot 中,你可以重寫三個函式來控制著色器行為,分別是 vertex (頂點)、 fragment (片段)和 light (光照)。本教學主要帶你寫出同時包含頂點和片段函式的著色器。光照函式比前兩者複雜許多,這裡不會討論。

你的第一個片段函式

片段函式會針對 Sprite2D 的每一個像素運行,用來決定每個像素的顏色。

片段函式只會影響 Sprite2D 覆蓋到的像素,也就是說,你無法用它來在精靈周圍畫出外框等效果。

最簡單的片段函式就是讓每個像素都呈現同一個顏色。

我們只需將一個 vec4 寫入內建變數 COLOR 即可。vec4 是由四個數字組成的向量。想瞭解更多向量知識,請參閱 向量數學教學COLOR 同時是片段函式的輸入和最終輸出。

void fragment(){
  COLOR = vec4(0.4, 0.6, 0.9, 1.0);
}
../../../_images/blue-box.png

恭喜你!你已經在 Godot 完成了你的第一個著色器。

接下來我們來增加一些進階內容。

片段函式有許多可用的輸入變數來計算 COLOR,其中之一是 UV。UV 座標會自動設定在 Sprite2D 上(即使你沒特別指定!),它們會告訴著色器該從貼圖的哪個位置取樣。

在片段函式中你只能讀取 UV,但你可以在其他函式裡使用,或直接用來設定 COLOR

UV 的取值範圍是 0 到 1,分別對應左右與上下。

../../../_images/iconuv.png
void fragment() {
  COLOR = vec4(UV, 0.5, 1.0);
}
../../../_images/UV.png

使用 TEXTURE 內建變數

預設情況下,片段函式會自動從 Sprite2D 的貼圖取樣並顯示出來。

當你想調整 Sprite2D 的顏色時,可以像下例程式碼那樣手動從貼圖讀取顏色再進行調整。

void fragment(){
  // This shader will result in a blue-tinted icon
  COLOR.b = 1.0;
}

某些節點(如 Sprite2D)有專屬的貼圖變數,可在著色器中用 TEXTURE 存取。如果你想將 Sprite2D 的貼圖與其他顏色混合,可以用 UV 搭配 texture 函式取得貼圖顏色,再進行處理。這樣可以在著色器內重繪 Sprite2D。

void fragment(){
  COLOR = texture(TEXTURE, UV); // Read from texture again.
  COLOR.b = 1.0; //set blue channel to 1.0
}
../../../_images/blue-tex.png

Uniform 輸入

Uniform 輸入可用來將外部資料傳入著色器,而且整個著色器運作時都保持一致。

你可以這樣在著色器頂部定義 uniform 變數來使用:

uniform float size;

更多 uniform 用法請參考 著色語言文件

加入一個 uniform 來調整我們 Sprite2D 的藍色量。

uniform float blue = 1.0; // you can assign a default value to uniforms

void fragment(){
  COLOR = texture(TEXTURE, UV); // Read from texture
  COLOR.b = blue;
}

現在你可以直接在編輯器調整 Sprite2D 的藍色量了。回到屬性檢視器,你會在「Shader Param」區塊看到剛剛宣告的 uniform。只要在編輯器改動這個值,就會覆蓋著色器裡的預設值。

從程式碼與著色器互動

你可以透過節點的材質資源呼叫 set_shader_parameter() 來從程式碼設定 uniform。以 Sprite2D 為例,下面的程式碼會設定 blue 這個 uniform。

var blue_value = 1.0
material.set_shader_parameter("blue", blue_value)

請注意 uniform 名稱是字串,必須與著色器內變數名稱完全一致(包含大小寫)。

你的第一個頂點函式

現在我們已經有了片段函式,接下來來寫一個頂點函式。

頂點函式可用來決定每個頂點在螢幕上的最終位置。

頂點函式裡最重要的變數是 VERTEX。一開始它代表模型裡的頂點座標,你也可以給它賦值來決定頂點實際被繪製的位置。VERTEX 是一個 vec2,預設是節點本地空間(不會參考相機、視埠或父節點)。

你可以直接對 VERTEX 加值來偏移頂點。

void vertex() {
  VERTEX += vec2(10.0, 0.0);
}

搭配內建的 TIME 變數,這可以實現簡單動畫效果。

void vertex() {
  // Animate Sprite2D moving in big circle around its location
  VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0);
}

結論

本質上,著色器就是如你所見,計算 VERTEXCOLOR。進階用法則取決於你能設計出多複雜的數學策略來指定這些變數。

如需靈感,不妨參考一些進階著色器教學,也可參考 ShadertoyThe Book of Shaders 這類網站。