add-effect
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdd a new @remotion/effects
effect
@remotion/effects为@remotion/effects
添加新特效
@remotion/effectsUse this skill when adding a new effect to .
@remotion/effects当你需要为添加新特效时,请遵循此流程。
@remotion/effects1. Pick the effect shape
1. 确定特效结构
- Prefer the WebGL2 backend for new effects. Use 2D only when WebGL cannot express the effect.
- Use a single file at for simple effects.
packages/effects/src/<effect-name>.ts - Use a folder at plus a top-level re-export file when the effect needs multiple shaders, runtime helpers, or multiple files.
packages/effects/src/<effect-name>/ - Follow naming already used by the package:
- File/subpath: kebab-case ()
chromatic-aberration - Function: camelCase ()
chromaticAberration - Type: PascalCase params ()
ChromaticAberrationParams - Effect type string:
remotion/<kebab-case-name>
- File/subpath: kebab-case (
- 新特效优先使用WebGL2后端。仅当WebGL无法实现该特效时,才使用2D方案。
- 简单特效直接在创建单个文件。
packages/effects/src/<effect-name>.ts - 若特效需要多个着色器、运行时工具或多文件支持,则在创建文件夹,并添加一个顶级重导出文件。
packages/effects/src/<effect-name>/ - 遵循包内已有的命名规范:
- 文件/子路径:短横线分隔命名(kebab-case),例如
chromatic-aberration - 函数:小驼峰命名(camelCase),例如
chromaticAberration - 类型:大驼峰命名参数(PascalCase),例如
ChromaticAberrationParams - 特效类型字符串:
remotion/<短横线分隔名称>
- 文件/子路径:短横线分隔命名(kebab-case),例如
2. Implement the effect
2. 实现特效
In the effect file:
- Import and
SequenceSchemafromInternals.remotion - Use .
const {createEffect, createWebGL2ContextError} = Internals; - Define defaults as values.
const - Define a schema with ; these fields appear in Studio visual editing.
satisfies SequenceSchema - Export the params type.
- Resolve defaults in a helper.
resolve() - Validate params using helpers from:
packages/effects/src/validate-effect-param.tspackages/effects/src/color-utils.ts
- Throw if WebGL2 cannot be acquired.
createWebGL2ContextError('<effect name> effect') - Set to
documentationLink.https://www.remotion.dev/docs/effects/<slug> - Include every resolved parameter in .
calculateKey()
For WebGL2 effects, use this general structure:
ts
import type {SequenceSchema} from 'remotion';
import {Internals} from 'remotion';
import {assertOptionalFiniteNumber, validateUnitInterval} from './color-utils.js';
import {assertEffectParamsObject} from './validate-effect-param.js';
const {createEffect, createWebGL2ContextError} = Internals;
const DEFAULT_AMOUNT = 1 as const;
const myEffectSchema = {
amount: {
type: 'number',
min: 0,
max: 1,
step: 0.01,
default: DEFAULT_AMOUNT,
description: 'Amount',
},
} as const satisfies SequenceSchema;
export type MyEffectParams = {
readonly amount?: number;
};
type MyEffectResolved = {
amount: number;
};
const resolve = (p: MyEffectParams): MyEffectResolved => ({
amount: p.amount ?? DEFAULT_AMOUNT,
});
const validateMyEffectParams = (params: MyEffectParams): void => {
assertEffectParamsObject(params, 'My effect');
assertOptionalFiniteNumber(params.amount, 'amount');
validateUnitInterval(params.amount ?? DEFAULT_AMOUNT, 'amount');
};
type MyEffectState = {
readonly gl: WebGL2RenderingContext;
readonly program: WebGLProgram;
readonly vao: WebGLVertexArrayObject;
readonly vbo: WebGLBuffer;
readonly texture: WebGLTexture;
readonly uSource: WebGLUniformLocation | null;
readonly uAmount: WebGLUniformLocation | null;
};
const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;
void main() {
vUv = aUv;
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const FRAGMENT_SHADER = /* glsl */ `#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D uSource;
uniform float uAmount;
void main() {
vec4 color = texture(uSource, vUv);
fragColor = vec4(color.rgb * uAmount, color.a);
}
`;
// Follow existing helpers in halftone.ts or a runtime file for shader
// compilation, program linking, fullscreen-quad setup, and texture setup.
export const myEffect = createEffect<MyEffectParams, MyEffectState>({
type: 'remotion/my-effect',
label: 'My Effect',
documentationLink: 'https://www.remotion.dev/docs/effects/my-effect',
backend: 'webgl2',
calculateKey: (params) => {
const r = resolve(params);
return `my-effect-${r.amount}`;
},
setup: (target) => {
const gl = target.getContext('webgl2', {
premultipliedAlpha: true,
alpha: true,
preserveDrawingBuffer: true,
});
if (!gl) {
throw createWebGL2ContextError('my effect effect');
}
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
return createMyEffectState(gl, VERTEX_SHADER, FRAGMENT_SHADER);
},
apply: ({source, width, height, params, state, flipSourceY}) => {
const r = resolve(params);
state.gl.viewport(0, 0, width, height);
state.gl.bindFramebuffer(state.gl.FRAMEBUFFER, null);
state.gl.activeTexture(state.gl.TEXTURE0);
state.gl.bindTexture(state.gl.TEXTURE_2D, state.texture);
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
state.gl.texImage2D(
state.gl.TEXTURE_2D,
0,
state.gl.RGBA,
state.gl.RGBA,
state.gl.UNSIGNED_BYTE,
source as TexImageSource,
);
state.gl.useProgram(state.program);
if (state.uSource) state.gl.uniform1i(state.uSource, 0);
if (state.uAmount) state.gl.uniform1f(state.uAmount, r.amount);
state.gl.bindVertexArray(state.vao);
state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, 4);
},
cleanup: ({gl, program, vao, vbo, texture}) => {
gl.deleteTexture(texture);
gl.deleteBuffer(vbo);
gl.deleteProgram(program);
gl.deleteVertexArray(vao);
},
schema: myEffectSchema,
validateParams: validateMyEffectParams,
});Look at existing WebGL2 effects such as ,
, ,
and before adding new helpers. In the template above,
stands for the shader compilation, program linking,
fullscreen-quad, texture, and uniform-location setup used by those files.
halftone.tsblur/blur-runtime.tschromatic-aberration/chromatic-aberration-runtime.tswave/wave-runtime.tscreateMyEffectState()在特效文件中:
- 从导入
remotion和SequenceSchema。Internals - 使用解构方法。
const {createEffect, createWebGL2ContextError} = Internals; - 将默认值定义为常量。
const - 使用定义Schema;这些字段会在Studio可视化编辑中展示。
satisfies SequenceSchema - 导出参数类型。
- 在工具函数中处理默认值。
resolve() - 使用以下工具函数验证参数:
packages/effects/src/validate-effect-param.tspackages/effects/src/color-utils.ts
- 若无法获取WebGL2上下文,抛出错误。
createWebGL2ContextError('<effect name> effect') - 将设置为
documentationLink。https://www.remotion.dev/docs/effects/<slug> - 在中包含所有已处理的参数。
calculateKey()
WebGL2特效可遵循以下通用结构:
ts
import type {SequenceSchema} from 'remotion';
import {Internals} from 'remotion';
import {assertOptionalFiniteNumber, validateUnitInterval} from './color-utils.js';
import {assertEffectParamsObject} from './validate-effect-param.js';
const {createEffect, createWebGL2ContextError} = Internals;
const DEFAULT_AMOUNT = 1 as const;
const myEffectSchema = {
amount: {
type: 'number',
min: 0,
max: 1,
step: 0.01,
default: DEFAULT_AMOUNT,
description: 'Amount',
},
} as const satisfies SequenceSchema;
export type MyEffectParams = {
readonly amount?: number;
};
type MyEffectResolved = {
amount: number;
};
const resolve = (p: MyEffectParams): MyEffectResolved => ({
amount: p.amount ?? DEFAULT_AMOUNT,
});
const validateMyEffectParams = (params: MyEffectParams): void => {
assertEffectParamsObject(params, 'My effect');
assertOptionalFiniteNumber(params.amount, 'amount');
validateUnitInterval(params.amount ?? DEFAULT_AMOUNT, 'amount');
};
type MyEffectState = {
readonly gl: WebGL2RenderingContext;
readonly program: WebGLProgram;
readonly vao: WebGLVertexArrayObject;
readonly vbo: WebGLBuffer;
readonly texture: WebGLTexture;
readonly uSource: WebGLUniformLocation | null;
readonly uAmount: WebGLUniformLocation | null;
};
const VERTEX_SHADER = /* glsl */ `#version 300 es
in vec2 aPos;
in vec2 aUv;
out vec2 vUv;
void main() {
vUv = aUv;
gl_Position = vec4(aPos, 0.0, 1.0);
}
`;
const FRAGMENT_SHADER = /* glsl */ `#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
uniform sampler2D uSource;
uniform float uAmount;
void main() {
vec4 color = texture(uSource, vUv);
fragColor = vec4(color.rgb * uAmount, color.a);
}
`;
// Follow existing helpers in halftone.ts or a runtime file for shader
// compilation, program linking, fullscreen-quad setup, and texture setup.
export const myEffect = createEffect<MyEffectParams, MyEffectState>({
type: 'remotion/my-effect',
label: 'My Effect',
documentationLink: 'https://www.remotion.dev/docs/effects/my-effect',
backend: 'webgl2',
calculateKey: (params) => {
const r = resolve(params);
return `my-effect-${r.amount}`;
},
setup: (target) => {
const gl = target.getContext('webgl2', {
premultipliedAlpha: true,
alpha: true,
preserveDrawingBuffer: true,
});
if (!gl) {
throw createWebGL2ContextError('my effect effect');
}
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
return createMyEffectState(gl, VERTEX_SHADER, FRAGMENT_SHADER);
},
apply: ({source, width, height, params, state, flipSourceY}) => {
const r = resolve(params);
state.gl.viewport(0, 0, width, height);
state.gl.bindFramebuffer(state.gl.FRAMEBUFFER, null);
state.gl.activeTexture(state.gl.TEXTURE0);
state.gl.bindTexture(state.gl.TEXTURE_2D, state.texture);
state.gl.pixelStorei(state.gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);
state.gl.texImage2D(
state.gl.TEXTURE_2D,
0,
state.gl.RGBA,
state.gl.RGBA,
state.gl.UNSIGNED_BYTE,
source as TexImageSource,
);
state.gl.useProgram(state.program);
if (state.uSource) state.gl.uniform1i(state.uSource, 0);
if (state.uAmount) state.gl.uniform1f(state.uAmount, r.amount);
state.gl.bindVertexArray(state.vao);
state.gl.drawArrays(state.gl.TRIANGLE_STRIP, 0, 4);
},
cleanup: ({gl, program, vao, vbo, texture}) => {
gl.deleteTexture(texture);
gl.deleteBuffer(vbo);
gl.deleteProgram(program);
gl.deleteVertexArray(vao);
},
schema: myEffectSchema,
validateParams: validateMyEffectParams,
});在添加新工具函数前,可参考已有的WebGL2特效实现,例如、、和。在上述模板中,代表这些文件中用到的着色器编译、程序链接、全屏四边形、纹理以及统一变量位置的初始化逻辑。
halftone.tsblur/blur-runtime.tschromatic-aberration/chromatic-aberration-runtime.tswave/wave-runtime.tscreateMyEffectState()3. Register package entry points
3. 注册包入口
Update:
- — add the new
packages/effects/bundle.tsentrypoint.src/<effect-name>.ts - :
packages/effects/package.json- Add .
exports["./<effect-name>"] - Add the entry.
typesVersions
- Add
If using a folder implementation, add a top-level file that re-exports from the folder:
ts
export {myEffect, type MyEffectParams} from './my-effect/index.js';更新以下文件:
- — 添加新的
packages/effects/bundle.ts入口。src/<effect-name>.ts - :
packages/effects/package.json- 添加配置。
exports["./<effect-name>"] - 添加条目。
typesVersions
- 添加
如果使用文件夹结构实现,添加一个顶级文件来重导出文件夹内的内容:
ts
export {myEffect, type MyEffectParams} from './my-effect/index.js';4. Add tests
4. 添加测试
Update :
packages/effects/src/test/effect-params.test.ts- Import the new effect.
- Add it to the documentation link test.
- Test default params when all fields are optional.
- Test required params if any are required.
- Test invalid values and exact error substrings.
- Test that meaningful params produce distinct values.
effectKey
Run:
bash
cd packages/effects
bun test src/test
bunx turbo make --filter="@remotion/effects"更新:
packages/effects/src/test/effect-params.test.ts- 导入新特效。
- 将其添加到文档链接测试中。
- 测试所有字段为可选时的默认参数。
- 若存在必填参数,测试必填参数逻辑。
- 测试无效值及精确的错误子串。
- 测试不同有效参数能否生成不同的值。
effectKey
运行:
bash
cd packages/effects
bun test src/test
bunx turbo make --filter="@remotion/effects"5. Add docs
5. 添加文档
Create .
packages/docs/docs/effects/<effect-name>.mdxFollow existing effect pages:
- Frontmatter: ,
slug,title,sidebar_label.crumb: '@remotion/effects' - Add only after running
image:.bun render-cards.ts - H1: .
# effectName()<AvailableFrom v="..." /> - Include .
_Part of the [@remotion/effects](/docs/effects/api) package._ - Add a short description.
- Add .
<Demo type="effects-<effect-name>" /> - Add a twoslash example with .
title="MyComp.tsx" - Document each option as its own heading, using
###for optional parameters.? - Add a section.
disabled? - Add a See also section.
Update:
- — add
packages/docs/sidebars.ts.'effects/<effect-name>' - — add a card in the right category.
packages/docs/docs/effects/table-of-contents.tsx - by running the card generator, not by hand.
packages/docs/src/data/articles.ts
Use the skill for documentation wording.
writing-docs创建文件。
packages/docs/docs/effects/<effect-name>.mdx参考已有特效页面的结构:
- 前置元数据:、
slug、title、sidebar_label。crumb: '@remotion/effects' - 仅在运行后添加
bun render-cards.ts字段。image: - 一级标题:。
# effectName()<AvailableFrom v="..." /> - 添加说明。
_Part of the [@remotion/effects](/docs/effects/api) package._ - 添加简短描述。
- 添加组件。
<Demo type="effects-<effect-name>" /> - 添加带有的twoslash示例。
title="MyComp.tsx" - 将每个选项作为独立的标题进行文档说明,可选参数标注
###。? - 添加章节。
disabled? - 添加“另请参阅”章节。
更新以下文件:
- — 添加
packages/docs/sidebars.ts到侧边栏。'effects/<effect-name>' - — 在对应分类下添加卡片。
packages/docs/docs/effects/table-of-contents.tsx - 通过运行卡片生成器更新,请勿手动修改。
packages/docs/src/data/articles.ts
文档措辞可参考技能规范。
writing-docs6. Add the interactive docs demo
6. 添加交互式文档演示
Create .
packages/docs/components/effects/effects-<effect-name>-preview.tsxUse the same preview source as other effects:
tsx
import {myEffect} from '@remotion/effects/my-effect';
import React from 'react';
import {CanvasImage} from 'remotion';
import {EFFECTS_PREVIEW_IMAGE_SRC} from './effects-preview-image';
export const EffectsMyEffectPreview: React.FC<{
readonly amount: number;
}> = ({amount}) => {
return (
<CanvasImage
src={EFFECTS_PREVIEW_IMAGE_SRC}
width={1280}
height={720}
fit="cover"
effects={[myEffect({amount})]}
/>
);
};Use for docs effect previews so the shared preview image fills
the 16:9 canvas and does not leave transparent bars.
fit="cover"Register the demo:
packages/docs/components/demos/types.ts- Import the preview component.
- Export .
effectsMyEffectDemo - Use .
id: 'effects-<effect-name>' - Add controls matching the effect schema.
packages/docs/components/demos/index.tsx- Import and add the demo to the array.
demos
- Import and add the demo to the
Use the skill for demo details.
docs-demo创建文件。
packages/docs/components/effects/effects-<effect-name>-preview.tsx参考其他特效的预览代码结构:
tsx
import {myEffect} from '@remotion/effects/my-effect';
import React from 'react';
import {CanvasImage} from 'remotion';
import {EFFECTS_PREVIEW_IMAGE_SRC} from './effects-preview-image';
export const EffectsMyEffectPreview: React.FC<{
readonly amount: number;
}> = ({amount}) => {
return (
<CanvasImage
src={EFFECTS_PREVIEW_IMAGE_SRC}
width={1280}
height={720}
fit="cover"
effects={[myEffect({amount})]}
/>
);
};文档特效预览请使用,确保共享预览图填满16:9画布,避免出现透明边栏。
fit="cover"注册演示组件:
- :
packages/docs/components/demos/types.ts- 导入预览组件。
- 导出。
effectsMyEffectDemo - 设置。
id: 'effects-<effect-name>' - 添加与特效Schema匹配的控件。
- :
packages/docs/components/demos/index.tsx- 导入并将演示添加到数组中。
demos
- 导入并将演示添加到
演示细节可参考技能规范。
docs-demo7. Render the table-of-contents preview image
7. 渲染目录预览图
The TOC card should use a rendered image from the same preview component, not a hand-written SVG.
Create a temporary Remotion entry point for the still render and delete it before committing:
tsx
import React from 'react';
import {Composition, registerRoot} from 'remotion';
import {EffectsMyEffectPreview} from '../../components/effects/effects-my-effect-preview';
const Root: React.FC = () => {
return (
<Composition
id="effects-my-effect-preview"
component={EffectsMyEffectPreview}
width={1080}
height={720}
fps={30}
durationInFrames={1}
defaultProps={{
amount: 1,
}}
/>
);
};
registerRoot(Root);Then render from :
packages/docsbash
npx --no-install --package @remotion/cli remotion still src/remotion/effects-preview-entry.tsx effects-my-effect-preview static/img/effects-my-effect-preview.jpg --overwrite --image-format=jpegAdd the rendered image to , reference it from , and delete the temporary entry point before committing.
packages/docs/static/img/table-of-contents.tsx目录卡片应使用预览组件渲染出的图片,而非手写SVG。
创建一个临时Remotion入口点用于渲染静态图,提交前删除该文件:
tsx
import React from 'react';
import {Composition, registerRoot} from 'remotion';
import {EffectsMyEffectPreview} from '../../components/effects/effects-my-effect-preview';
const Root: React.FC = () => {
return (
<Composition
id="effects-my-effect-preview"
component={EffectsMyEffectPreview}
width={1080}
height={720}
fps={30}
durationInFrames={1}
defaultProps={{
amount: 1,
}}
/>
);
};
registerRoot(Root);然后在目录下运行渲染命令:
packages/docsbash
npx --no-install --package @remotion/cli remotion still src/remotion/effects-preview-entry.tsx effects-my-effect-preview static/img/effects-my-effect-preview.jpg --overwrite --image-format=jpeg将渲染后的图片添加到,在中引用该图片,并在提交前删除临时入口点。
packages/docs/static/img/table-of-contents.tsx8. Generate docs card
8. 生成文档卡片
Run:
bash
cd packages/docs
bun render-cards.tsCommit the generated and the new frontmatter line.
packages/docs/static/generated/articles-docs-effects-<effect-name>.pngimage:If opportunistically generates unrelated missing cards, remove those unrelated files unless they belong to the current change.
render-cards.ts运行:
bash
cd packages/docs
bun render-cards.ts提交生成的文件及新增的前置元数据行。
packages/docs/static/generated/articles-docs-effects-<effect-name>.pngimage:如果顺带生成了无关的缺失卡片,除非这些卡片属于当前变更内容,否则请删除这些无关文件。
render-cards.ts9. Format, build, and verify
9. 格式化、构建与验证
Run:
bash
cd packages/effects
bunx oxfmt src --write
cd ../..
bun run build
bun run formattingIf the change touches docs source, covers . For MDX-only edits, do not run formatters on docs pages.
bun run formattingpackages/docs/srcBefore committing, check:
bash
git diff --check
git status --short运行:
bash
cd packages/effects
bunx oxfmt src --write
cd ../..
bun run build
bun run formatting若变更涉及文档源码,会处理目录。仅编辑MDX文件时,请勿对文档页面运行格式化工具。
bun run formattingpackages/docs/src提交前,请检查:
bash
git diff --check
git status --shortCommon pitfalls
常见陷阱
- Do not forget
package.jsonandexports; subpath imports liketypesVersionsdepend on them.@remotion/effects/my-effect - Do not forget ; otherwise the ESM subpath will not be built.
bundle.ts - Do not leave temporary render entry points in .
packages/docs/src/remotion - Do not use a hand-written SVG for the effect TOC preview.
- Preserve alpha unless the effect intentionally changes it.
- For pixel math, be aware canvases store premultiplied alpha.
- WebGL color math often needs to unpremultiply the sampled RGB before luminance or threshold calculations, then premultiply the output RGB again.
- 不要忘记配置中的
package.json和exports;像typesVersions这样的子路径导入依赖这些配置。@remotion/effects/my-effect - 不要忘记更新;否则ESM子路径将无法构建。
bundle.ts - 不要在目录下遗留临时渲染入口点。
packages/docs/src/remotion - 不要为特效目录预览使用手写SVG。
- 除非特效有意修改,否则请保留透明度通道。
- 进行像素计算时,注意画布存储的是预乘透明度(premultiplied alpha)。
- WebGL颜色计算通常需要在亮度或阈值计算前对采样的RGB值进行去预乘处理,然后再对输出的RGB值进行预乘处理。