webgl实现径向模糊

简介: webgl实现径向模糊

径向模糊简介


径向模糊,是一种从中心向外呈幅射状,逐渐模糊的效果。因此径向模糊经常会产生一些中心的发散效果,在PS中同样也有径向模糊的滤镜效果。径向模糊通常也称为变焦模糊。径向模糊(Radial Blur)可以给画面带来很好的速度感,是各类游戏中后处理的常客,也常用于Sun Shaft等后处理特效中作为光线投射(体积光)的模拟。


在游戏中,常常使用径向模糊来模拟一些运动的动感效果。如鬼泣4中的场景切换特效,和一些技能打击特效;赛车游戏也尝用来模拟动感模糊,如狂野飙车,极品飞车等。径向模糊还是实现体积光照的一种技术手段之一,如下图:


微信图片_20220425121849.png

《巫师2》 中基于径向模糊(Radial Blur)的Sun Shaft


径向模糊的原理


图形学中模糊的大致原理都是一样的:就是从原像素周围去寻找附近像素的颜色,来共同影响当前的像素颜色。如高斯模糊就是将原像素四周像素的颜色加权求和作为原像素的颜色以达到模糊的目的。


不同的模糊就是取周边像素和加权求和的算法不太一样。径向模糊的特点是从某个中心点向外散射扩散,因此其需要采样的像素来自于当前的像素点和中心点的连线上,通过参数可以控制采样的数量和采样步进的距离。像素的颜色是由该像素的点与中心点之间连线上进行采样,然后求将这些采样点颜色的加权平均。根据径向模糊的特性,离目标点越近采样点越密集,反之亦然。


因此,实现径向模糊的大致流程如下:

  • 确定径向模糊的中心点,一般为画布的中心点,或这个某个对象的中心点在屏幕投影所在的位置。(注意中心点是2维坐标)
  • 计算当前像素和中心点的距离和向量线段。
  • 在线段上面进行采样,加权。
  • 将模糊的结果和原图进行一个叠加合成(可能需要)


webgl实现径向模糊


径向模糊是一个后处理过程,径向模糊可以对静态的图片施加效果,也可以对动态渲染的图像施加效果。本示例中将对动态的图像施加效果。先上一张图看看效果:


微信图片_20220425121907.png

webgl径向模糊


首先绘制的几个圆环对象,然后对绘制的图像施加径向模糊。


渲染到纹理


要施加径向模糊,首先要把圆环绘制到texture对象上面,我们知道,通过framebuffer的技术,可以实现把绘制效果输出到贴图对象上。

有关framebuffer的技术,不属于本文重点介绍的内容,如果读者不熟悉,可以自行查找相关资料。也可以参考:渲染到纹理。


输入贴图对象


要把贴图对象输出到屏幕上面,我们需要构造一个矩形对象,该对象正好是webgl坐标系中的四个顶点,代码如下:


function quad() {
  var pos = [-1,1,0, -1,-1,0, 1,-1,0, 1,1,0],
    st = [0,1,0,0,1,0, 1,1],
    idx = [0,1,2,0,2,3];
  return {
    p:pos,
    t:st,
    i:idx,
  }
}


上述对象可以正好把一个贴图对象完整的输出到屏幕上(webgl坐标系)


实现径向模糊


径向模糊的主要在着色器语言中进行实现,而且主要是在片元着色器中,下面是片元着色器的代码:


var ofs = `precision mediump float;
uniform sampler2D texture;
uniform float strength;
varying vec2 vTexCoord;
const float tFrag = 1.0 / 512.0;
const float nFrag = 1.0 / 30.0;
const vec2 centerOffset = vec2(256.0, 256.0);
float rnd(vec3 scale, float seed) {
  return fract(sin(dot(gl_FragCoord.stp + seed, scale)) * 43758.5453 + seed);
}
vec4 fragRadialBlur(){
  vec2 fc = vec2(gl_FragCoord.s, 512.0 - gl_FragCoord.t);
  vec2 fcc = fc - centerOffset;
  vec3 destColor = vec3(0.0);
  float random = rnd(vec3(12.9898, 78.233, 151.7182), 0.0);
  float totalWeight = 0.0;
  for (float i = 0.0; i <= 30.0; i++) {
    float percent = (i + random) * nFrag;
    float weight = percent - percent * percent;
    vec2 t = fc - fcc * percent * strength * nFrag;
    destColor += texture2D(texture, t * tFrag).rgb * weight;
    totalWeight += weight;
  }
  return vec4(destColor / totalWeight, 1.0);
}
void main(void) {
  gl_FragColor =  fragRadialBlur();
}`;


随机函数


首先声明了一个rnd函数,此函数是一个简单的随机数生成器,当给定向量和种子值时,将返回0到1范围内的随机数。

使用随机数,是为了让模糊的效果呈现一定的随机状态,而不是按照某种一致性的原则进行模糊。

rnd 在每次片元着色器中都会调用,因此要尽量使用轻量化的实现,不然可能会造成性能负载。


定义相关变量


const float tFrag = 1.0 / 512.0;
const float nFrag = 1.0 / 30.0;
const vec2  centerOffset = vec2(256.0, 256.0);


