ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenReel Video 可关键帧化着色器效果与填充(Shader Fills Effects)设计解析与实现验证

OpenReel Video 可关键帧化着色器效果与填充(Shader Fills  Effects)设计解析与实现验证 OpenReel Video 可关键帧化着色器效果与填充Shader Fills Effects设计解析与实现验证【免费下载链接】openreel-videoOpenReel Video - Professional browser-based video editor. Open source CapCut alternative. 100% browser-based, no installation, no cloud uploads, no watermarks.项目地址: https://gitcode.com/GitHub_Trending/op/openreel-videoOpenReel Video 的 Motion 模块通过docs/superpowers/specs/2026-06-30-keyframeable-shader-fx-design.md这份设计文档规划并落地了一套参数化、可在时间轴上打关键帧的程序化着色器体系既能作为图层填充如液态金属、水彩也能作为效果叠加在图层输出上如抖动、渐变映射、像素化。本文以该设计文档为骨架结合 packages/core/src/motion/ 下已实现的源码完整讲解 v1 的架构、着色器库契约、渲染器原理、参数关键帧机制、Inspector 集成、关键决策与风险应对帮助你在阅读代码或二次开发时快速建立全局认知。一、设计目标与范围v1 Scope1.1 要解决的问题设计文档开篇即点明目标对齐 Figma Motion 的核心卖点——一个精选的参数化程序化着色器库分为两类Fills填充作为图层填充的程序化材质例如 liquid metal液态金属、watercolor水彩Effects效果变换图层输出例如 dither抖动、gradient map渐变映射、pixelate像素化。关键约束是每个着色器的每个参数都能在 Motion 时间轴motion timeline上打关键帧即effect.id.param形式的参数路径。设计文档明确 v1 不做 GLSL 代码编辑器也不做 AI 生成着色器这两个是后续子项目。文档还特别强调了与既有代码的关系此前子项目 1 是暴露已有引擎而本子项目2 of 4是构建缺失的引擎——Motion 应用的 2D 渲染目前基于 Canvas2D对 2D 图层没有 GPU 片元着色器 pass。这正是本项目存在的根本原因。1.2 v1 范围清单维度v1 内容说明渲染基座WebGL2 全屏四边形fullscreen-quad片元着色器 pass复用 scene3d 已验证的 render GL → bitmap → composite into 2D canvas 模式实时预览与导出共用效果库Dither、Gradient Map、Pixelate、Halftone4 个效果源码中实际扩展到 12 个见下文填充库Liquid Metal、Watercolor、Gradient Noise3 个填充参数化 可关键帧每个着色器暴露具名数值/颜色参数到 Inspector数值参数通过effect.id.param关键帧InspectorEffectsPanel Add Effect 图库新增 Shaders 分类填充控件新增 Shader 填充类型预览一致性着色器图层复用 scene3d 的 renderer-backed 预览保证预览与导出一致OUT明确不做GLSL 代码编辑器、AI 生成着色器、输入/光标响应式着色器、逐字形文本着色器逐字形文本着色器属于单独的 shader-text 子项目需要说明设计文档描述的 v1 为 4 效果 3 填充而当前仓库源码 effect-shaders.ts 中已实现并注册了 12 个效果在 4 个 v1 基础上扩展了 VHS、Posterize、Duotone、Prism Split、Fisheye、Wave Warp、Scanlines、Edge Glow并新增了 text文本着色器与 paperPaper Design 第三方着色器类别。本文以设计文档主体为准同时用源码现状印证架构可行性。二、Shader 库单一事实来源Single Source of Truth2.1 核心类型契约设计文档定义了着色器描述def与参数描述param def两类接口源码 shaders/types.ts 将其完整实现并做了增强export type MotionShaderParamType number | color; export interface MotionShaderParamDef { readonly name: string; // uniform key例如 scale readonly label: string; // inspector 标签 readonly type: MotionShaderParamType; readonly default: number | string; readonly min: number; readonly max: number; readonly step: number; readonly control?: slider | number; // 源码新增控件类型偏好 } export type MotionShaderCategory fill | effect | text; // 源码新增 text export interface MotionShaderDef { readonly id: string; // 稳定 id例如 liquid-metal readonly name: string; readonly category: MotionShaderCategory; readonly glsl: string; // 片元着色器源码body readonly params: readonly MotionShaderParamDef[]; // 以下为源码在实现期新增的扩展字段 readonly origin?: builtin | generated; readonly vertexShader?: string; // 自定义顶点着色器默认内置 readonly staticUniforms?: ReadonlyRecordstring, number | readonly number[]; readonly colorArrayParams?: MotionShaderColorArrayParams; // 颜色数组 uniform readonly needsNoiseTexture?: string; // 需要绑定噪声纹理的 uniform 名 readonly timeScale?: number; // u_time 缩放 readonly inputUniform?: string; // 输入纹理 uniform 名默认 u_input readonly collection?: string; // 集合分组 }与设计文档MotionShaderParamDef相比源码将min/max/step从可选?改为必填并新增control字段用于告诉 Inspector 渲染滑块还是数字输入框。2.2 库注册表与查询 API设计文档要求MOTION_SHADER_LIBRARY数组 getMotionShaderDef(id)查询函数源码 shaders/index.ts 实现如下export const MOTION_SHADER_LIBRARY: readonly MotionShaderDef[] [ ...EFFECT_SHADERS, ...FILL_SHADERS, ...TEXT_SHADERS, ...PAPER_SHADER_DEFS, ]; export function getMotionShaderDef(id: string): MotionShaderDef | undefined { return MOTION_SHADER_LIBRARY.find((def) def.id id) ?? generatedShaderById(id); } export function getMotionShaderEffectDefs(): readonly MotionShaderDef[] { /* effect 类别过滤 */ } export function getMotionShaderFillDefs(): readonly MotionShaderDef[] { /* fill 类别过滤 */ } export function defaultMotionShaderParams(def: MotionShaderDef): Recordstring, number | string { return Object.fromEntries(def.params.map((p) [p.name, p.default])); }值得注意的是getMotionShaderDef的兜底逻辑内置库查不到时还会通过 registry.ts 的generatedShaderById(id)查找运行时注册的生成着色器registerMotionShader/unregisterMotionShader动态增删。这意味着虽然 v1 明确不做 AI 生成与代码编辑器但底层已经为后续子项目预留了动态注册的扩展点。2.3 内建着色器与参数一览源码现状效果effect全部采样u_input图层纹理定义于 effect-shaders.tsid名称参数name/label/默认/min/max/stepditherDitherlevels4 (2–16, 1)、scale1 (1–8, 1)gradient-mapGradient Mapmix1 (0–1, 0.01)pixelatePixelatesize8 (1–64, 1)halftoneHalftonedotSize8 (2–32, 1)、angle15 (0–90, 1)vhsVHSintensity0.75、scanlines0.4、jitter0.45均 0–1posterizePosterizelevels5 (2–16)、mix1 (0–1)duotoneDuotoneshadowColor#11133f、highlightColor#ffca6b、mix0.9、contrast1.15 (0.25–2.5)prismPrism Splitamount8 (0–40)、angle0 (0–360)、mix1fisheyeFisheyestrength0.55 (−1–1.5)、radius0.8 (0.2–1.5)wave-warpWave Warpamplitude0.025 (0–0.15)、frequency5 (1–20)、speed1.5 (0–8)scanlinesScanlinesdensity360 (40–1200)、intensity0.3、speed0.2edge-glowEdge Glowstrength4 (0–12)、radius1.5 (0.5–6)、color#4de8ff填充fill不采样输入纹理由vUv与参数直接生成定义于 fill-shaders.tsid名称参数liquid-metalLiquid Metalscale6 (1–20)、speed0 (0–2)、contrast1.4 (0.5–3)均control: numberwatercolorWatercolorscale5 (1–16, number)、bleed0.5 (0–1, slider)gradient-noiseGradient Noisescale8 (1–24, number)、warp0.4 (0–1, slider)这三类填充的实现思路完全一致用 value noise fbm分形布朗运动叠加多层噪声再通过 smoothstep / sin 等映射成材质观感。例如liquid-metal用 5 层 fbm 叠加 sin 干扰pow(ramp, 1.6)塑造金属高光watercolor用三层噪声加权0.55/0.3/0.15模拟水彩纸面洇染gradient-noise用域扭曲domain warp做流动的渐变噪声。它们都以#version 300 es开头、out vec4 fragColor结尾符合后文所述的校验契约。2.4 着色器契约校验器为了让新增着色器不会在运行期炸掉源码新增了 motion-shader-validator.ts实现设计文档隐含的每帧同步渲染必须稳定的要求export function validateMotionShaderSource(glsl, category): MotionShaderValidationResult它做两层检查静态契约不依赖 GL 环境GLSL 非空必须声明#version 300 es必须声明out vec4 fragColoreffect 类必须声明uniform sampler2D u_input否则无法采样图层纹理fill 类禁止引用u_input填充是自生成的不允许依赖输入text 类必须声明uniform float u_progress供逐字形动画使用。真实编译若MotionShaderRenderer.isSupported()为真则调用共享渲染器的validateFragmentSource(glsl)在 WebGL2 上下文中实际编译一次并返回错误日志。这套校验器与渲染器解耦既可以在单元测试里 mock也可以在运行时对新注册的着色器做准入校验。三、MotionShaderRendererWebGL2 渲染器实现设计文档第 2 节要求一个轻量 WebGL2 渲染器一个共享离屏 GL 上下文 一个单位四边形源码 motion-shader-renderer.ts 完整实现了该设计接口如下render(def: MotionShaderDef, input: MotionShaderRenderInput): MotionShaderCanvas | null // MotionShaderRenderInput: { width, height, time, progress?, params, inputCanvas? } // 返回 HTMLCanvasElement 或 OffscreenCanvas3.1 渲染管线核心步骤render()的内部流程对应 motion-shader-renderer.tsensureContext()惰性创建共享 WebGL2 上下文属性为premultipliedAlpha: true、preserveDrawingBuffer: true、antialias: false上传三角形大四边形顶点[-1,-1, 3,-1, -1,3]一个覆盖屏幕的 2 三角形四边形ensureProgram(gl, def)按def.id查程序缓存未命中则编译vertex 默认用内置VERTEX_SHADER_SOURCE也可用def.vertexShader覆盖并 link记录a_position属性位置与所有 active uniform 的位置若def.inputUniform ?? u_input存在且传入inputCanvas将输入画布上传为TEXTURE_2DCLAMP_TO_EDGE、LINEAR过滤、UNPACK_FLIP_Y_WEBGLtrue绑定到 TEXTURE0若def.needsNoiseTexture从paper-design/shaders获取噪声纹理paper-shaders.ts 提供绑定到 TEXTURE1REPEAT环绕依次上传标准 uniformu_resolution像素宽高、u_timetime * (def.timeScale ?? 1)非有限数时回退 0、u_progressinput.progress ?? 0上传def.staticUniforms与def.params声明的参数 uniform数值参数经resolveShaderParamUpload上传为float颜色参数解析为 RGBA 后上传为vec4若 GLSL 中该 uniform 实为float则退化为亮度值colorLuminance若有def.colorArrayParams如多色渐变数组color1..colorN展开为uniform4fv并同步上传数量 uniformgl.drawArrays(TRIANGLES, 0, 3)完成一帧返回离屏画布。编译一次性、渲染同步设计文档强调 compile is one-time; render is sync源码通过private readonly programs new Mapstring, CompiledProgram | null()实现按 id 的编译缓存ensureProgram这正是async compile vs sync render风险的关键化解手段。3.2 优雅降级永不出现空白图层设计文档要求 Degrades safely if WebGL2 is unavailable (returns the input unchanged for effects / a flat fallback for fills) — never a blank layer。源码实现了renderMotionShaderFallbackCanvas(def, input)上下文创建失败、程序编译失败、渲染抛异常三条路径都会进入该函数用 Canvas2D 合成一个确定性近似材质取 def 中颜色参数不足时用 def.id 哈希出的色相生成 HSL 三色做线性渐变对含 dot/halftone/dither 的 id 绘制点阵对含 wave/water/swirl 的 id 绘制随时间正弦波动线条对 effect 类先drawImage输入并source-atop合成、globalAlpha 0.82最后destination-in恢复输入 alpha——刻意保留输入透明度让文本/效果预览仍有意义而非退化为纯色MotionShaderRenderer.isSupported()静态方法可在 UI 层预判能力motion-shader-validator.ts 也用它决定是否执行真实编译校验。3.3 上下文生命周期与丢失恢复设计文档 Risk 一节要求处理webglcontextlost。源码在ensureContext()里注册了 handler上下文丢失时清空程序缓存、输入纹理与噪声纹理引用资源已被 GL 释放但保留 renderer 实例下一次render()若gl仍可用则重新编译否则走 fallback。dispose()负责反向清理所有 program/shader/buffer/texture 并移除监听。整条链路保证任何 GL 异常都不会击穿 2D 渲染树。四、Effect 与 Fill 的集成点4.1 Effect 类型与渲染接入设计文档要求把shader加入MotionEffectType并新增MotionShaderEffect类型。源码 types.ts 已落地// types.ts:51 —— MotionEffectType 联合中加入 | shader // types.ts:424-426 —— MotionEffect 联合中的新成员 export interface MotionShaderEffect extends MotionEffectBase { readonly type: shader; readonly shaderId: string; readonly params: Recordstring, number; }注意设计文档中的MotionShaderEffect还带params泛型 bag源码进一步将其声明为readonly符合不可变数据流风格。渲染接入点设计文档指向renderVisualLayerWithAdvancedMasksmotion-renderer.ts 中像素效果已经走 getImageData → mutate → putImageData 的临时画布路径。实现策略是把 shader effect 与 pixel effect 归为一类触发临时画布路径后按效果栈顺序对每个启用的 shader effect 调用MotionShaderRenderer.render(def, { inputCanvas: tempCanvas })并把结果画回从而让多层效果可以顺序叠加。4.2 Fill 类型与绘制接入设计文档要求扩展FillStylegraphics/types.ts增加shader变体并为文本层增加fillShader?覆盖字段因为文本用的是fillGradient而非FillStyle判别式。绘制路径设计文档定位到renderShape/createShapeFillStyle/resolveTextFillStyle当填充为 shader 时用MotionShaderRenderer渲染不传 inputCanvas因为填充自生成随后ctx.fillStyle ctx.createPattern(shaderCanvas, no-repeat); ctx.fill(path);即把着色器输出画布转成 Canvas2D pattern 再填充路径这样 Canvas2D 渲染树完全不需要感知 GPU 细节。填充的工厂函数与守卫逻辑位于 motion-shape-style.ts保证非法/缺失 shaderId 时回退到安全填充。4.3 预览一致性Live Preview Parity设计文档强调着色器 fill/effect 不产生 CSS/Canvas2D 输出因此 DOM 预览必须强制走 renderer-backed 位图预览。源码沿用 scene3d 的usesRendererPreview机制StageCanvas.tsx 中判断只要图层带任何 shader fill/effect就强制使用 renderer 预览 2D hit-test。由于MotionShaderRenderer运行在MotionRenderer内部预览与导出天然共享同一条渲染路径避免编辑器显示未着色图层、导出却有特效的静默分叉——这也是设计文档 Risk 一节点名要防的问题。五、可关键帧参数数据驱动分支与 10/23 Bug 修复5.1 现状问题硬编码参数系统设计文档指出旧的 effect 参数系统是硬编码的一个 23 项MotionEffectNumericParameter枚举 motion-effects.ts 中每个 effect 的 get/set switch。而isMotionEffectNumericParameter只白名单了 23 项中的 10 项导致其余 13 个已声明参数静默地无法打关键帧已确认的 bug。源码现状印证MotionEffectNumericParameter类型定义在 motion-effects.tsgetMotionEffectParameterValue在 L912getMotionEffectParameterValueAtTime在 L992内部都依赖该枚举。5.2 数据驱动 shader 分支设计文档给出的改造方案已按计划实现getMotionEffectParameterValue/setMotionEffectParameterValue增加 shader 分支当 effect 类型为shader时直接读写effect.params这个Recordstring, numberbag任何参数名都通用无需为每个参数写 switchgetMotionLayerEffectPropertyDescriptorsmotion-keyframes.ts把 shader def 的数值参数定义反射进 graph-picker 与时间轴即用def.params元数据驱动 UI而不是枚举路径校验器放行 shader 参数名让effect.id.param这类路径能通过校验并进入关键帧系统。这套参数即泛型 bag def 元数据驱动的架构让新增一个着色器及其全部参数不需要触碰任何枚举或 switch正是设计文档 Key Decision 中 data-driven without enumerating every param 的落地。5.3 相邻正确性修复设计文档要求修复isMotionEffectNumericParameter丢参数的 bugverified bug并配回归测试断言原本被丢弃的 13 个参数现在可以打关键帧。这一修复虽然表面与 shader 无关但它改变了既有 effect 哪些参数可关键帧的行为因此设计文档特别强调需要回归测试覆盖避免重构 blast radius 扩散。六、Inspector UI 与交互设计文档第 6 节规划的 UI 集成对应 EffectsPanel.tsx 与填充控件EffectsEffectsPanel 的 Add Effect 图库新增Shaders分组列出getMotionShaderEffectDefs()点击添加即创建一个带默认参数defaultMotionShaderParams的MotionShaderEffecteffect 控件根据每个 param def 渲染Slider/NumberInput/ColorInput写入走既有 effect-update auto-keyframe 通道——因此在 Inspector 里调参数就是隐式打关键帧Fills形状/文本填充控件新增Shader填充类型 → shader 选择器 同样的参数控件组参数控件的control: slider | number字段如 liquid-metal 的 scale/speed/contrast 偏好数字框watercolor 的 bleed 偏好滑块直接驱动控件形态。七、关键决策Key Decisions与取舍依据设计文档记录了四条架构决策源码均已落实值得展开理解WebGL2 而非 WebGPUWebGL2 在所有主流浏览器普遍可用无需 fallback 分支且 scene3d 的 WebGL→bitmap→合成进 2D canvas 模式在预览与导出都已验证。选择 WebGL2 等于同时拿到兼容性与既有模式复用两条收益。单一共享 GL 上下文所有 shader pass 共用一个惰性创建的离屏上下文程序按 id 编译一次并缓存随 renderer 生命周期 dispose。这避免了每图层/每 effect 各建上下文的资源爆炸。参数用泛型Recordstring, numberbag 颜色子集关键帧/描述符系统对 shader 完全数据驱动新增参数零枚举成本。fragment 契约以文档化约定统一实现期补充#version 300 es、out vec4 fragColor、effect 必须u_input、fill 禁止u_input由 motion-shader-validator.ts 强制执行。八、风险清单与应对Risks设计文档列出的 5 项风险在源码中均有对应措施风险应对实现Async compile vs sync render首次使用即编译并按 id 缓存ensureProgram渲染全程同步编译失败走 fallback绝不 mid-render 抛异常GL 上下文丢失/生命周期webglcontextlost监听 清缓存dispose()完整回收任何异常被 try/catch 包住预览一致性强制usesRendererPreviewMotionShaderRenderer在MotionRenderer内部预览与导出共享一条路径参数系统重构 blast radius数据驱动 shader 分支与旧硬编码 effect 并行不悖10/23 修复改变既有行为均配回归测试FillcreatePattern成本pattern 每帧重渲染是显式接受的代价设计文档建议按 (shaderId, params, size) 做帧内缓存九、测试策略Testing设计文档的测试矩阵覆盖六层仓库中对应测试文件均已存在可直接对照阅读Shader 库单元测试每个MotionShaderDef参数默认值落在 min/max 内、id 唯一、GLSL 非空——见 shader-library.test.ts、registry.test.ts、paper-shaders.test.ts渲染器测试编译库内 shader 并渲染出非空白画布无 WebGL2 时安全回退mock GL——见 motion-shader-renderer.test.ts 与 uniform 上传专项 motion-shader-renderer.uniforms.test.ts契约校验测试#version 300 es/out vec4 fragColor/ fill 禁用u_input等契约——见 motion-shader-validator.test.tsEffect 集成测试添加MotionShaderEffect、在关键帧时间点解析effect.id.param得到插值结果10/23 回归测试断言 13 个参数可关键帧——见 motion-effects.shader.test.tsFill 集成测试shaderFillStyle经工厂与绘制守卫完整 round-trip——见 motion-shader-fill.test.ts、motion-shader-fill-keyframe.test.tsVisual/RTLEffectsPanel Shaders 分组添加 shader effect、参数编辑写回、数值参数出现在 graph-picker亮/暗主题下 Dither Liquid Metal 组合渲染与参数关键帧动画验证tsc eslint 干净。十、总结从设计文档到源码落地OpenReel Video 的可关键帧化着色器体系验证了一条清晰的路径以 WebGL2 fullscreen-quad 片元着色器为统一基座以 def 元数据为渲染与 UI 的单一事实来源以泛型参数 bag 支撑完全数据驱动的关键帧系统。任何新的效果或填充只需要写一段符合契约的 GLSLeffect 声明u_input、fill 不引用它、声明参数 def、放进MOTION_SHADER_LIBRARY即可自动获得 Inspector 控件、时间轴关键帧、预览与导出一致性以及无 WebGL2 时的安全降级——这正是设计文档 curated library of parameterized procedural shaders 目标的工程化实现。【免费下载链接】openreel-videoOpenReel Video - Professional browser-based video editor. Open source CapCut alternative. 100% browser-based, no installation, no cloud uploads, no watermarks.项目地址: https://gitcode.com/GitHub_Trending/op/openreel-video创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表