辉光效果小百科爱来自glsl( ̄︶ ̄)↗ 

469 阅读7分钟

写在开头

之前写的两篇glsl其实都用到了辉光效果,但是当我自己准备自定义一个镂空的辉光效果时还是会有一些疑惑,为什么如此代码可以有一个镂空的效果,自己重新归纳总结后有了这篇博客内容~

学习一个马斯克转发的Grok效果(glsl)

什么是色调映射/以及tanh的使用(glsl)

cover.png

如何理解glsl中的辉光

在制作辉光效果之前,我们需要思考什么是光,在现实生活中太阳月亮都是会产生光亮,但是光亮的强度截然不同,这种光可能投射的范围可能非常小,也可能像太阳一样可以照亮整片大地

所以,让物体看起来像发光有两个重要因素

  • 物体颜色与背景形成对比
  • 随着与物体距离的增加而产生亮度衰变

只要实现了这两个条件,我们就算是完成了一个辉光效果!

圆形发光体

圆形是我们在glsl中接触到最简单的2d形状了,圆形SDF如下

float sdCircle( vec2 p, float r )
{
    return length(p) - r;
}

01.png

本篇内容不涉及SDF的解析

此时我们圆形内部点 color是 < 0,在圆周上的点 color = 0,在圆周外的点color > 0,知道了这一点之后我们处理一下color让其成为一个实心圆

    float dist = sdCircle(p, .2);
    vec3 color = vec3(step(0., -dist));
    gl_FragColor = vec4(color, 1.0);
  1. 因为在圆心内的点<0,所以我们使用-dist反转数值
  2. step方法根据一个阈值,对输入值执行“是否大于等于”的判断,并返回 0 或 1,即大于0.的我们返回1.小于0.的我们返回0.

02.png

这个时候其实我们就完成了辉光效果的第一点🎉

物体颜色与背景形成对比

但是很明显此时肉眼看起来完全就没有辉光的那种感觉,此时我们就应该开始考虑辉光要素的第二点

随着与物体距离的增加而产生亮度衰变

geogebra-export.png

如果所示,随着我们距离圆心越远,我们输出的亮度应该越小!

    float dist = sdCircle(p, .2);

    vec3 color = vec3(step(0., -dist));

    float glow = 0.01/dist;
    color += glow;

    gl_FragColor = vec4(color, 1.0);

这个技巧在学习一个马斯克转发的Grok效果(glsl)中有提到,当分母越小时产生的亮度越大!还记得我们圆形SDF的结果么圆形内部点 color是 < 0,在圆周上的点 color = 0,在圆周外的点color > 0,此时我们运行起来的效果是有有着奇怪阴影的圆形!!

03.png

这个现象其实是一个非常经典的glsl问题,因为当 dist → 0(即像素非常接近圆的边缘)时,1/dist → ∞,会导致 发光值爆炸,进而让颜色强度极高,形成视觉上的“阴影边缘撕裂”或“发亮断层”。所以我们这时候需要钳制一个grow!让其不能产生 ∞ 大的值。

    float glow = clamp(0.01/dist,0.0,1.0);

这样做会让glow始终限制在 [0, 1],即便 dist 很小,也不会产生一个无限大的值。

    float dist = sdCircle(p, .2);
    vec3 color = vec3(step(0., -dist));
    float glow = clamp(0.01/dist,0.0,1.0);
    color += glow;

04.png

此时我们可以直观肉眼的看到产生了一个辉光效果!

不同的衰减方法产生不同的效果

倒数衰减

float glow = clamp(0.01/dist,0.0,1.0);

上述我们的衰减方法属于一种倒数衰减 glow(d)=kdglow(d)= \frac{k}{d}

geogebra-export02.png

这种衰减的特点就是中心亮度极强,衰减很快,假设我们想让这个辉光效果不要那么快就衰减到0其实也可以调整一下倒数衰减的分母!只要我们能理解这个方法的核心就是分母越小亮度越爆炸,分母越大衰减的越快!

float glow = clamp(0.01/(dist * .2), 0.0, 1.0);

05.png

指数衰减

 float glow = exp(-10.0 * abs(dist));

指数衰减(Exponential Decay) 是一种非常常见的数学衰减模型,广泛应用于物理、信号处理、着色器、动画等场景,用来描述值如何随输入(如距离或时间)以指数方式快速减小。 指数衰减的基本公式 f(x)=Aekxf(x) = A \cdot e^{-k x}

  • x:自变量,表示距离、时间等
  • A:初始值(最大值)
  • k:衰减速率(越大衰减越快)
  • e:自然常数 ≈ 2.71828

