Linux中國

著色器入門:符號距離函數!

大家好!不久前我學會了如何使用著色器製作有趣的閃亮旋轉八面體:

我的著色器能力仍然非常基礎,但事實證明製作這個有趣的旋轉八面體比我想像中要容易得多(從其他人那裡複製了很多代碼片段!)。

我在做這件事時, 從一個非常有趣的叫做 符號距離函數教程:盒子和氣球 的教程中學到了「符號距離函數」的重要思路。

在本文中,我將介紹我用來學習編寫簡單著色器的步驟,並努力讓你們相信著色器並不難入門!

更高級著色器的示例

如果你還沒有看過用著色器做的真正有趣的事情,這裡有幾個例子:

  1. 這個非常複雜的著色器就像一條河流的真實視頻:https://www.shadertoy.com/view/Xl2XRW
  2. 一個更抽象(更短!)有趣的著色器,它有很多發光的圓圈: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,這意味著圓心也隨著時間沿另一個圓移動。

著色器是一種學習數學的有趣方式!

我覺得有意思的(即使我們沒有做任何超級高級的事情!)是這些著色器為我們提供了一種有趣的可視化方式學習數學 - 我用 sincos 來使某些東西沿著圓移動,如果你想更直觀地了解三角函數的工作方式, 也許編寫著色器會是一種有趣的方法!

我喜歡的是,可以獲得有關數學代碼的即時視覺反饋 - 如果你把一些東西乘以 2,圖像里的東西會變得更大!或更小!或更快!或更慢!或更紅!

但是我們如何做一些真正有趣的事情呢?

這個會彈跳的圓圈很好,但它與我見過的其他人使用著色器所做的非常奇特的事情相去甚遠。那麼下一步要做什麼呢?

思路:不要使用 if 語句,而是使用符號距離函數!

在我上面的圓圈代碼中,我基本上是這樣寫的:

if (dot(uv, uv) < 0.03) {
    // 圓里的代碼
} else {
    // 圓外的代碼
}

但問題(也是我感到卡住的原因)是不清楚如何將它推廣到更複雜的形狀!編寫大量的 if 語句似乎不太好用。那人們要如何渲染這些 3d 形狀呢?

所以! 符號距離函數 Signed distance function 是定義形狀的另一種方式。不是使用硬編碼的 if 語句,而是定義一個 函數,該函數告訴你,對於世界上的任何一個點,該點與你的形狀有多遠。比如,下面是球體的符號距離函數。

float sdSphere( vec3 p, float center )
{
  return length(p) - center;
}

符號距離函數非常棒,因為它們:

  • 易於定義!
  • 易於組合!如果你想要一個被切去一塊的球體, 你可以用一些簡單的數學來計算並集/交集/差集。
  • 易於旋轉/拉伸/彎曲!

製作旋轉陀螺的步驟

當我開始時,我不明白需要編寫什麼代碼來製作一個閃亮的旋轉東西。結果表明如下是基本步驟:

  1. 為想要的形狀創建一個符號距離函數(在我的例子里是八面體)
  2. 光線追蹤符號距離函數,以便可以在 2D 圖片中顯示它(或沿光線行進?我使用的教程稱之為光線追蹤,我還不明白光線追蹤和光線行進之間的區別)
  3. 編寫代碼處理形狀的表面紋理並使其發光

我不打算在本文中詳細解釋符號距離函數或光線追蹤,因為我發現這個 關於符號距離函數的神奇教程 非常友好,老實說,它比我做的更好,它解釋了如何執行上述 3 個步驟,並且代碼有大量的注釋,非常棒。

步驟四:複製教程代碼並開始更改內容

我在這裡使用了久負盛名的編程實踐,即「複製代碼並以混亂的方式更改內容,直到得到我想要的結果」。

最後一堆閃亮的旋轉八面體著色器在這裡: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);

編寫著色器很有趣!

上面就是全部的步驟了!讓這個八面體旋轉並閃閃發光使我很開心。如果你也想用著色器製作有趣的動畫,希望本文能幫助你製作出很酷的東西!

通常對於不太了解的主題,我可能在文章中說了至少一件關於著色器的錯誤事情,請讓我知道錯誤是什麼!

再說一遍,如下是我用到的兩個資源:

  1. 「符號距離函數教程:盒子和氣球」:https://www.shadertoy.com/view/Xl2XWt(修改和玩起來真的很有趣)
  2. 可以將大量符號距離函數複製並粘貼到你的代碼中: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

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國