视频混剪-转场效果是怎么实现的

视频混剪-转场效果是怎么实现的

BaseCut 技术博客第四篇。这篇聊转场------两个画面之间的淡入淡出、滑动、擦除是怎么做的。


什么是转场

最简单的解释:让画面 A 逐渐变成画面 B。

没有转场的话,两个片段之间是硬切:

复制代码
时间轴:

片段 A    |    片段 B
oooooooooo|oooooooooo
          ↑
      瞬间切换

有转场的话:

复制代码
片段 A      转场区间      片段 B
ooooooooo[A+B 混合区]ooooooooo
         ↑───────────↑
       起点         终点

转场的历史:电影中的视觉语言

转场不是瞎搞,它是电影百年历史沉淀下来的视觉语言。

硬切(Hard Cut)

镜头直接切换,没有任何过渡。

用途:

  • 同一场景的不同角度
  • 紧张、快节奏的剪辑

淡入淡出(Fade In/Out)

画面逐渐变亮或变暗,通常是黑场。

用途:

  • 场景开始/结束
  • 表示时间流逝

叠化(Dissolve)

两个画面叠加在一起,A 逐渐消失,B 逐渐出现。

用途:

  • 平行时空
  • 回忆、梦境
  • 时间流逝

擦除(Wipe)

一条线从一侧推到另一侧,后面露出新画面。

用途:

  • 《星球大战》经典转场
  • 复古风格

转场的数学本质

核心公式:线性插值

不管多复杂的转场,核心都是一个公式:

复制代码
output = A × (1 - progress) + B × progress

其中 progress 从 0 变到 1:

复制代码
progress = 0.0  →  100% A,0% B
progress = 0.5  →  50% A,50% B
progress = 1.0  →  0% A,100% B

这个公式叫 Linear Interpolation ,简称 Lerp,是图形学最常用的公式之一。

不同转场类型

虽然公式一样,但 A 和 B 的"混合方式"可以有很多变化:

类型 混合方式
淡入淡出 整个画面透明度渐变
滑动 B 从画面外推入,A 被推走
擦除 一条线逐渐划过,后面露出 B
缩放 A 缩小消失,B 放大出现

缓动函数(Easing)

如果 progress 匀速从 0 增长到 1,动画会很"机械",像机器人一样。

真实世界的物体都有惯性:

  • 汽车启动:先慢后快(Ease-In)
  • 门关闭:先快后慢(Ease-Out)
  • 弹簧:快-慢-回弹

常用缓动函数

线性(Linear)

javascript 复制代码
function linear(t) {
  return t
}

没有加速度,匀速变化。

缓入(Ease-In)

javascript 复制代码
function easeIn(t) {
  return t * t
}

开始慢,结束快。

缓出(Ease-Out)

javascript 复制代码
function easeOut(t) {
  return 1 - (1 - t) * (1 - t)
}

开始快,结束慢。

缓入缓出(Ease-In-Out)

javascript 复制代码
function easeInOut(t) {
  return t < 0.5
    ? 2 * t * t
    : 1 - Math.pow(-2 * t + 2, 2) / 2
}

两头慢,中间快。最常用。

对比效果

复制代码
时间:  0    0.25   0.5   0.75   1
线性:  0    0.25   0.5   0.75   1.0
缓动:  0    0.12   0.5   0.88   1.0

缓动让动画有"弹性",更自然。


WebGL 实现

双纹理绑定

转场需要同时访问两帧画面,所以要绑定两个纹理:

javascript 复制代码
// 绑定 A 帧到纹理槽位 0
gl.activeTexture(gl.TEXTURE0)
gl.bindTexture(gl.TEXTURE_2D, textureA)
gl.uniform1i(uTexALocation, 0)

// 绑定 B 帧到纹理槽位 1
gl.activeTexture(gl.TEXTURE1)
gl.bindTexture(gl.TEXTURE_2D, textureB)
gl.uniform1i(uTexBLocation, 1)

// 传入进度
gl.uniform1f(uProgressLocation, progress)

