Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

你的第二個 3D 著色器

從高級設定開始,Godot所做的是為使用者提供一組可選設定的參數("環境光遮蔽" , "次表面散射強度" , "邊緣" 等等)這些參數對應不同的複雜效應(環境遮擋, 次表面散射, 邊緣照明等等)如果沒有寫入, 程式碼在編譯之前被拋出, 因此著色器不會產生額外功能的成本. 這使得使用者很容易擁有複雜的支援PBR著色, 而不需要編寫複雜的著色器. 當然,Godot還允許您忽略所有這些參數, 並編寫一個完全定制的著色器.

有關這些參數的完整列表, 請參見 空間著色器 參考文件.

頂點函式和片段函式的區別在於, 頂點函式是按頂點運作的, 並設定諸如 VERTEX (座標)和 NORMAL 等屬性, 而片段著色器是按像素運作的, 最重要的是設定 MeshALBEDO 顏色.

第一個空間片段函式

如本教學前一部分所述. 在Godot中, 片段函式的標準用法是設定不同的材質屬性, 然後讓Godot處理剩下的部分. 為了提供更大的靈活性,Godot還提供了渲染模式. 渲染模式設定在著色器的頂部, 直接在 "著色_方式" 下面, 它們指定了你想要著色器的內建方面具有什麼樣的功能.

例如, 如果你不想讓燈光影響一個物體, 設定渲染模式為 "無陰影":

render_mode unshaded;

您還可以將多個渲染模式堆疊在一起。例如,如果你想使用卡通材質而不是更真實的 PBR 材質,將漫反射模式和鏡面反射模式設定為卡通:

render_mode diffuse_toon, specular_toon;

這個內建功能模型允許您通過更改幾個參數來編寫複雜的自訂著色器.

有關渲染模式的完整列表, 請參見空間著色器參考 Spatial shader reference.

在本教學的這一部分中, 我們將介紹如何將前一部分的崎嶇地形變成海洋.

首先讓我們設定水的顏色. 我們通過設定 ALBEDO 來做到這一點.

ALBEDO 是一個 vec3 , 包含物體的顏色.

我們把它調成藍色.

void fragment() {
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/albedo.png

我們將其設定為深藍色, 因為水的大部分藍色來自天空的反射.

PBR模型的Godot使用者兩個主要參數:"金屬度" 和 "粗糙度".

粗糙度是指材料表面的光滑程度. 低 "粗糙度" 會使材料看起來像閃亮的塑膠, 而高粗糙度使材料在顏色上看起來更堅實.

METALLIC 指定該物體有多像金屬, 它最好設定為接近 01 . 把 METALLIC 看作是改變反射和 ALBEDO 顏色之間的平衡. 高的 METALLIC 幾乎完全忽略了 ALBEDO , 看起來像天空的鏡子. 而低的 METALLIC 對天空的顏色和 ALBEDO 的顏色有一個更平實的表現.

粗糙度 從左到右從0增加到1, 而 "金屬度" 從上到下從0增加到1.

../../../_images/PBR.png

備註

對恰當的PBR陰影,"金屬度" 應當接近0或者1. 為了混合不同的材料, 只有將其設定在0和1之間.

水不是金屬,所以我們將其 METALLIC 屬性設定成 0.0。水的反射性也很高,因此我們將其``ROUGHNESS`` 屬性也設定得非常低。

void fragment() {
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/plastic.png

現在,我們有了光滑的塑膠外觀表面。現在該考慮要類比的水的某些特定屬性了。這裡有兩種主要的方法可以把詭異的塑膠表面變成好看的水。首先是鏡面反射(Specular)。鏡面反射是那些來自太陽直接反射到你眼裡的明亮斑點。第二個是菲涅耳反射(Fresnel)。菲涅爾反射是物體在小角度下更具反射性的屬性。這就是為什麼你可以看見自己身下的水,卻在更遠處看見天空倒影的原因。

為了增強鏡面反射,我們需要做兩件事。首先,由於卡通渲染模式具有更高的鏡面反射高光,我們將更改鏡面反射為卡通渲染模式。

render_mode specular_toon;
../../../_images/specular-toon.png

其次, 我們將新增邊緣照明. 邊緣照明增加了掠射角度的光線效果. 通常, 它用於類比光線穿過物件邊緣上的織物的路徑, 但是我們將在此處使用它來幫助實作良好的水潤效果.

void fragment() {
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01;
  ALBEDO = vec3(0.1, 0.3, 0.5);
}
../../../_images/rim.png

In order to add fresnel reflectance, we will compute a fresnel term in our fragment shader. Here, we aren't going to use a real fresnel term for performance reasons. Instead, we'll approximate it using the dot product of the NORMAL and VIEW vectors. The NORMAL vector points away from the mesh's surface, while the VIEW vector is the direction between your eye and that point on the surface. The dot product between them is a handy way to tell when you are looking at the surface head-on or at a glancing angle.

float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));

並將其混合到 ROUGHNESSALBEDO。這是 ShaderMaterial 比 SpatialMaterial 的好處。使用 SpatialMaterial,我們可以用紋理來設定這些屬性,或者設定成一個均勻的數字。但是用著色器,我們可以根據我們能想到的任何數學函式來設定它們。

