朋友们,你们好,今天我们学习如何在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,我建议你从第一篇教程开始吧!