著色器入門:符號距離函數!
大家好!不久前我學會了如何使用著色器製作有趣的閃亮旋轉八面體:
我的著色器能力仍然非常基礎,但事實證明製作這個有趣的旋轉八面體比我想像中要容易得多(從其他人那裡複製了很多代碼片段!)。
我在做這件事時, 從一個非常有趣的叫做 符號距離函數教程:盒子和氣球 的教程中學到了「符號距離函數」的重要思路。
在本文中,我將介紹我用來學習編寫簡單著色器的步驟,並努力讓你們相信著色器並不難入門!
更高級著色器的示例
如果你還沒有看過用著色器做的真正有趣的事情,這裡有幾個例子:
- 這個非常複雜的著色器就像一條河流的真實視頻:https://www.shadertoy.com/view/Xl2XRW
- 一個更抽象(更短!)有趣的著色器,它有很多發光的圓圈:https://www.shadertoy.com/view/lstSzj
步驟一:我的第一個著色器
我知道你可以在 shadertoy 上製作著色器,所以我去了 https://www.shadertoy.com/new。它們提供了一個默認著色器,如下圖所示:
代碼如下:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// 規範像素坐標 (從 0 到 1)
vec2 uv = fragCoord / iResolution.xy;
// 隨時間改變像素顏色
vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0, 2, 4));
// 輸出到屏幕
fragColor = vec4(col, 1.0);
}
雖然還沒有做什麼令人興奮的事情,但它已經教會了我著色器程序的基本結構!
思路:將一對坐標(和時間)映射到一個顏色
這裡的思路是獲得一對坐標作為輸入(fragCoord
),你需要輸出一個 RGBA 向量作為此坐標的顏色。該函數也可以使用當前時間(iTime
),圖像從而可以隨時間變化。
這種編程模型(將一對坐標和時間映射到其中)的巧妙之處在於,它非常容易並行化。我對 GPU 了解不多,但我的理解是,這種任務(一次執行 10000 個微不足道的可並行計算)正是 GPU 擅長的事情。
步驟二:使用 shadertoy-render
加快開發迭代
玩了一段時間的 shadertoy 之後,我厭倦了每次保存我的著色器時都必須在 shadertoy 網站上單擊「重新編譯」。
我找到了一個名為 shadertoy-render 命令行工具,它會在每次保存時實時查看文件並更新動畫。現在我可以運行:
shadertoy-render.py circle.glsl
並更快地開發迭代!
步驟三:畫一個圓圈
接下來我想 —— 我擅長數學!我可以用一些基本的三角學來畫一個會彈跳的彩虹圈!
我知道圓的方程為(x^2 + y^2 = 任意正數
!),所以我寫了一些代碼來實現它:
代碼如下:(你也可以 在 shadertoy 上查看)
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// 規範像素坐標 (從 0 到 1)
vec2 uv = fragCoord / iResolution.xy;
// 繪製一個中心位置依賴於時間的圓
vec2 shifted = uv - vec2((sin(iGlobalTime) + 1) / 2, (1 + cos(iGlobalTime)) / 2);
if (dot(shifted, shifted) < 0.03) {
// 改變像素顏色
vec3 col = 0.5 + 0.5 * cos(iGlobalTime + uv.xyx + vec3(0, 2, 4));
fragColor = vec4(col, 1.0);
} else {
// 使圓之外的其他像素都是黑色
fragColor = vec4(0,0, 0, 1.0);
}
}
代碼將坐標向量 fragCoord
與自身點積,這與計算 x^2 + y^2
相同。我還在這個圓圈的中心玩了一點花活 – 圓心為 vec2((sin(iGlobalTime) + 1)/ 2,(1 + cos(faster)) / 2
,這意味著圓心也隨著時間沿另一個圓移動。
著色器是一種學習數學的有趣方式!
我覺得有意思的(即使我們沒有做任何超級高級的事情!)是這些著色器為我們提供了一種有趣的可視化方式學習數學 - 我用 sin
和 cos
來使某些東西沿著圓移動,如果你想更直觀地了解三角函數的工作方式, 也許編寫著色器會是一種有趣的方法!
我喜歡的是,可以獲得有關數學代碼的即時視覺反饋 - 如果你把一些東西乘以 2,圖像里的東西會變得更大!或更小!或更快!或更慢!或更紅!
但是我們如何做一些真正有趣的事情呢?
這個會彈跳的圓圈很好,但它與我見過的其他人使用著色器所做的非常奇特的事情相去甚遠。那麼下一步要做什麼呢?
思路:不要使用 if 語句,而是使用符號距離函數!
在我上面的圓圈代碼中,我基本上是這樣寫的:
if (dot(uv, uv) < 0.03) {
// 圓里的代碼
} else {
// 圓外的代碼
}
但問題(也是我感到卡住的原因)是不清楚如何將它推廣到更複雜的形狀!編寫大量的 if
語句似乎不太好用。那人們要如何渲染這些 3d 形狀呢?
所以! 符號距離函數 是定義形狀的另一種方式。不是使用硬編碼的 if
語句,而是定義一個 函數,該函數告訴你,對於世界上的任何一個點,該點與你的形狀有多遠。比如,下面是球體的符號距離函數。
float sdSphere( vec3 p, float center )
{
return length(p) - center;
}
符號距離函數非常棒,因為它們:
- 易於定義!
- 易於組合!如果你想要一個被切去一塊的球體, 你可以用一些簡單的數學來計算並集/交集/差集。
- 易於旋轉/拉伸/彎曲!
製作旋轉陀螺的步驟
當我開始時,我不明白需要編寫什麼代碼來製作一個閃亮的旋轉東西。結果表明如下是基本步驟:
- 為想要的形狀創建一個符號距離函數(在我的例子里是八面體)
- 光線追蹤符號距離函數,以便可以在 2D 圖片中顯示它(或沿光線行進?我使用的教程稱之為光線追蹤,我還不明白光線追蹤和光線行進之間的區別)
- 編寫代碼處理形狀的表面紋理並使其發光
我不打算在本文中詳細解釋符號距離函數或光線追蹤,因為我發現這個 關於符號距離函數的神奇教程 非常友好,老實說,它比我做的更好,它解釋了如何執行上述 3 個步驟,並且代碼有大量的注釋,非常棒。
- 該教程名為「符號距離函數教程:盒子和氣球」,它在這裡:https://www.shadertoy.com/view/Xl2XWt
- 這裡有大量符號距離函數,你可以將其複製粘貼到代碼中(以及組合它們以製作其他形狀的方法):http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
步驟四:複製教程代碼並開始更改內容
我在這裡使用了久負盛名的編程實踐,即「複製代碼並以混亂的方式更改內容,直到得到我想要的結果」。
最後一堆閃亮的旋轉八面體著色器在這裡:https://www.shadertoy.com/view/wdlcR4
動畫出來的樣子是這樣的:
為了做到這一點,我基本上只是複製了關於符號距離函數的教程,該函數根據符號距離函數呈現形狀,並且:
- 將
sdfBalloon
更改為sdfOctahedron
,並使八面體旋轉而不是在我的符號距離函數中靜止不動 - 修改
doBalloonColor
著色功能,使其有光澤 - 有很多八面體而不是一個
使八面體旋轉!
下面是我用來使八面體旋轉的代碼!事實證明這真的很簡單:首先從 這個頁面 複製一個八面體符號距離函數,然後添加一個 rotate
使其根據時間旋轉,然後它就可以旋轉了!
vec2 sdfOctahedron( vec3 currentRayPosition, vec3 offset ){
vec3 p = rotate((currentRayPosition), offset.xy, iTime * 3.0) - offset;
float s = 0.1; // s 是啥?
p = abs(p);
float distance = (p.x + p.y + p.z - s) * 0.57735027;
float id = 1.0;
return vec2( distance, id );
}
用一些噪音讓它發光
我想做的另一件事是讓我的形狀看起來閃閃發光/有光澤。我使用了在 這個 GitHub gist 中找到的雜訊函數使表面看起來有紋理。
以下是我如何使用雜訊函數的代碼。基本上,我只是隨機地將參數更改為雜訊函數(乘以 2?3?1800?隨你!),直到得到喜歡的效果。
float x = noise(rotate(positionOfHit, vec2(0, 0), iGlobalTime * 3.0).xy * 1800.0);
float x2 = noise(lightDirection.xy * 400.0);
float y = min(max(x, 0.0), 1.0);
float y2 = min(max(x2, 0.0), 1.0);
vec3 balloonColor = vec3(y, y + y2, y + y2);
編寫著色器很有趣!
上面就是全部的步驟了!讓這個八面體旋轉並閃閃發光使我很開心。如果你也想用著色器製作有趣的動畫,希望本文能幫助你製作出很酷的東西!
通常對於不太了解的主題,我可能在文章中說了至少一件關於著色器的錯誤事情,請讓我知道錯誤是什麼!
再說一遍,如下是我用到的兩個資源:
- 「符號距離函數教程:盒子和氣球」:https://www.shadertoy.com/view/Xl2XWt(修改和玩起來真的很有趣)
- 可以將大量符號距離函數複製並粘貼到你的代碼中:http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
via: https://jvns.ca/blog/2020/03/15/writing-shaders-with-signed-distance-functions/
作者:Julia Evans 選題:lujun9972 譯者:Starryi 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive