Shader 魔法的學習之路(2):畫一個笑臉


3樓貓 發佈時間:2022-05-13 15:17:45 作者:mnikn Language

這次我們來開始真正的繪圖,我們畫一個笑臉吧!

先畫個馬賽克圓

笑臉首先需要一張臉,我們先畫個圓來表示一張臉吧。
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { // 計算 uv vec2 uv = fragCoord / iResolution.xy; // 讓繪製區域位於正中心 uv -= 0.5; // 根據 uv 長度得出繪製顏色 float c = length(uv); // 畫! fragColor = vec4(vec3(c) ,1.0); }
當然這樣代碼畫出來看起來是一團像圓的馬賽克,我先解釋下代碼,說下為什麼然後再改好它。
vec2 uv = fragCoord / iResolution.xy 這一句其實在很多平臺都沒必要,大部分平臺 uv 都是內置變量,就 webgl 特殊 uv 都需要計算。uv 表示的是當前像素的位置,如果放到整體來看,uv 就表示整個畫布的範圍,範圍值是 [0,1],裡面的值都是基於左下角零點的比例座標,不是物理座標。這裡面計算 uv 的方式是拿 fragCoord 當前像素的物理座標 / iResolution 當前畫布的物理大小從而算出 uv。
uv -= 0.5
這個相當於把畫布往左下角偏移了,為什麼要這樣做,這其實要和接下來畫圓的操作配合說明,我們先跳過。
float c = length(uv) 這句是畫圓的關鍵,通過這句代碼我們得到了當前像素在圓中的顏色值。length 的函數作用是得出向量的長度,而我們傳的是 uv,就相當於得出了當前像素和零點的距離,範圍在 [0, 1]。從整體看來,離零點越遠的像素得到的值也越大,圖形化出來就是以零點發散的圓。
fragColor = vec4(vec3(c) ,1.0) 最後一句用這個算出來的值來決定像素顏色,值範圍處於 [0, 1],值為 0 是黑色 1 是白色。為什麼我們會看到的是一個馬賽克的圓?因為我們得到的值是一個範圍,有很多中間值,表現起來就是不同程度的灰色。
回到 uv -= 0.5 這句,先看下如果我們去掉這行代碼展示的是什麼:
我們看到的是一個只有右上部分的半圓,為什麼會這樣?因為我們是用 length(uv)來算出像素顏色值,而離左下角零點越近那值肯定越小,所以就會出現左下角像右邊擴散的情況。所以 uv -= 0.5 的作用是讓零點往左下角再移一些,這樣算出來的值就相當於整個圓。不理解的話可以自己在代碼上調一下,或者自己畫畫來模擬。

高清無碼的圓

問題來了,我們怎麼把這樣一個馬賽克畫質的圓轉成高清無碼呢?既然我們得到的是一個範圍值,那隻要讓它要麼是 0 要麼是 1 就好。
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy; uv -= 0.5; float c = length(uv); // 大於 0.3 就是 1,否則就是 0 c = step(c, 0.3); fragColor = vec4(vec3(c) ,1.0); }
經過處理我們得到了一個看起來不錯的圓,step 函數的作用是接受兩個參數,然後比較這兩個參數,第一個參數大於第二個參數就返回 1.0 否則返回 0.0。所以 step 傳過去的第二個參數相當於圓的半徑。

高清有碼的圓

細心的人會發現我們上面的圓邊緣看起來很銳利,因為我們的值不是 0 就是 1,沒有任何過渡。我們要做的是讓圓的邊角部位值處於 0 到1 之間,這樣就會圓滑很多。
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy; uv -= 0.5; float c = length(uv); float r = 0.3; c = smoothstep(r, r - 0.01, c); fragColor = vec4(vec3(c) ,1.0); }
我們用 smoothstep 代替了 step,區別在於 smoothstep 接受三個參數,第三個參數用來判斷,第一個和第二個參數表示當判斷值處於第一個參數和第二個參數之間,根據比例線性插值得到一個 0-1 的區間值,否則要麼是 0 要麼是 1。

帶點顏色

目前為止我們都是黑白,但是我們可以讓圓帶點顏色:
void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy; uv -= 0.5; float c = length(uv); // 定義圓的顏色為黃色 vec3 color = vec3(1.0, 1.0, 0.0); float r = 0.3; c = smoothstep(r, r - 0.01, c); // 混色 color *= c; fragColor = vec4(vec3(color) ,1.0); }
我們定義了個 color
來表示圓的顏色,然後用它來乘以我們之前算出來的值,再傳到 fragColor 上。因為我們用白色來表示圓,color * c 相當於在圓的區域上混上黃色,而白色混色等於對應的顏色。

用 shader 帶來笑容

經過一番波折我們畫了一個臉,接下來就是眼睛和嘴巴了,這些我們都用圓實現。因為要畫多次圓,我們先封裝下畫圓的函數。
float circle(vec2 uv, float r, vec2 p) { uv -= p; float c = length(uv); return smoothstep(r, r - 0.01, c); } ​ void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy; uv -= 0.5; vec3 color = vec3(1.0, 1.0, 0.0); float fare = circle(uv, 0.3, vec2(0.0, 0.0)); color *= face; fragColor = vec4(vec3(color) ,1.0); }
畫面效果沒什麼變化,不過我們封裝了個 circle 函數,接受 uv、圓的半徑和圓心座標,用 circle 函數就能很容易得到一個圓。
有了這樣一個函數後,我們接下來畫眼睛和嘴巴:
float circle(vec2 uv, float r, vec2 p) { uv -= p; float c = length(uv); return smoothstep(r, r - 0.01, c); } ​ void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 uv = fragCoord / iResolution.xy; uv -= 0.5; vec3 color = vec3(1.0, 1.0, 0.0); float face = circle(uv, 0.3, vec2(0.0, 0.0)); float eye1 = circle(uv, 0.05, vec2(-0.1, 0.10)); float eye2 = circle(uv, 0.05, vec2(0.1, 0.10)); float mouth_1 = circle(uv, 0.15, vec2(0.0, -0.1)); float mouth_2 = circle(uv, 0.15, vec2(0.0, 0.0)); float mouth = max(mouth_1 - mouth_2, 0.0); color *= face; color -= eye1; color -= eye2; color -= mouth; fragColor = vec4(vec3(color) ,1.0); }
眼睛和嘴巴的畫法都是先得出區域,然後減去這部分區域的顏色,不同的是嘴巴是用兩個圓相減得出一個半圓。
好了本文就到這裡,這次的作業是畫一個哭臉。

© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com