// 渲染
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)

淡入淡出着色器

glsl 复制代码
uniform sampler2D u_texA;
uniform sampler2D u_texB;
uniform float u_progress;

void main() {
  vec4 colorA = texture2D(u_texA, v_texCoord);
  vec4 colorB = texture2D(u_texB, v_texCoord);
  
  // GLSL 内置的 mix 函数就是 Lerp
  gl_FragColor = mix(colorA, colorB, u_progress);
}

滑动着色器

glsl 复制代码
void main() {
  // A 向左移动
  vec2 uvA = v_texCoord + vec2(u_progress, 0.0);
  // B 从右边进来
  vec2 uvB = v_texCoord - vec2(1.0 - u_progress, 0.0);
  
  vec4 colorA = texture2D(u_texA, uvA);
  vec4 colorB = texture2D(u_texB, uvB);
  
  // 根据 UV 坐标判断显示哪个
  if (v_texCoord.x > 1.0 - u_progress) {
    gl_FragColor = colorB;
  } else {
    gl_FragColor = colorA;
  }
}

擦除着色器

glsl 复制代码
void main() {
  vec4 colorA = texture2D(u_texA, v_texCoord);
  vec4 colorB = texture2D(u_texB, v_texCoord);
  
  // 一条竖线从左向右扫过
  if (v_texCoord.x < u_progress) {
    gl_FragColor = colorB;
  } else {
    gl_FragColor = colorA;
  }
}

转场时机判断

播放器在渲染每一帧时,需要判断当前是否处于转场区间:

typescript 复制代码
function render(currentTime: number) {
  // 查找当前时间是否有转场
  const transition = findActiveTransition(currentTime)
  
  if (transition) {
    // 计算进度 (0-1)
    const rawProgress = (currentTime - transition.startTime) / transition.duration
    
    // 应用缓动函数
    const progress = easeInOut(rawProgress)
    
    // 获取两帧画面
    const frameA = getFrame(transition.clipA, currentTime)
    const frameB = getFrame(transition.clipB, currentTime)
    
    // 渲染转场
    webgl.renderTransition(frameA, frameB, progress, transition.type)
  } else {
    // 正常渲染单帧
    const frame = getCurrentFrame(currentTime)
    webgl.renderFrame(frame)
  }
}

下一篇

讲视频导出------怎么把编辑好的内容变成 MP4 文件,以及 WebCodecs 的原理。


系列目录

  1. 技术选型与项目结构
  2. 时间轴数据模型
  3. WebGL 渲染与滤镜
  4. 转场动画实现(本文)
  5. WebCodecs 视频导出
  6. LeaferJS 贴纸系统
相关推荐
一条破秋裤21 小时前
【V4L2采集视频】
音视频
橙橙笔记1 天前
QT的学习第四部分
linux·qt·学习·音视频
AI视频工具指南2 天前
用 AI 能做哪些视频?按想交付的作品选做法
人工智能·aigc·音视频
希和20272 天前
免费AI视频生成工具汇总(国内优先,适合做漫剧、短视频,Obsidian笔记版)
人工智能·笔记·音视频
謓泽2 天前
【插件】用写网页的方式做视频?HyperFrames 让我重新理解了“做视频“这件事!
gpt·ai·大模型·音视频·媒体·claude·视频
无忧_wy2 天前
MoneyPrinterTurbo 自动化视频生成实战指南
运维·人工智能·自动化·音视频
联犀3 天前
联犀 Go SIMD 实战:视频转码从 16.5ms 到 5.46ms
开发语言·golang·音视频
9527华安4 天前
FPGA纯verilog实现DP视频接口,支持2K60帧分辨率,提供3套工程源码和技术支持
fpga开发·音视频·dp
MetaEnchanter4 天前
视频章节怎么整理:填好时间点,导出前检查这几处
音视频
小豆庄4 天前
测试视频里的人脸车牌敏感信息自动化脱敏方案
计算机视觉·目标跟踪·ffmpeg·音视频·边缘计算·视频·敏捷流程