作者:星隕
來源:
音視訊開發進階 前幾天釋出了這樣一篇文章: 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
相當于是指定網格線的寬度。
如果在範圍内,那麼顯示的顔色就是網格色,否則就是預設的背景色。
以上的講解對于坐标的
x
和
y
值是一樣的道理。原理通過判斷該像素點的坐标是否位于臨界範圍内來選擇性着色。
顯示這種繪制方式是有它的弊端,因為每一個像素執行片段着色器的時候,都要進行一次
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
進行
floor
fract
操作可以分出它的整數和小數部分。
step
函數類似于
if
判斷,當第二個參數大于等于第一個參數,則傳回 1 ,否則傳回 0 。
整個 Shader 代碼第一行還是相同的,都是歸一化操作。
然後在第二行
st.x *= u_resolution.x / u_resolution.y
實際上是做了一個比例切換的操作。将
st
的
x
,
y
值按照圖像分辨率的寬高比做了調整,其中以 y 為基準 1 。
這樣一來,
st
y
值還是在
[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
其實也是将
x
y
軸做了等分,比如
y
的值域是
[0,1]
,乘以 10 之後,就是十等分,
x
的值域如果是
[0,1.7]
,乘以 10 之後,就是十七等分。
而
fract
操作的結果範圍必然是
[0,1)
的左閉右開區間。
step 函數的意圖就是如果該像素點的坐标接近于等分線,那麼 color 的顔色值傳回的就是 1 ,顯示白色,否則傳回 0 ,顯示黑色。
比如,st 的
x
值是 7.99 了,接近于 8 ,那麼就要顯示白色網格線了,對于 y 值同理。
這樣一來就可以對每個像素點進行判斷,根據它的坐标決定要顯示什麼顔色。
總結對比
在第二種繪制中,由于做了比例轉換操作,是以繪制出來的網格大小都是一緻的,且都是正方形。
而第一種沒有比例切換操作,當寬高不同的情況下,同樣進行十等分的話,畫出來的網格是個長方形了。
但是,兩種繪制的思路都是相同的,姑且稱它為
接近法
吧,當繪制的像素接近等分線時,就顯示不一樣的顔色。
于是,等分線的操作思路就各有不同了。前者是利用
for
循環來制造劃分,後者則是利用目前像素的
x
、
y
值的特點來繪制的。
當然更推崇後者的繪制方式了,也是學到了新技巧~~~
OpenGL 系列文章「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。