WebGL第三十三课:2D特效合集

1,576 阅读5分钟

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

本文标题:WebGL第三十三课:2D特效合集

友情提示

这篇文章是WebGL课程专栏的第33篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。

本课代码与三十二课差别不大,请自行参考本文修改,直接跳转获取:三十二课代码

引子

上一篇文章讲了简单的一个光照的效果的实现。本文就基于上一篇的代码,继续搞几个常用的特效。

局部放大镜效果

先看效果:

image.png

点击到哪个地方,就将这个局部进行放大,并且高亮,其余的地方放暗。

局部高亮

第一步先实现高亮,不管放大镜效果。

这个跟上一篇文章搞的光照效果差不多,无非就是去掉模糊发散。

image.png

我们可以在 fragment_shader 中,计算 光源到当前位置的距离,如果小于某一个距离,我们就将 light 设置成1,如果大于这个距离,就将 light 设置成0.5。

先看代码:

    <script id="fragment_shader" type="myshader">
        // Fragment shader
        precision mediump int;
        precision mediump float;

        uniform sampler2D u_funny_cat; // 有趣的猫的图片
        uniform vec2 u_lightPos;         // 光源的位置

        varying vec2 uv;
        varying vec2 pos;

        void main() {
            float light = 0.5;
            float n = distance(pos, u_lightPos);
            vec2 m_uv = uv.xy;
            if (n < 0.3)
            {
                light = 1.0;
            }
            vec4 sample_color = texture2D(u_funny_cat, m_uv);
            gl_FragColor = vec4(sample_color.xyz * light, 1.0);
        }
    </script>

上面的代码写的很清楚,在main函数开始,就定义了一个 light 变量,并初始化成 0.5。
然后我们计算了一下 当前位置pos 和 光源u_lightPos的距离,这里使用了内置函数 distance
然后在图片上进行了一次采样,最后将采样的结果和light相乘,使light生效。

这个效果还是很容易实现的。

最重要的就是回顾一下, pos 是在 vertex_shader 中通过 varying的方式传递过来的,因为 pos 这个buffer中的数据,在fragment_shader中本身是没发获取的。

局部放大

这个稍微复杂一点,但也是几行代码搞定。最重要的就是原理。

我们知道,webgl 中渲染图片是通过 UV 的方式,来锚定原始图片的。

也就是说,在我们选中的这个小的局部范围内,我们尽量展示原始图片的更小的范围内的内容,那么效果上就是放大的。

原理清楚了之后,代码就很简单了。

        void main() {
            float light = 0.5;
            float n = distance(pos, u_lightPos);
            vec2 m_uv = uv.xy;
            if (n < 0.3)
            {
                light = 1.0;
                vec2 diff = pos - u_lightPos;
                m_uv.x -= diff.x * 0.25;
                m_uv.y += diff.y * 0.25;
            }
            vec4 sample_color = texture2D(u_funny_cat, m_uv);
            gl_FragColor = vec4(sample_color.xyz * light, 1.0);
        }

观察上面代码,还是在距离小于0.3的范围内,做一些逻辑。
我们首先计算了当前位置和光源的偏差向量diff = pos - u_lightPos
然后其实就是将这个范围内的UV数据往光源处收缩,至于为什么x维度是减去一个值,y维度是加上一个值,这个是由于,在 vertex_shader 中,我们将UV的y维度反了过来,所以这里 y维度自然就是加上一个值。

这里给一下vertex_shader代码:

        void main() {
          vec3 coord = u_all * vec3(a_PointVertex, 1.0);
          gl_Position = vec4(coord.x, coord.y, 0.0, 1.0);
          uv = a_PointUV;
          uv.y = 1.0 - uv.y; // 这里有翻转,是因为图片本身的坐标系是反的,可以通过另一个设置搞定,这里不提
          pos = coord.xy;
        }

灰度图效果

就是彩色的图像,变成灰度的,就是去掉彩色。

原理:最终渲染的RGB三个维度的值是一样的就行了。

一种实现:我们最终的颜色的RGB计算出一个平均值,然后将这个RGB = 平均值。