然后定义相关变量。在此示例中,缩放的中心点设置为画布的中心。

画布的大小为512像素,因此上面的代码相应地声明了一些常量。

vec2变量centerOffset用于定义中心位置。

floag变量tFrag用于规范化,把二维顶点坐标转换成归一化为uv坐标,以正确引用着色器中的纹理像素。

另一个float类型常量nFrag用于着色器中for的语句进行迭代处理进行归一化。


径向模糊逻辑


vec4 fragRadialBlur(){
  vec2 fc = vec2(gl_FragCoord.s, 512.0 - gl_FragCoord.t);
  vec2 fcc = fc - centerOffset;
  vec3 destColor = vec3(0.0);
  float random = rnd(vec3(12.9898, 78.233, 151.7182), 0.0);
  float totalWeight = 0.0;
  for (float i = 0.0; i <= 30.0; i++) {
    float percent = (i + random) * nFrag;
    float weight = percent - percent * percent;
    vec2 t = fc - fcc * percent * strength * nFrag;
    destColor += texture2D(texture, t * tFrag).rgb * weight;
    totalWeight += weight;
  }
  return vec4(destColor / totalWeight, 1.0);
}


首先,通过gl_FragCoord计算出变量fc,表示当前像素在canvas画布的坐标。注意gl_FragCoord坐标的原点是在左下角,而canvas画布的坐标原点在左上角,应此做了一个翻转计算:


512.0 - gl_FragCoord.t


计算变量fcc,表示当前坐标到中心点的向量。

定义变量destColor用于保存最终要输出的像素颜色。然后计算一个随机值random。totalWeight表示每次迭代时候采样像素所占的比重之和。

然后开始迭代处理。

在片段着色器中通过for语句进行迭代处理,使用i加上随机数之和来计算目标像素的percent(比重),然后通过percent - percent * percent 是为了把线性的比重数据变成非线性的比重weight。

然后通过percent和strength计算出一采样的坐标点t,其中strength表示径向模糊的强度,此值越大,偏离越大,模糊效果越强。并通过texture2D函数获取对应坐标点的颜色值,然后乘以weight并加入到destColor,并最终计算totalWeight。

最后,完成迭代过程后进行归一化destColor:destColor / totalWeight。


说明


此处我们使用了30次迭代,看起来性能并没有太大影响。实际过程中,可以选择不同的迭代次数,来达到效果和性能的平衡。

最终效果如下,


微信图片_20220425121915.png

webgl径向模糊


本文也发表在我的webgl专栏,完整代码可以在专栏中获取:

https://xiaozhuanlan.com/topic/6480975213

下一篇文章讲述利用径向模糊实现体积光的效果,先上一张图看看效果:


微信图片_20220425121918.png

体积光




相关实践学习
函数计算部署PuLID for FLUX人像写真实现智能换颜效果
只需一张图片,生成程序员专属写真!本次实验在函数计算中内置PuLID for FLUX,您可以通过函数计算+Serverless应用中心一键部署Flux模型,快速体验超写实图像生成的魅力。
从 0 入门函数计算
在函数计算的架构中,开发者只需要编写业务代码,并监控业务运行情况就可以了。这将开发者从繁重的运维工作中解放出来,将精力投入到更有意义的开发任务上。
相关文章
|
存储 编解码 前端开发
React 视频上传组件 Video Upload
随着互联网的发展,视频内容在网站和应用中愈发重要。本文探讨如何使用React构建高效、可靠的视频上传组件,涵盖基础概念、常见问题及解决方案。通过React的虚拟DOM和组件化开发模式,实现文件选择、进度显示、格式验证等功能,并解决跨域请求、并发上传等易错点。提供完整代码案例,确保用户能顺畅上传视频。
616 92
|
存储 算法 搜索推荐
探索常见数据结构:数组、链表、栈、队列、树和图
探索常见数据结构:数组、链表、栈、队列、树和图
685 64
|
算法 计算机视觉
图像处理之水波纹扩散效果(water ripple effect)
图像处理之水波纹扩散效果(water ripple effect)
549 5
|
机器学习/深度学习 缓存 自然语言处理
一文揭秘|预训练一个72b模型需要多久?
本文讲述评估和量化训练大规模语言模型,尤其是Qwen2-72B模型,所需的时间、资源和计算能力。
1713 12
|
定位技术 图形学
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
1416 0
|
网络协议 测试技术 网络性能优化
Android系统 以太网吞吐量和丢包测试
Android系统 以太网吞吐量和丢包测试
1287 1
|
缓存 图形学 异构计算
【#Unity Shader#Amplify Shader Editor(ASE)_第二篇】
【#Unity Shader#Amplify Shader Editor(ASE)_第二篇】
|
Java
【Java】已解决java.nio.channels.ClosedChannelException异常
【Java】已解决java.nio.channels.ClosedChannelException异常
1715 1
|
计算机视觉
图像处理之常见二值化方法汇总
图像处理之常见二值化方法汇总
795 0
|
测试技术 API Android开发
autox.js如何监听异常情况,比如网络中断、内存慢、应用死机或者页面无响应
autox.js如何监听异常情况,比如网络中断、内存慢、应用死机或者页面无响应