剪映APP的视频特效如何在Web中JS实现

这篇文章发布于 2023年11月19日,星期日,23:12,归类于 JS实例。 阅读 9473 次, 今日 13 次 3 条评论

 

封面图-闪电特效实现

一、前言

视频剪辑软件,无论是桌面端还是手机端,都已经被剪映称霸。

其中,有个“特效”的标签页,里面有各式各样的效果,如下图所示:

剪映特效示意

本文会以pixi.js为基础工具,介绍这里面的特效如何在Web网页中实现。

这里面的特效分为了两种:

一种是直接改变当前绘制的滤镜,主要是通过解析.frag后缀的文件后渲染实现,例如模糊,扭曲等效果。

模糊等效果示意

另外一种是混合滤镜,专门实现一些氛围特效效果,主要是通过解析MP4视频资源(本质是图片序列),然后使用滤色混合模式实现,例如下雨起雾,烟花绽放等效果。

氛围中的特效

好,系好安全带,准备发车了。

准备开车

二、frag滤镜的解析与渲染

frag文件其实上不能叫做滤镜,而是一个片段着色器,它的作用是改变当前绘制的颜色,从而实现一些特殊的效果。

frag文件的格式如下:

uniform sampler2D uSampler; // 采样器
varying vec2 vTextureCoord; // 纹理坐标
void main(void) {
    vec4 color = texture2D(uSampler, vTextureCoord); // 采样
    gl_FragColor = color; // 输出
}

需要注意的是,uSampler和vTextureCoord是pixi.js内部定义的变量,如果你使用其他3D库,例如Three.js或p5.js或Babylon.js,则可能就不是这个名称。

另外,uSampler和vTextureCoord是可以自定义的,不过这就属于高端操作了,对于初学者而言,不要关心这个,就默认的这个内置变量玩玩就可以了。

uniform与变量控制

frag着色器效果是动态的,这个动态是通过uniform变量控制的,也就是传入不同的值,会有不同的着色变化。下面是一个抖动效果的frag文件:

precision highp float;

varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform float scale;
uniform float horzIntensity;
uniform float vertIntensity;

void main() {
    vec2 newTextureCoordinate = vec2((scale - 1.0) * 0.5 + vTextureCoord.x / scale,
                                        (scale - 1.0) * 0.5 + vTextureCoord.y / scale);
    vec4 textureColor = texture2D(uSampler, newTextureCoordinate);
    
    // shift color
    vec4 shiftColor1 = texture2D(uSampler, newTextureCoordinate + vec2(-0.05 * (scale - 1.0) * horzIntensity * 2., -0.05 * (scale - 1.0) * vertIntensity * 2.));
    vec4 shiftColor2 = texture2D(uSampler, newTextureCoordinate + vec2(-0.1 * (scale - 1.0) * horzIntensity * 2., -0.1 * (scale - 1.0) * vertIntensity * 2.));
    
    // 3d blend color
    vec3 blendFirstColor = vec3(textureColor.r, textureColor.g, shiftColor1.b);
    vec3 blend3DColor = vec3(shiftColor2.r, blendFirstColor.g, blendFirstColor.b);
    gl_FragColor = vec4(blend3DColor, textureColor.a);
}

是不是看不懂,天书一样的?没错,我也看不懂。😅

不过不要紧,看不懂并不影响我们的使用,因为着色的细节都是写死的,我们只需要关心uniform变量即可。

在这个frag中,顶部有三行uniform float xxx的定义,表明此特效需要三个浮点类型的变量,scale、horzIntensity、vertIntensity。

需要关心的变量

才名称上判断,scale应该就是缩放比例,horzIntensity和vertIntensity应该是水平和垂直方向的抖动强度。

在frag中,通常变量的值都比较小,因为都是以像素为单位的,太大的值效果太夸张,不太实际,例如这里的水平和垂直方向的抖动,我们可以传入0.1-0.5之间的值,这样效果就会比较自然。

