朋友們,你們好,今天我們學習如何在Shadertoy中爲物體制作光暈效果。
什麼是光暈
在製作光暈效果之前,先來考慮一下是什麼原因導致了物體發生光暈現象。生活中的很多物體都會發光:螢火蟲,燈管,水母甚至是天上的星星。這些物體能夠產生熒光,照亮黑暗的房間或者區域。有些光暈效果可能會比較弱,一段距離後就看不到了,有些則很強,可以將整個房間照亮,甚至點亮夜空。物體出現光暈效果的前提有兩個:
- 與物體顏反差加大的背景。
- 物體附近的顏色漸變。
有了這兩個條件,我們就能製作光暈效果,現在,我們就開始吧!
發光的圓
利用SDF,繪製出一個簡單的圓:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = length(uv) - 0.2; // signed distance value
vec3 col = vec3(step(0., -d)); // create white circle with black background
fragColor = vec4(col,1.0); // output color
}
圓形距離符號函數返回一個距離圓中心位置的長度值,着色器程序會同時在每一個像素中執行,每一塊像素和圓的中心都是有一段距離。
接下來,我們向圓行之外的一段距離添加光暈的效果。使用Desmos,在其中輸入y = 1/ x
,可以看見對應的圖形曲線,接下來就需要用到這個公式。假設x
表示某個點到圓形的距離。那麼隨着x
的增加,y
變得越來越小直至消失不見。
把這個函數應用到代碼中去。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = length(uv) - 0.2; // signed distance function
vec3 col = vec3(step(0., -d)); // create white circle with black background
float glow = 0.01/d; // create glow and diminish it with distance
col += glow; // add glow
fragColor = vec4(col,1.0); // output color
}
運行以上的代碼,會出現一些奇怪的效果。
y= 1/x
這個函數,當x小於或者等於0的時候,會產生一些意外的結果。這導致了編譯器的執行產生了一些意想不到的顏色。使用clamp
函數,確保glow的值,被限定在0到1之間。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = length(uv) - 0.2; // signed distance function
vec3 col = vec3(step(0., -d)); // create white circle with black background
float glow = 0.01/d; // create glow and diminish it with distance
glow = clamp(glow, 0., 1.); // remove artifacts
col += glow; // add glow
fragColor = vec4(col,1.0); // output color
}
運行以上的代碼,就能看到一個正在發光的圓了!
增加光暈的強度
給結果乘以一個值,讓圓看起來更亮一些,甚至讓它發散的光線更遠一些。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = length(uv) - 0.2; //符號距離場函數 signed distance function
vec3 col = vec3(step(0., -d)); // 在黑色背景上繪製一個白色的圓 create white circle with black background
float glow = 0.01/d; // 創建光暈效果,隨距離增加而減弱 create glow and diminish it with distance
glow = clamp(glow, 0., 1.); // remove artifacts
col += glow * 5.; // 添加光暈 add glow
fragColor = vec4(col,1.0); // 輸出顏色 output color
}
發光的星星
目前我們使用了圓形,你也可以使用其他的形狀。使用IQ大神網站上的2D距離函數sdStar5
繪製一個五角星。我們在第五篇教程中,提到了更多這方面的知識。
float sdStar5(vec2 p, float r, float rf)
{
const vec2 k1 = vec2(0.809016994375, -0.587785252292);
const vec2 k2 = vec2(-k1.x,k1.y);
p.x = abs(p.x);
p -= 2.0*max(dot(k1,p),0.0)*k1;
p -= 2.0*max(dot(k2,p),0.0)*k2;
p.x = abs(p.x);
p.y -= r;
vec2 ba = rf*vec2(-k1.y,k1.x) - vec2(0,1);
float h = clamp( dot(p,ba)/dot(ba,ba), 0.0, r );
return length(p-ba*h) * sign(p.y*ba.x-p.x*ba.y);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = sdStar5(uv, 0.12, 0.45); // signed distance function
vec3 col = vec3(step(0., -d));
col += clamp(vec3(0.001/d), 0., 1.) * 12.; // add glow
col *= vec3(1, 1, 0);
fragColor = vec4(col,1.0);
}
運行以上的代碼,就可以看到一顆閃閃發光的星星了!
利用第三篇教程中學到的知識,我們將星星進行旋轉,讓它轉起來。
vec2 rotate(vec2 uv, float th) {
return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv;
}
float sdStar5(vec2 p, float r, float rf)
{
const vec2 k1 = vec2(0.809016994375, -0.587785252292);
const vec2 k2 = vec2(-k1.x,k1.y);
p.x = abs(p.x);
p -= 2.0*max(dot(k1,p),0.0)*k1;
p -= 2.0*max(dot(k2,p),0.0)*k2;
p.x = abs(p.x);
p.y -= r;
vec2 ba = rf*vec2(-k1.y,k1.x) - vec2(0,1);
float h = clamp( dot(p,ba)/dot(ba,ba), 0.0, r );
return length(p-ba*h) * sign(p.y*ba.x-p.x*ba.y);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy; // x: <0, 1>, y: <0, 1>
uv -= 0.5; // x: <-0.5, 0.5>, y: <-0.5, 0.5>
uv.x *= iResolution.x/iResolution.y; // x: <-0.5, 0.5> * aspect ratio, y: <-0.5, 0.5>
float d = sdStar5(rotate(uv, iTime), 0.12, 0.45); // signed distance function
vec3 col = vec3(step(0., -d));
col += clamp(vec3(0.001/d), 0., 1.) * 12.; // add glow
col *= vec3(1, 1, 0);
fragColor = vec4(col,1.0);
}
總結
本篇教程中,我們學習瞭如何利用了符號距離場函數(SDF)讓2D模型發光。在物體顏色背面,我們設置了對比鮮明的背景,同時在物體的邊緣製作了平滑的漸變效果。這兩個條件讓我們能夠在着色器中模擬光暈的效果。如果要學習更多的Shadertoy,我建議你從第一篇教程開始吧!