06.png

可以看到我们此时的光晕更为柔和!

线性衰减

  float glow = max(0.0, 1.0 - abs(dist));

线性衰减的基本公式 f(x)=max(0,1kx)f(x) = \max(0, 1 - kx)

  • x:自变量,通常表示距离
  • k:衰减系数,控制下降速度
  • 1:表示初始值(距离为 0 时为 1)

07.png

可以看到默认的线性衰减公式就是0~1的线性渐变,所以线性衰减是非常不自然的!!

简单的介绍了几种衰减方案让大家快速的了解不同的衰减的效果是什么,但是需要注意的是不同的衰减根据不同的参数产生的结果大不相同!大家要灵活掌握~

如何自定义辉光的颜色

和衰减方法一样,定义辉光颜色也有多种方法!这其实也是glsl自由度的体现~

乘法滤镜

    float glow = clamp(0.01/(dist * .2), 0.0, 1.0);

    vec3 glowColor = vec3(1, 1, 0);

    color += glow;

    color *= glowColor;

    gl_FragColor = vec4(color, 1.0);

08.png

这个方法比较简单只需要直接乘上我们想要的颜色即可!这背后的数学原理我们还要结合上文中的内容,此时圆心内部的数值是1.0此时×上我们的glowColor那自然也就是我们的glowColor没有变化!

物理衰减

乘法滤镜非常简单好用,但也是有一些问题

  • 当距离很小时,颜色会变得极其明亮
  • 容易产生过饱和的颜色
  • 颜色比例可能失真

比如和下图颜色结合

vec3 glowColor = vec3(.2, .4, .1)

11.png 可以发现结合出来的颜色整体非常暗淡!下面介绍另一种更自然的调整颜色方法

// color = lightColor / (lightColor + distance * attenuation);

float dist = sdCircle(p, .2);
vec3 glowColor = vec3(.2, .4, .1);
vec3 color = glowColor / (glowColor + clamp(dist, 0.0,1.0) * 8.0);
gl_FragColor = vec4(color, 1.0);

12.png

  1. lightColor - 光源的固有颜色和强度
  • 这是光源本身的颜色特性
  • 数值越大,该颜色通道越亮
  • 例如:(.2, .4, .1) 表示偏绿色的光源
  1. distance - 从光源到当前像素的距离
  • 当前渲染的像素点离光源有多远
  • 距离越远,光照效果越弱
  • 距离为0时,就在光源中心
  1. attenuation - 衰减系数(光照减弱的速度)
  • 控制光照衰减的快慢
  • 数值越大,光照衰减越快(光照范围越小)
  • 数值越小,光照衰减越慢(光照范围越大)

如何镂空物体只让边框产生辉光效果!

我们已经讲过了如何产生一个辉光,以及如何调整辉光颜色!接下来的部分和辉光本身关系不大,属于在glsl中的绘制技巧~

13.png

其实这个是非常简单的,我们只需要记住一个核心当分母越小时产生的亮度越大!那么我们如果要让一个圆中心不发亮,只需要把圆心的分母调大即可!

    float dist = abs(sdCircle(p, .2) - .1);
    vec3 glowColor = vec3(.2, .4, .1);
    vec3 color = glowColor / (glowColor + clamp(dist, 0.0, 1.0) * 8.0);
    gl_FragColor = vec4(color, 1.0);

我们利用abs反转圆心的分母,只保留在.1附近的状态!其余的辉光效果不用修改即可产生这种镂空的效果,这种方式适用于绝大部分SDF

  1. 矩形
    float dist = abs(sdBox(p, vec2(.2,.1)));
    vec3 glowColor = vec3(.2, .4, .1);
    vec3 color = glowColor / (glowColor + clamp(dist, 0.0, 1.0) * 10.0);

14.png 2. 三角形

        float dist = abs(sdEquilateralTriangle(p, .1));
        vec3 glowColor = vec3(.2, .4, .1);
        vec3 color = glowColor / (glowColor + clamp(dist, 0.0, 1.0) * 10.0);

15.png 3. 五角形

    float dist = abs(sdPentagram(p, .1));
    vec3 glowColor = vec3(.2, .4, .1);
    vec3 color = glowColor / (glowColor + clamp(dist, 0.0, 1.0) * 10.0);

16.png

结束语

glsl越学越上瘾这一块!希望大家共同进步共同学习~

参考资料

iquilezles

Glow Shader in Shadertoy