至于scale,最大缩放1.2就差不多了。

frag文件的加载与使用

frag文件本质是是个文本文件,我们可以使用Fetch API获取,如下所示:

// 素材资源准备
const fragUrl = './scale.frag';
// 加载frag资源,作为字符串
const frag = await fetch(fragUrl).then(res => res.text());

如果嫌麻烦,也可以直接内联在JavaScript文件中。

接下来就是应用frag文件了,pixi官网有不少使用案例,大家参考参考实现就可以了,例如这个演示页面

或者参考我的实现,您可以狠狠地点击这里:pixijs解析frag并应用在图片上demo

然后就可以看到类似抖音logo一般的抖动效果,如下视频所示,不动点击播放:

不过此视频是录制的,掉帧严重,想要感受原始效果,还是去上面的演示页面,如果嫌弃画面晃得眼睛疼,点击画面就可以暂停了。

其中,与frag特效实现的代码非常简单,聊聊数行,示意如下:

const uniforms = {
    scale: 1.0,
    horzIntensity: 0.5,
    vertIntensity: 0.5,
};

// 目标缩放scale
// 可以使用 Tween.js 等库来实现动画
// https://github.com/zhangxinxu/Tween
// 这里呢,使用枚举的方式逐帧变化实现动画
const scale = [1.0,1.07,1.1,1.13,1.17,1.2,1.2,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0];

// 创建自定义滤镜
const filter = new PIXI.Filter(null, frag, uniforms);
container.filters = [filter];

// 动画
let start = 0;
app.ticker.add(() => {
    // 每帧更新滤镜的uniform变量
    filter.uniforms.scale = scale[start++ % scale.length];
});

原理很简单,将frag中的uniform变量弄出来,然后改变这些值,动画就出现了。

至于动画如何变,方法很多,你可以线性变化,也可以使用贝塞尔曲线缓动,这个推荐使用我之前弄的 Tween.js 动画算法库来实现。

而这里这个例子直接枚举了每一帧的缩放比例,也算是一种低成本的方式。

其他frag的使用

其他frag文件的使用其实大同小异,大家也没有必要花时间去学习具体的实现算法,因为在业界,frag文件可以说是免费公开的资源,很多的,一抓一大把,你想要的效果都有,没有说需要自己去写特效滤镜的,除非你想成为这个领域的大师。

当然,有些细节还是要说下的。

就是frag中的变量不仅有float浮点型,还有整型,矢量变量和特殊变量,例如下面某个实现模糊效果的frag文件顶部定义的变量:

precision highp float;

uniform sampler2D uSampler;
varying vec2 vTextureCoord;
uniform float blurSize;
uniform float angleR;
uniform float angleG;
uniform float angleB;
uniform vec2 moveR;
uniform vec2 moveG;
uniform vec2 moveB;

uniform vec4 u_ScreenParams;

其中,vec2表示二维向量,数组,支持两个值,第一个值与x坐标有关,第二个值与y坐标有关;vec4表示是个自个项目数量的数组,下面就是这几个变量的默认值设置示意,希望可以给大家一些参考。

const uniforms = {
    blurSize: 0.0,
    angleR: 0.0,
    angleG: 0.0,
    angleB: 0.0,
    moveR: [0, 0],
    moveG: [0, 0],
    moveB: [0, 0],
    // viewWidth和viewHeight是canvas画布尺寸,表示整个画面都应用特性
    u_ScreenParams: [0, 0, viewWidth, viewHeight]
};

三、MP4视频与氛围特效的实现

氛围特效也是应用范围很广的一种效果,这种效果可以批量化生产,只要有足够的素材,使用统一的混合模式就可以实现了。

而这个混合模式一定是滤色screen混合模式,其计算公式是:滤色模式的计算公式

具有以下混合特征:

  1. 任何颜色和黑色执行滤色,还是呈现原来的颜色;
  2. 任何颜色和白色执行滤色得到的是白色;
  3. 任何颜色和其他颜色执行滤色模式混合后的颜色会更浅,有点类似漂白的效果。

