在图像渲染完成之后,进行进一步的处理的过程,就叫做图像处理(postprocessing)。

其实就是滤镜

学过CSS的人都知道属性filter,这里面就包含了各式各样的滤镜:模糊、亮度、对比度、灰度、反转之类的。

示例图片

这些滤镜是是直接就能用的,一行简单的:

filter: xxx();

就搞定了。但是,在Shader中,并没有任何内置的滤镜函数可以给我们使用,想要实现上面的滤镜效果,我们只能亲自动手去写,不过,这样做会给我们极大的灵活性,我们可以不需要按照公式化的API去做效果。

染色

第一个滤镜,就是染色滤镜
实现非常简单,把输出结果乘上染色的颜色值就可以了

#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif"

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    vec3 tex = texture(iChannel0, uv).xyz;
    vec3 col = tex;
    vec3 tintColor=vec3(.220,.380,.651);
    col *= tintColor;
    fragColor = vec4(col, 1.);
}

这样就给整体图片笼罩了一层蓝色。

RGB 位移

看到这个标题你想到了什么?是的,就是抖音的 logo:

抖音的 logo 就是中间一个白色的音符,两侧有青和红的投影,这种其实就是色调偏移的效果。

RGB位移滤镜和这种就很像,他可以将 R、G、B 三个颜色分量进行偏移,产生一种色差的效果。

我们来尝试实现一下色差的效果:

// 复制三份uv
    vec2 rUv = uv;
    vec2 gUv = uv;
    vec2 bUv = uv;
    // 设置一个较大的偏移量数值,方便观察
    float offset = .1;
    // 给R和B通道进行一正一负的偏移
    rUv += offset;
    bUv -= offset;
    // 采样三次纹理
    vec4 rTex = texture(iChannel0, rUv);
    vec4 gTex = texture(iChannel0, gUv);
    vec4 bTex = texture(iChannel0, bUv);
    vec4 col = vec4(rTex.r, gTex.g, bTex.b, gTex.a);
    fragColor = vec4(col);

效果出现了,偏移量设置的 0.1,主要是看效果。

我们把偏移量变更为 0.009,就能看到可以看出来的色差,但是不会突兀的感觉。

除了一般的固定便宜之外,我们还可以把上一篇的“随机”拿出来用,给偏移整点噪声。

完整的代码:

#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif"

// 随机函数
highp float random(vec2 co)
{
    highp float a=12.9898;
    highp float b=78.233;
    highp float c=43758.5453;
    highp float dt=dot(co.xy,vec2(a,b));
    highp float sn=mod(dt,3.14);
    return fract(sin(sn)*c);
}


void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    vec3 tex = texture(iChannel0, uv).xyz;
    // 为了将噪声值控制在0到1
    float noise=random(uv)*.5+.5;
    // 使用sin和cos调整一下噪点的偏移量位置
    vec2 offset=.0075*vec2(cos(noise),sin(noise));

    // 复制三份uv
    vec2 rUv = uv;
    vec2 gUv = uv;
    vec2 bUv = uv;
    // 给R和B通道进行一正一负的偏移
    rUv += offset;
    bUv -= offset;
    // 采样三次纹理
    vec4 rTex = texture(iChannel0, rUv);
    vec4 gTex = texture(iChannel0, gUv);
    vec4 bTex = texture(iChannel0, bUv);
    vec4 col = vec4(rTex.r, gTex.g, bTex.b, gTex.a);
    fragColor = vec4(col);
}

把噪点的值设置为 0.075 是为了查看噪点的具体分布,可以将值自行调整观察。

在这里贴一张噪点的图:

挤压

在 PS 里面,有一个叫做挤压滤镜的东西,大概是这样的:

在 Shader 里面,我们一样可以通过代码来做出一样的效果。

首先,我们创建一下负责膨胀或者挤压的函数bulge

vec2 bulge(vec2 p) {
    return p;
}

咱们在主函数mainImage中应用膨胀函数bulge

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = bulge(uv);
    vec3 tex = texture(iChannel0, uv).xyz;

    fragColor = vec4(tex, 1.);
}

接下来,我们来编写膨胀函数吧: 1.用length计算uv上每个点到原点的距离,然后跟uv直接相乘。

float d = length(p);
p *= d;

长这样了:

这个效果是不太对的,因为UV的原点不在画布中心,在画布左下角,我们手动选择一个画布正中心的值吧:

vec2 center = vec2(.5);

在调用length函数之前,我们先把UV减去这个中点,然后调用之后加回去。

p -= center;

float d = length(p);
p *= d;

p += center;

完美复刻了之前 PS 的效果:

当然,我们还可以对效果做一下加强,我们把d的距离应用一下pow函数做一个指数增加,效果会更加明显。

float dPow = pow(d, 2.);
// p *= d;
p *= dPow;

效果有点鬼畜了,就不贴图了。

截止到现在,我们做的挤压实际上都是外凸的,有什么办法能做到内凹吗?

其实很简单,我们只需要给uv乘上距离的倒数就行了。

float dRev = 1./dPow;
p *= dRev;

让我们来试一下:

呃啊,辣眼睛的万花筒出现了,为什么会这样?

因为dPow很可能是一个非常小的值,比如说(0.00001)乃至无穷小,那么它的倒数就会变成非常大,乃至无穷大。就会出现上述的万花筒。

我们要解决它,其实也非常的简单,我们给底数加一个 1,让这个整体不至于非常的小就行:

float dRev = 1. / (dPow + 1.);

最后,我们来微调一下这个内凹效果,并且加上与用户鼠标进行互动的逻辑,要怎么做?增加两个额外的参数,扭曲的半径radius以及扭曲的强度strength

