动机

在shadertoy看到这个动态模糊的代码Analytical Motionblur 2D。不只是他的动态模糊,他整体的颜色、背景、小球的运动等都感觉很棒。代码也不长,因此仔细研究了下。

具体效果如下:

出于好奇尝试了100个小球、1000个小球高速运动的情况:

下面一步步实现。

1. 坐标映射

首先对原来的屏幕坐标 fragCoord 重新做映射,映射后屏幕坐标如下:

通过除以屏幕高度,将纵坐标限制在[−1,1][-1,1][−1,1]。代码:

vec2 p = (2.0*fragCoord.xy-iResolution.xy) / iResolution.y;

2. 画背景

这个效果让人看着舒服,和他的背景关系也非常大。他是从纵向渐变的黑色,并且非常smooth。实现代码是这样的:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{vec2 p = (2.0*fragCoord.xy-iResolution.xy) / iResolution.y;vec3 col = vec3(0.2) + 0.05*p.y;// 加入噪声col += (1.0/255.0)*hash3(p.x+13.0*p.y); // hash3返回随机3维向量fragColor = vec4(col,1.0);
}

hash3返回[0,1)[0,1)[0,1)的随机三维向量。给像素点的颜色值加上这个随机数,是为了避免webGL的优化。如果去掉这行代码,最终结果如下左图,加上后如下右图。

3. 生成运动的小球

先看想要达到的效果:

首先会需要用来生成运动小球的函数:

const float speed = 5.0;
vec2 getPosition( float time, vec4 id ) { return vec2(0.9*sin((speed*(0.75+0.5*id.z))*time+20.0*id.x),0.75*cos(speed*(0.75+0.5*id.w)*time+20.0*id.y) );
}

不同的ididid能得到不同的小球,他们的运动轨迹在xxx轴yyy轴上的投影都是正弦函数。四维向量ididid的每个分量分别决定了:小球水平运动的初项、小球水平运动的频率、小球垂直运动的初项、小球垂直运动的频率。常量speedspeedspeed能控制整体的运动速度。

接下来就能生成ballNum个小球了。

