編寫光暈着色器

朋友們,你們好,今天我們學習如何在Shadertoy中爲物體制作光暈效果。

什麼是光暈

在製作光暈效果之前,先來考慮一下是什麼原因導致了物體發生光暈現象。生活中的很多物體都會發光:螢火蟲,燈管,水母甚至是天上的星星。這些物體能夠產生熒光,照亮黑暗的房間或者區域。有些光暈效果可能會比較弱,一段距離後就看不到了,有些則很強,可以將整個房間照亮,甚至點亮夜空。物體出現光暈效果的前提有兩個:

  1. 與物體顏反差加大的背景。
  2. 物體附近的顏色漸變。
    有了這兩個條件,我們就能製作光暈效果,現在,我們就開始吧!

發光的圓

利用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,我建議你從第一篇教程開始吧!

資源

*Glowing Star Shader

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章