Shader 優化 | OpenGL 繪製網格效果

語言: CN / TW / HK

前幾天釋出了這樣一篇文章:

KodeLife | Shader 實時編輯預覽的強大工具使用實踐

除了介紹 KodeLife 的使用之外,還附帶了一個 Shader 繪製網格效果的程式碼。

把這篇文章發到技術群裡,隨機就有大佬指出不足之處,提示說程式碼還可以進一步優化,並且提供了原始碼學習。

可見加入一個高質量的技術群是多麼重要,哪怕平時不說話,圍觀大佬們聊天都能學到很多。

現在加入還來得及,尚有餘位,詳情點選如下連結:

移動端技術交流喊你入群啦~~~


Shader 講解

在我的 Shader 程式碼中是這樣繪製網格的:

    vec2 fragcoord = vec2(gl_FragCoord.xy / u_resolution);
    vec3 bgColor = vec3(1.0,1.0,1.0);
    vec3 pixelColor = bgColor;
    vec3 gridColor = vec3(0.5,0.5,0.5);

    const float width = 0.1;
    const float minWidth = 0.003;
    for(float i = 0.0; i < 1.0; i+=width){
    if (mod(fragcoord.x,width) < minWidth || mod(fragcoord.y,width) < minWidth){
            pixelColor = gridColor;
        }
    }
    gl_FragColor = vec4(pixelColor,1.0);
複製程式碼

首先,講解幾個概念:

gl_FragCoord 代表當前畫素相對於螢幕的座標,螢幕左下角為原點。

u_resolution 是當前影象的解析度。

gl_FragCoord 除以 u_resolution 得到的結果 fragcoord 就是歸一化的螢幕座標。

由於已經歸一化了那麼 fragcoord 的值就在 [0,1] 的閉區間內。

同時用 gridColor 作為網格的顏色,bgColor 作為背景色,也是預設的顏色,pixelColor 作為最後輸出的顏色。

那麼,程式碼的重點就在於 for 迴圈裡面了。

由於 fragcoord 歸一化有了確定的值域範圍,所以可以在 for 迴圈中將它十等分。

另外,因為片段著色器每個畫素都會執行一遍,每次 fragcoord 值都是變化的,但不管怎麼變化,它的範圍都會落在 for 迴圈的十等份裡面。

比如其中某一份的範圍是 [0.2,0.3) 的左閉右開區間,當前畫素就落在這個範圍內。

那麼 mod 取模函式就會判斷當前值距離左區間閾值是否在 minWidth 範圍內,其中 minWidth 相當於是指定網格線的寬度。

如果在範圍內,那麼顯示的顏色就是網格色,否則就是預設的背景色。

以上的講解對於座標的 xy 值是一樣的道理。原理通過判斷該畫素點的座標是否位於臨界範圍內來選擇性著色。

顯示這種繪製方式是有它的弊端,因為每一個畫素執行片段著色器的時候,都要進行一次 for 迴圈判斷它處於哪個區域內。

這樣就有了太多不必要的計算流程,尤其是 for 迴圈的每次遍歷。


接下來就是微信群中大佬給出的 Shader 程式碼:

vec2 st = vec2(gl_FragCoord.xy / u_resolution);
st.x *= u_resolution.x / u_resolution.y;

vec3 color = vec3(.0);

st *= 10.;

vec2 i_st = floor(st);
vec2 f_st = fract(st);

color += step(.98,f_st.x) + step(.98,f_st.y);

gl_FragColor = vec4(color,1.0);
複製程式碼

可以一眼看出這裡面沒有 for 迴圈的操作了。

還是先講解幾個級別操作:

floor 函式就是向下取整的操作

fract 函式是 x - floor(x) 的操作,也就是取小數部分的意思。

通過對 st 進行 floorfract 操作可以分出它的整數和小數部分。

step 函式類似於 if 判斷,當第二個引數大於等於第一個引數,則返回 1 ,否則返回 0 。

整個 Shader 程式碼第一行還是相同的,都是歸一化操作。

然後在第二行

st.x *= u_resolution.x / u_resolution.y
複製程式碼

實際上是做了一個比例切換的操作。將 stxy 值按照影象解析度的寬高比做了調整,其中以 y 為基準 1 。

這樣一來,sty 值還是在 [0,1] 範圍內,而 x 值可能大於也可能小於這個範圍了,這都取決於影象解析度了。

接下來將 st 乘了 10 ,這下 st 的值域範圍就在 [0,10] 了 ,這樣的操作是為了接下來的 floor 函式,因為它是取整,如果都在 [0,1] 範圍內,取的整數永遠都是 0 了。

前面轉換操作是為了接下來的重點函式 step

color += step(.98,f_st.x) + step(.98,f_st.y);
複製程式碼

前面的 floor 其實也是將 xy 軸做了等分,比如 y 的值域是 [0,1] ,乘以 10 之後,就是十等分,x 的值域如果是 [0,1.7] ,乘以 10 之後,就是十七等分。

fract 操作的結果範圍必然是 [0,1) 的左閉右開區間。

step 函式的意圖就是如果該畫素點的座標接近於等分線,那麼 color 的顏色值返回的就是 1 ,顯示白色,否則返回 0 ,顯示黑色。

比如,st 的 x 值是 7.99 了,接近於 8 ,那麼就要顯示白色網格線了,對於 y 值同理。

這樣一來就可以對每個畫素點進行判斷,根據它的座標決定要顯示什麼顏色。

總結對比

在第二種繪製中,由於做了比例轉換操作,所以繪製出來的網格大小都是一致的,且都是正方形。

而第一種沒有比例切換操作,當寬高不同的情況下,同樣進行十等分的話,畫出來的網格是個長方形了。

但是,兩種繪製的思路都是相同的,姑且稱它為 接近法 吧,當繪製的畫素接近等分線時,就顯示不一樣的顏色。

於是,等分線的操作思路就各有不同了。前者是利用 for 迴圈來製造劃分,後者則是利用當前畫素的 xy 值的特點來繪製的。

當然更推崇後者的繪製方式了,也是學到了新技巧~~~

能力不足,經驗有限,文中有何不對的地方歡迎批評指正,也可以加我微信 ezglumes 交流~~