编写光晕着色器

朋友们,你们好,今天我们学习如何在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

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