代码:

        void main() {
            float light = 0.5;
            float n = distance(pos, u_lightPos);
            vec2 m_uv = uv.xy;
            if (n < 0.3)
            {
                light = 1.0;
                vec2 diff = pos - u_lightPos;
                m_uv.x -= diff.x * 0.25;
                m_uv.y += diff.y * 0.25;
            }
            vec4 sample_color = texture2D(u_funny_cat, m_uv);
            if (n < 0.3)
            {
                float avg = (sample_color.x + sample_color.y + sample_color.z)*0.33;
                sample_color.xyz = vec3(avg, avg, avg);
            }
            gl_FragColor = vec4(sample_color.xyz * light, 1.0);
        }

效果,这里采用色图来展示,因为猫的图片本身就很灰度:

image.png

马赛克效果

我们一般意义上的马赛克,就是一堆小格子的那种马赛克:

image.png

这种其实也很简单,也是通过UV去搞,我们只要在这个局部范围内,将UV进行离散化,不让UV连续取值就行。

我们知道UV本身的值是[0, 1]范围内的连续值。小数点后面可以有两三位。

我们只保留两位小数点,那么UV的值就比较离散了。

具体代码如下:

            if (n < 0.3)
            {
                light = 1.0;
                m_uv.x *= 10.0;
                m_uv.x -= fract(m_uv.x);
                m_uv.x *= 0.1;

                m_uv.y *= 10.0;
                m_uv.y -= fract(m_uv.y);
                m_uv.y *= 0.1;
            }

其中 fract 是内置函数,可以用来取出某一个值的小数部分。
我们的思路是先让这个数变大十倍,然后减掉这个变大后的数的小数部分,然后再变小十倍。
这样的话,就自然保留了一位小数了。

模糊效果

先看效果:

image.png

那这个怎么搞呢,其实就是采样的时候,多采样几个点,然后算平均值而已:


        void main() {
            float light = 0.5;
            float n = distance(pos, u_lightPos);
            vec2 m_uv = uv.xy;
            vec4 sample_color = vec4(0.0, 0.0, 0.0, 0.0);
            if (n < 0.3)
            {
                light = 1.0;
                vec4 sample_color0 = texture2D(u_funny_cat, m_uv + vec2(-0.01, -0.01) );
                vec4 sample_color1 = texture2D(u_funny_cat, m_uv + vec2(0.0, -0.01));
                vec4 sample_color2 = texture2D(u_funny_cat, m_uv + vec2(0.01, -0.01));
                vec4 sample_color3 = texture2D(u_funny_cat, m_uv + vec2(-0.01, 0.0));
                vec4 sample_color4 = texture2D(u_funny_cat, m_uv + vec2(0.0, 0.0));
                vec4 sample_color5 = texture2D(u_funny_cat, m_uv + vec2(0.01, 0.0));
                vec4 sample_color6 = texture2D(u_funny_cat, m_uv + vec2(-0.01, 0.01));
                vec4 sample_color7 = texture2D(u_funny_cat, m_uv + vec2(0.0, 0.01));
                vec4 sample_color8 = texture2D(u_funny_cat, m_uv + vec2(0.01, 0.01));
                sample_color = (sample_color0 + sample_color1 + sample_color2 + sample_color3 + sample_color4+sample_color5+sample_color6+sample_color7+sample_color8) / 9.0;
            }else{
                sample_color = texture2D(u_funny_cat, m_uv);
            }
            gl_FragColor = vec4(sample_color.xyz * light, 1.0);
        }

上面的代码,很简单,我们采样了9次,然后算了一个平均值,这才是最后的颜色值。至于怎么选取9个不同的uv,那就是可以变花样了,我现在就是以当前uv为中心,然后以粒度 0.01 取的新的采样点。你可以搞的更小一点或者更大一点都行。 比如说,我搞更小一点,0.005:

效果如下:

image.png

模糊的就越连续一点,没有那种离散感。

我搞的大一点:0.02:

image.png

看上去也还行,反正就是模糊了一下。

本次代码全部都在 fragment_shader中实现,我们可以发现这玩意有多么强大和自由,想怎么写就怎么写,天马行空的效果全在你的脑子里,有意思!




  正文结束,下面是答疑

小瓜瓜说:是不是还有其他的效果?

  • 当然,你可以参考一下你手机里的拍照滤镜,可以尝试一下实现各种滤镜。