这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战
本文标题:WebGL第三十三课:2D特效合集
友情提示
这篇文章是WebGL课程专栏的第33篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。
本课代码与三十二课差别不大,请自行参考本文修改,直接跳转获取:三十二课代码
引子
上一篇文章讲了简单的一个光照的效果的实现。本文就基于上一篇的代码,继续搞几个常用的特效。
局部放大镜效果
先看效果:
点击到哪个地方,就将这个局部进行放大,并且高亮,其余的地方放暗。
局部高亮
第一步先实现高亮,不管放大镜效果。
这个跟上一篇文章搞的光照效果差不多,无非就是去掉模糊发散。
我们可以在 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);
}
效果,这里采用色图
来展示,因为猫的图片本身就很灰度:
马赛克效果
我们一般意义上的马赛克,就是一堆小格子的那种马赛克:
这种其实也很简单,也是通过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 是内置函数,可以用来取出某一个值的小数部分。
我们的思路是先让这个数变大十倍,然后减掉这个变大后的数的小数部分,然后再变小十倍。
这样的话,就自然保留了一位小数了。
模糊效果
先看效果:
那这个怎么搞呢,其实就是采样的时候,多采样几个点,然后算平均值而已:
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:
效果如下:
模糊的就越连续一点,没有那种离散感。
我搞的大一点:0.02:
看上去也还行,反正就是模糊了一下。
本次代码全部都在 fragment_shader
中实现,我们可以发现这玩意有多么强大和自由,想怎么写就怎么写,天马行空的效果全在你的脑子里,有意思!
正文结束,下面是答疑
小瓜瓜说:是不是还有其他的效果?
- 当然,你可以参考一下你手机里的拍照滤镜,可以尝试一下实现各种滤镜。