基于上述特征,screen模式就特别适合下雨,闪电,花瓣飘飘的效果,只要背景素材是黑底或透明底即可。

再说素材。

氛围特效本质上就是将一系列的图片资源和原始的视频内容进行混合,不需要人为地实现动画,因为动画都是序列帧。

而之所以选择使用MP4视频作为素材资源是因为这两个原因:

  1. 单一文件,相比PNG或JPG图片序列帧,请求数量少很多,对减少cos资源的占用和提高页面的交互速度都很有帮助。
  2. 体积最小,虽然APNG文件也是单一文件,也能解析并渲染,但是体积实在是太大了,根据我的实际使用体验,APNG的尺寸是MP4的10倍。

如果我只有图片序列

如果只有图片序列,而没有MP4视频,很简单,我多年前做了个APNG在线合成的工具,最近稍微扩展了下,也支持了视频的合成。

狠击这里:APNG/MP4在线合成下载工具

例如选择一套闪电的序列帧图片:

闪电素材选择

直接点击下面的按钮,就可以下载对应的APNG和MP4了,默认是每秒24帧,如果希望是每秒30帧,修改时间间隔从42ms为33ms。

按钮下载示意

视频的解析与渲染

在过去,类似效果的实现都是使用图片序列帧,为何现在推荐使用视频了呢,那是因为WebCodecs API,浏览器现在有了解码MP4的能力,我们可以非常高效地解码视频中的序列帧图片。

至于具体的实现,可以参考上一篇文章:“mp4box.js加WebCodecs 解码MP4视频帧并渲染

然后,为了方便大家学习与参考,我使用那个闪电素材做了个演示页面,从网上找了个没版权的阴天图片,喏,就是下图:

阴天素材

然后解析闪电视频,再一融合,一个逼真的特性就出来了。

闪电特效

眼见为实,您可以狠狠地点击这里:Pixi.js实现闪电特效demo

完整代码demo页面均有,还有贴心的注释哦~

另外,如果你发现合成的视频解码时候报编码不识别的错误,例如,有些陈旧的windows电脑合成的MP4文件可能就会有这个问题,则可以换个高级的设备合成下,例如Mac OS,或者更换电脑升级硬件。

四、视频滤镜等其他说明

除了特效,视频剪辑工具中还有一个大类叫做滤镜,如下图所示。

剪映中的滤镜

这种滤镜其实也分为两大类,一类是算法滤镜,例如高斯模糊,反相,色调变化,本质上是数学计算,但这类效果实现的滤镜往往不够精致,还有一类是颜色映射滤镜,关键词是ColorMapFilter,这个我之前其实介绍过,“3D LUT 滤镜颜色映射原理剖析与JS实现”,也提供了解决方案。

这种滤镜要么是一个ColorMap的PNG图片,就像下图这样的:

色彩平面图

要么就是LUT文件(多以.cube或.3dl后缀结尾),就像下面这样的:

LUT数据点截图示意

本质上是一个东西,之前提供过2d canvas的实现示意,不过2d的解析速度一般,使用WebGL解析会更快。

但,这些内容不是本文想要介绍的,所以就不展开,等时机成熟,我有空,心情也好了,再开一篇讲讲如何在Web中实现各类APP中的滤镜效果。

噢啦,就这么多,创作不易,尤其这种独家内容,欢迎转发,欢迎,也欢迎支持我写的书籍、小册或者我抖音上拍的钓鱼视频(搜“最会钓鱼的程序员”关注我)。

我的抖音号

(本篇完)

分享到:


发表评论(目前3 条评论)

  1. 720说道:

    3D LUT 滤镜颜色映射原理剖析与JS实现 链接错了

  2. 老五子说道:

    大佬dy里面分享的果然都是钓鱼🎣,只字不谈技术。😂

  3. 代码如诗如画说道:

    跟上大佬的步伐