#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif"

// 挤压函数
vec2 bulge(vec2 p) {
    vec2 center = iMouse.xy / iResolution.xy;
    float radius = .9;
    float strength = 1.1;
    p -= center;
    float d = length(p);
    d /= radius;
    float dPow = pow(d, 2.);
    float dRev = strength / (dPow + 1.);
    p *= dRev;
    p += center;
    return p;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    uv = bulge(uv);
    vec3 tex = texture(iChannel0, uv).xyz;

    fragColor = vec4(tex, 1.);
}

来讲解一下代码,其中:

  • radius表示扭曲效果的影响半径,radius的值越大,更多的区域会受到扭曲效果的影响。
  • strength表示扭曲效果的强度,strength的值越大,扭曲效果越强,纹理坐标的变化越大。
  • d /= radius,将距离d归一化,使其相对于 radius。如果d小于radius,则d 的值在[0, 1]范围内;如果d大于radius,则d的值大于 1。

最后,来看看效果吧:

还挺有意思的!

像素化

也就是我们经常看到的,马赛克。将特定区域变成方块,遮挡敏感信息。

在 Shader 里,马赛克就是像素,就等于是把画面像素化。

我们现在就会接触一个新的函数floor,函数的作用就是向下取整,比如说floor(114.514)就是114.

我们来举一个例子,我们对uvx轴进行输出,将x的坐标乘上像素化大小的值,并且使用 floor 函数来向下取整,然后再除以像素化大小的值。

void mainImage(out vec4 fragColor,in vec2 fragCoord) {
    vec2 uv = fragCoord/iResolution.xy;
    float c = uv.x;
    c = floor(c * 10.) / 10.;
    fragColor = vec4(vec3(c), 1.0);
}

好,他现在长这样的:

为什么?

来我们去 Graphtoy 看看这个函数的效果:

简单来说,举一个值的例子吧:
比如我的x坐标位于(0, 0.09)的时候,x*10的值域是(0, 0.9),但是floor函数是向下取整的,所以floor(x*10)的值是0,除以10还是0;但当x坐标位于0.1的时候,x*10就变为了1,这个时候的向下取整就成了1,除以10就变为了0.1;之后的范围值就会以0.1的步长递增,公式总体是阶梯的形状了。

那我们回到一开始的采样纹理吧:

#iChannel0 "https://s2.loli.net/2024/07/10/oshNYlIBJ5eAu7k.gif"

void mainImage(out vec4 fragColor,in vec2 fragCoord) {
    vec2 uv = fragCoord/iResolution.xy;
    vec2 size = vec2(50., 50.);
    uv=floor(uv*size)/size;
    vec3 tex=texture(iChannel0,uv).xyz;
    fragColor=vec4(tex,1.);
}

心中无码,自然高清。马赛克,打好了!

晕影

在去澳门之前拿到了一副佳能相机,随之研究了一下摄影手法,其中,接触到一种特殊的手法叫做晕影

在摄影和光学领域内,晕影或暗角是指图像的外围部分的亮度或饱和度比中心区域低。

有时却因为需要创意风格而刻意加入,例如引起对图像中心区的注意。摄影师也会故意选择能产生晕影的镜头来制造特效,也可以使用特别的滤镜或以后期处理来制造晕影。

比如:

我们来用Shader来尝试实现一下吧。
为了观察这个效果的实现,我们先把画布设置为白色。

void mainImage (out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    vec3 col = vec3(1.);
    fragColor = vec4(col, 1.);
}

紧接着,我们在vec3 col = vec3(1.)下一行进行编码,我们来创建一个圆形的径向转变,这一步骤就是画个圆的步骤,详情可以参考本教程的 uv 相关的章节

因为我们要把效果拉伸到整个画布,所以不需要对比例进行修改

    vec2 p = uv;
    p -= .5;
    float d = length(p);
    col = vec3(d);

这是一个简单的径向渐变,接下来,注释掉
col = vec3(d);
我们来使用smoothstep来把这个渐变效果拉伸一下,并且把边界值设置的稍微差距大一点,可以塑造一种模糊的感觉:

    col = vec3(d);
    float c = smoothstep(.4, .8, d);
    col = vec3(c);

接下来是关键的一步,把这个渐变效果翻转过来,我们就可以得到晕影的效果了,要怎么做?

有两种做法,效果是一样的,根据个人喜好即可 1.使用 1 减去c的值 2.调换smoothstep的边界值

出于个人喜好,我选择了第一种,第二种也是完全没有问题的,原理也是非常的简单,前面的文章也充分介绍过了。

之前演示的 GBC 的人物图背景黑色有点不是很明显,我们换回上一章节使用的那张图,并且加上晕影滤镜试一下:

全部代码:

#iChannel0 "https://s2.loli.net/2023/09/10/QozT59R6KsYmb3q.jpg"

void mainImage (out vec4 fragColor, in vec2 fragCoord) {
    vec2 uv = fragCoord / iResolution.xy;
    vec3 tex = texture(iChannel0, uv).xyz;
    vec3 col = tex;
    vec2 p = uv;
    p -= .5;
    float d = length(p);
    col = vec3(d);
    float c = smoothstep(.4, .8, d);
    col = vec3(1. - c) * tex;
    fragColor = vec4(col, 1.);
}

嗯,效果很好,这就完成了。

这里介绍的滤镜知识很简单,都是一些基础的滤镜,各位可以根据诸如 PS 的工具上的滤镜思考一下 Shader 如何去实现一样的效果,其实都是操作像素和画布,本质是一样的东西!

最后修改:2024 年 08 月 14 日
收款不要了,给孩子补充点点赞数吧