const int ballNum = 15;
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{vec2 p = (2.0*fragCoord.xy-iResolution.xy) / iResolution.y;vec3 col = vec3(0.2) + 0.05*p.y;for( int i=0; i<ballNum; i++ ) {       vec4 off = hash4(float(i) * 13.13); // hash4返回随机4维向量vec3 sph = vec3(getPosition(iTime, off), 0.02+0.1*off.x); // 小球的坐标,第三个维度代表小球半径vec3 sphcol = 0.7 + 0.3*sin( 3.0*off.z + vec3(4.0,0.0,2.0) ); // 随机小球的颜色col = disk( col, p, sph, sphcol ); // 混合得到该像素点的颜色,具体见下文}     col += (1.0/255.0)*hash3(p.x+13.0*p.y);fragColor = vec4(col,1.0);
}

disk函数的作用是,给定像素点ppp的坐标和原来的颜色,计算该像素点混合上小球后的颜色(小球坐标为sph.xysph.xysph.xy,半径为sph.zsph.zsph.z,颜色为sphcolsphcolsphcol)。代码如下:(这部分是笔者自己加的)

vec3 disk( vec3 col, in vec2 uv, in vec3 sph, in vec3 sphcol ) {vec2 xc = uv - sph.xy;float h = (sph.z - sqrt(dot(xc,xc))) / sph.z ;return mix( col, sphcol, clamp(2.0*h,0.0,1.0) );
}

原理就是算像素点到小球球心的距离,根据这个距离决定混合颜色的系数。

到这里为止,完整代码如下:

vec3 disk( vec3 col, in vec2 uv, in vec3 sph, in vec3 sphcol ) {vec2 xc = uv - sph.xy;float h = (sph.z - sqrt(dot(xc,xc))) / sph.z ;return mix( col, sphcol, clamp(2.0*h,0.0,1.0) );
}vec3 hash3( float n ) { return fract(sin(vec3(n,n+1.0,n+2.0))*43758.5453123); }
vec4 hash4( float n ) { return fract(sin(vec4(n,n+1.0,n+2.0,n+3.0))*43758.5453123); }const float speed = 5.0;
vec2 getPosition( float time, vec4 id ) { return vec2( 0.9*sin((speed*(0.75+0.5*id.z))*time+20.0*id.x), 0.75*cos(speed*(0.75+0.5*id.w)*time+20.0*id.y) );
}const int ballNum = 15;
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {vec2 p = (2.0*fragCoord.xy-iResolution.xy) / iResolution.y;vec3 col = vec3(0.2) + 0.05*p.y;for( int i=0; i<ballNum; i++ ) {       vec4 off = hash4( float(i)*13.13 );vec3 sph = vec3( getPosition( iTime, off ), 0.02+0.1*off.x );vec3 sphcol = 0.7 + 0.3*sin( 3.0*off.z + vec3(4.0,0.0,2.0) );col = disk( col, p, sph, sphcol );}     col += (1.0/255.0)*hash3(p.x+13.0*p.y);fragColor = vec4(col,1.0);
}

4.加入动态模糊

为了了解动态模糊的实现,先看一下开启动态模糊时,某一帧的样子。

事实上就是在原来小球的位置,朝着它速度的反方向扫出一条轨迹,这条轨迹的长短与速度大小有关。

要得到小球的速度,只要对小球位置函数求导:

vec2 getVelocity( float time, vec4 id ) { return vec2( speed*0.9*cos((speed*(0.75+0.5*id.z))*time+20.0*id.x)*(0.75+0.5*id.z), -speed*0.75*sin(speed*(0.75+0.5*id.w)*time+20.0*id.y)*(0.75+0.5*id.w) );
}

接下来问题就只是如何求某一像素点的颜色。原来的disk函数已经不够用了。为了简化问题,假设动态模糊时,小球朝速度反方向扫过的长度就等于速度大小。定义一些符号:速度向量cd⃗\vec{cd}cd,小球球心sphsphsph,像素点坐标uvuvuv,起点为sphsphsph终点为uvuvuv的向量xc⃗\vec{xc}xc,小球半径为rrr。

求解下述方程,推导可以知道这是一个一元二次方程。
∣∣cd⃗∗t+xc⃗∣∣=r|| \vec{cd} * t + \vec{xc} || = r∣∣cd∗t+xc∣∣=r

方程具体含义可以见下图。如果该方程无实数根,说明坐标为uvuvuv的像素一定在轨迹之外。若有实数根,得到两个根t1,t2t_1,t_2t1​,t2​,如果有一个根大于1,或两个根都大于0,那么也一定在轨迹外。根据这两个根大小能够混合颜色。具体见代码。

代码如下:

vec3 diskWithMotionBlur( vec3 col, in vec2 uv, in vec3 sph, in vec2 cd, in vec3 sphcol )
{vec2 xc = uv - sph.xy;float a = dot(cd,cd);float b = dot(cd,xc);float c = dot(xc,xc) - sph.z*sph.z;float h = b*b - a*c;if( h>0.0 ) // 判断是否有实数根{h = sqrt( h );float ta = max( 0.0, (-b - h)/a ); // 用求根公式求解float tb = min( 1.0, (-b + h)/a );col = mix( col, sphcol, clamp(2.0*(tb-ta),0.0,1.0) );}return col;
}

最终效果如下:

可以通过修改getVelocity函数,来修改轨迹的长度。例如将轨迹长度改为速度大小的两倍。

完整代码如下:

vec3 diskWithMotionBlur( vec3 col, in vec2 uv, in vec3 sph, in vec2 cd, in vec3 sphcol )
{vec2 xc = uv - sph.xy;float a = dot(cd,cd);float b = dot(cd,xc);float c = dot(xc,xc) - sph.z*sph.z;float h = b*b - a*c;if( h>0.0 ){h = sqrt( h );float ta = max( 0.0, (-b - h)/a );float tb = min( 1.0, (-b + h)/a );col = mix( col, sphcol, clamp(2.0*(tb-ta),0.0,1.0) );}return col;
}vec3 hash3( float n ) { return fract(sin(vec3(n,n+1.0,n+2.0))*43758.5453123); }
vec4 hash4( float n ) { return fract(sin(vec4(n,n+1.0,n+2.0,n+3.0))*43758.5453123); }const float speed = 5.0;
vec2 getPosition( float time, vec4 id ) { return vec2( 0.9*sin((speed*(0.75+0.5*id.z))*time+20.0*id.x), 0.75*cos(speed*(0.75+0.5*id.w)*time+20.0*id.y) );
}
vec2 getVelocity( float time, vec4 id ) { return vec2( speed*0.9*cos((speed*(0.75+0.5*id.z))*time+20.0*id.x)*(0.75+0.5*id.z), -speed*0.75*sin(speed*(0.75+0.5*id.w)*time+20.0*id.y)*(0.75+0.5*id.w) );
}const int ballNum = 15;
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{vec2 p = (2.0*fragCoord.xy-iResolution.xy) / iResolution.y;vec3 col = vec3(0.2) + 0.05*p.y;for( int i=0; i<ballNum; i++ ){        vec4 off = hash4( float(i)*13.13 );vec3 sph = vec3( getPosition( iTime, off ), 0.02+0.1*off.x );vec2 cd = getVelocity( iTime, off ) /24.0 ;vec3 sphcol = 0.7 + 0.3*sin( 3.0*off.z + vec3(4.0,0.0,2.0) );col = diskWithMotionBlur( col, p, sph, cd, sphcol );}       col += (1.0/255.0)*hash3(p.x+13.0*p.y);fragColor = vec4(col,1.0);
}

【shadertoy】线性动态模糊的实现 Analytical Motion Blur相关推荐

  1. Opencv 实现 运动模糊的添加(motion blur)与消除(demotion blur)

    此代码用于实现模糊运动的添加与消除. 原理:在已知模糊运动核的前提下,可通过核线性卷积的形式对图像添加运动模糊, 反之也可利用该核精确的去除该运动模糊. 说明:本例代码是在梳理前人代码的基础上整理得到 ...

  2. 动态模糊或运动模糊(motion blur) 介绍

    // 动态模糊或运动模糊(motion blur)是静态场景或一系列的图片像电影或是动画中快速移动的物体造成明显的模糊拖动痕迹. 摄影技术 当相机拍出影像时,不单只表现出单一时间的即时影像.由于技术限 ...

  3. 像素级动态模糊(Pixel Motion Blur)

    像素级动态模糊(Pixel Motion Blur) 动态模糊近几年广泛应用于游戏制作的一种特效,可以使得游戏所呈现出的运动画面更接近于真实相机所拍摄出的效果.      在真实世界中,运动模糊是指在 ...

  4. Ae动态模糊插件ReelSmart Motion Blur

    REVisionFX ReelSmart Motion Blur for Mac是一款运行在After Effects上的运动模糊插件,这款RSMB动态模糊插件支持自动跟踪动画运动的像素,然后添加自然 ...

  5. Arnold材质节点篇- 动态模糊Motion blur

    动态模糊Motion blur 启用渲染设置中的Motion blur: 快门角度: 长度值的不同效果对比:  关键帧的不同效果: 可以看到关键帧增加以后,运动轨迹形成弯曲的曲率变化,呈现了更好的运动 ...

  6. Unity Shader学习:动态模糊(shutter angle方式)

    Unity Shader学习:动态模糊 动态模糊一般有帧混合和motion vector两种,这里主要介绍motion vector的方法. Keijiro源码:https://github.com/ ...

  7. 求解模糊运动角度matlab,动态模糊图像复原MATLAB程序

    <动态模糊图像复原MATLAB程序>由会员分享,可在线阅读,更多相关<动态模糊图像复原MATLAB程序(7页珍藏版)>请在人人文库网上搜索. 1.1. 退化程序clc;clea ...

  8. 什么是运动模糊(Motion Blur)

    运动模糊是景物图象中的移动效果.它比较明显地出现在长时间暴光或场景内的物体快速移动的情形里. 为什么会出现运动模糊 摄影机的工作原理是在很短的时间里把场景在胶片上暴光.场景中的光线投射在胶片上,引起化 ...

  9. Unity游戏画面参数解析与应用:垂直同步、动态模糊、抗锯齿

    前言 最近会在B站刷到一些关于 30帧暴涨90帧! 高 中 低端显卡运行3A大作优化指南[干货向] 游戏画质设置教程 等等这样关于画面与性能调整的的视频,看完之后受益良多,UP主们经过实际测试获取到宝 ...

最新文章

  1. Python学习笔记之列表切片(六)
  2. 使用TextRange获取输入框中光标的位置
  3. mongoose c++封装
  4. **角点检测(Harris)基于Opencv2.4.9版本+VS2012开发平台进行编**
  5. MySQL集群之五大常见的MySQL高可用方案(转)
  6. 【POJ 2449】第K短路【A*算法】
  7. 带通滤波器作用和用途_带通滤波器是什么,带通滤波器的作用
  8. 定位算法——多边测量法及MATLAB编程
  9. 微信对账单 java_微信下载对账单
  10. EDK2编译报错,请帮我看看这个是什么错误
  11. 最新Spring Boot实战项目(权限后台管理系统)详解
  12. Django(投票系统项目)
  13. 源码解析2-GUI-绘制引擎(QPainter源码分析 )
  14. 计算机视觉相关网站整理
  15. 论对B/S模式外贸电子商务系统的规划和设计
  16. eap wifi 证书_如何手动连接802.1x EAP证书加密WIFI
  17. u-boot (1) —— 编译分析
  18. golang不编译.html,Golang 编译 条件编译
  19. 为什么图像成像时近大远小?
  20. React Native小菜鸡的踩坑排雷记录(4)

热门文章

  1. SQL获取当前周的开始日期和结束日期
  2. C# 实现类似SMSS的执行脚本的功能
  3. 批处理文件语法大全(怎么写Bat文件)
  4. oracle 创建唯一索引
  5. [BZOJ4430][Nwerc2015]Guessing Camels赌骆驼
  6. Java 密码学算法
  7. 基于Java毕业设计在线交易系统源码+系统+mysql+lw文档+部署软件
  8. 用python计算准确率_Python下的scikit-learn预测准确率计算(代码实例)
  9. Ambari入门及安装
  10. Fluter 应用调试