void fragment() {
  float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
  RIM = 0.2;
  METALLIC = 0.0;
  ROUGHNESS = 0.01 * (1.0 - fresnel);
  ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel);
}
../../../_images/fresnel.png

而現在, 只需要5行程式碼, 你就可以擁有看起來很複雜的水. 現在, 我們有了照明, 這個水看起來太亮了. 讓我們把它變暗. 這可以通過減少我們傳入 ALBEDOvec3 的值來輕鬆實作. 讓我們把它們設定為 vec3(0.01, 0.03, 0.05) .

../../../_images/dark-water.png

TIME 做動畫

回到頂點功能,我們可以使用內建變數 TIME 對波浪進行動畫處理。

TIME 是一個內建變數,可從頂點和片段函式存取。

在上一個教學中,我們通過從高度圖讀取來計算高度。對於本教學,我們將做同樣的事情。將高度圖程式碼放在一個名為 height() 的函式中。

float height(vec2 position) {
  return texture(noise, position / 10.0).x; // Scaling factor is based on mesh size (this PlaneMesh is 10×10).
}

為了在 height() 函式中使用 TIME,我們需要將其傳遞進去。

float height(vec2 position, float time) {
}

確保其正確傳遞到頂點函式中.

void vertex() {
  vec2 pos = VERTEX.xz;
  float k = height(pos, TIME);
  VERTEX.y = k;
}

而不是使用法線貼圖來計算法線。我們將在 vertex() 函式中手動計算它們。為此,請使用以下程式碼行。

NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME)));

我們需要手動計算 NORMAL,因為在下一節中,我們將使用數學來建立外觀複雜的波形。

現在,我們要通過使 positon 偏移 TIME 的餘弦來使 height() 函式更加複雜。

float height(vec2 position, float time) {
  vec2 offset = 0.01 * cos(position + time);
  return texture(noise, (position / 10.0) - offset).x;
}

這會實作緩慢移動的波紋效果, 但顯得有點不自然. 下一節將深入探討, 通過加入更多的數學函式, 來用著色器實作更複雜的效果, 比如更加真實的波紋.

進階效果:水波

利用數學, 著色器可以實作複雜的效果, 這是著色器的強大之處. 為闡述這一點, 我們將修改 height() 函式和引入新函式 wave() , 來讓波紋效果更進一層.

wave() 有一個參數, position, 和在 height() 中一樣.

我們將在 height() 函式中多次呼叫 wave() 函式, 來改變波紋的樣子.

float wave(vec2 position){
  position += texture(noise, position / 10.0).x * 2.0 - 1.0;
  vec2 wv = 1.0 - abs(sin(position));
  return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);
}

這在一開始會讓人覺得很複雜, 所以我們一行一行地來實作.

position += texture(noise, position / 10.0).x * 2.0 - 1.0;

通過 noise 紋理來偏移位置. 這將會使波浪成為曲線, 所以它們將不會是與網格所對齊的直線.

vec2 wv = 1.0 - abs(sin(position));

sin()position 定義一個類似波浪的函式. 通常 sin() 波是很圓的. 我們使用 abs() 去將其絕對化, 讓它有一個尖銳波峰, 並將其約束於0-1的範圍內. 然後我們再從 1.0 中減去, 將峰值放在上方.

return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);

將x方向的波乘以y方向的波, 並將其提高到使峰值變得尖銳的冪. 然後從 1.0 中減去它, 使山脊成為山峰, 並提高山脊銳化的能力.

現在我們可以用 wave() 代替 height() 函式的內容.

float height(vec2 position, float time) {
  float h = wave(position);
  return h;
}

使用模組

../../../_images/wave1.png

正弦曲線的形狀太明顯了. 所以讓我們把波型分散一下. 我們通過縮放 位置 來實作.

float height(vec2 position, float time) {
  float h = wave(position * 0.4);
  return h;
}

現在它看起來好多了.

../../../_images/wave2.png

如果我們將多個波以不同的頻率和幅度彼此疊加, 則可以做得更好. 這意味著我們將按比例縮放每個位置, 以使波形更細或更寬(頻率). 我們將乘以波的輸出, 以使它們變低或變高(振幅).

下面以四種波形為例, 說明如何將四種波形分層, 以達到更漂亮的波形效果.

float height(vec2 position, float time) {
  float d = wave((position + time) * 0.4) * 0.3;
  d += wave((position - time) * 0.3) * 0.3;
  d += wave((position + time) * 0.5) * 0.2;
  d += wave((position - time) * 0.6) * 0.2;
  return d;
}

請注意, 我們把時間加到兩個上, 再從另外兩個上減去. 這使得波在不同的方向上移動, 產生了複雜的效果. 還要注意, 振幅(結果乘以的數位)全部加起來是 1.0. 這使波浪保持在0-1的範圍內.

有了這段程式碼, 你應該可以得到更複雜的波形, 而你所要做的只是增加一點數學運算!

../../../_images/wave3.png

有關空間著色器的更多資訊, 請閱讀 Shading Language 文件和 Spatial Shaders 文件. 也可以看看 Shading 部分3D 部分的高級教學.