Shader入門 -- GLSL與GPU運行原則 Shader的基本運作方式
使用 GLSL 的基本操作
本文重點
- Shader 與 GPU 的關係
- Shader 語言 GLSL 入門
- Shader 範例
前言
在開始進行 Shader 的編寫之前,要先來了解 GLSL 的基本操作。相比於在 CPU 程式中使用的 print 或 console.log 等方法,GPU 上的 Debug 要複雜得多。在 GPU 上,我們無法像在 CPU 上那樣直接輸出日誌或訊息。然而,即使在這樣的環境下,我們仍然可以通過一些小技巧來檢查我們的 Shader 程式碼的執行情況。
Hello World
precision mediump float;
uniform vec2 u_resolution;
void main()
{
vec2 uv = gl_FragCoord.xy / u_resolution;
vec3 color = vec3(uv, 0.);
gl_FragColor = vec4(color, 1.0);
}
上面是一個可以透過 VSCode 執行的 GLSL 初始範本,跟大多數語言一樣有個 main function 入口,最主要就是對 gl_FragColor 做操作。
CPU vs GPU
而一般在 CPU 執行的程式是由一個 Thread 持續處裡一個程式,GPU 則是交由多個 Thread 去一批一批處理各個像素(Pixel)。
圖片來源:The Book of Shaders
上面的程式碼轉為 CPU 的大概就會變成
public static Color[] Pixels;
const int c_resolutionX = 1920;
const int c_resolutionY = 1080;
void Draw (int x, int y)
{
var res = new Color((float)x / c_resolutionX, (float)y / c_resolutionY, 0f);
Pixels[x * c_resolutionY + y] = res;
}
void Main ()
{
Pixels = new float[c_resolutionX * c_resolutionY];
while(true)
{
for (var x = 0; x < c_resolutionX; ++x)
{
for (var y = 0; y < c_resolutionY; ++y)
{
Draw(x, y);
}
}
}
}
上面的程式碼的意思就是跑過一個 1920x1080 的畫面,每個 Pixel 的結果根據帶入的座標來算顏色
兩段程式碼結果都會得到下面的結果
此外 GPU 執行時有兩個分常重要的限制
- 核心是完全獨立的,無法知道其他核心現在的運行狀況。
- 核心只有現在的狀況(Input),無法知道上一次執行的狀況(Input)
分析 Shader 內容
回到我們一開始的 Shader,我們該怎麼確定 gl_FragCoord 的值?與一般程式語言不同 Shader 並沒有任何的 Print 功能能幫我們知道 gl_FragCoord 的值,所以唯一能測試的方式就只有把結果印出來而已。
precision mediump float;
uniform vec2 u_resolution;
void main()
{
vec3 color = vec3(gl_FragCoord, 0.);
gl_FragColor = vec4(color, 1.0);
}
上面這個例子印出來的結果是什麼呢?
嗯?怎麼會是全黃的呢?
gl_FragCoord 帶入的值是一個二維(vec2)的值,代表該 Pixel 的座標 (x, y),如下圖。
而在電腦科學裡顏色通常是由 3 個數字組成,分別代表紅藍綠(RGB);P.S.關於色彩空間的細節可以看之前的文章;而且在 Shader 裡顏色值的範圍是介於 0~1 之間,因此直接將 gl_FragCoord 拿來用的話 就會變成最左下的值是(0, 0, 0)黑色,最下面那一行是(1, 0, 0)紅色,最左邊那一列是(0, 1, 0)紅色,而剩下的區域都是(1, 1, 0)黃色,上面的結果仔細看其實有一條綠色跟紅色的線,把結果放大來看其實就會變成如下圖。
回到最一開始的程式裡有著這麼一個行為
vec2 uv = gl_FragCoord.xy / u_resolution;
所做的事就是將寬 0 ~ 1919(1920 - 1)、高 0 ~ 1079(1080 - 1) 各自除以畫面的寬高(1920, 1080)將值縮放到 0 ~ 1 之間,得到紅藍各自 0 ~ 1 之間的混合,結果就如一開始的範例一樣,左到右紅色越來越多,下到上綠色越來越多。
Shader 的範例
上段我們可以知道 Shader 最基本的 Input 值就只有一個 pixel 的座標,但就一個如此簡單的資訊,只要結合許多數學知識也可以創造出非常驚人的畫面效果。
學習資源
The Book of Shaders
The Book of Shaders有大量基礎的範例及程式碼,還提供了一個線上的編輯器讓使用者可以測試自己的。
Shadertoy
Shadertoy上有著各路大神所創造的成果,並且可以即時觀看、編寫程式碼,你會發現很多看起來很厲害的效果,背後的程式碼可能不超過 200 行;這裡很多作品最主要是運用了 Ray Marching 這個演算法,未來也會寫文章來介紹。
結語
在遊戲開發中「渲染」絕對是無法避免的一個基本要素,雖然大多數的遊戲引擎都將渲染封裝起來,令使用者可以不用懂得太多渲染的細節,就可以完成一款遊戲的開發,但若想要讓遊戲更有風格的話,使用 Shader 就是一個必要的工具。
GuiltyGear 系列的 Toon Shader 風格是業界翹楚。
Persona 3 Reload 的 Shader 加強了遊戲中那獨特世界觀的印象。
Minecraft 用 Shader 令看似簡單的方塊世界有不同的面貌。