콘텐츠로 이동

이펙트와 컴포지션

이펙트는 그림을 바꾸고, 컴포지션은 어떤 이펙트를 무엇에 어떤 순서로 적용할지 정하는 그래프입니다. 이 페이지에서는 둘을 모두 설명하고, 직접 만든 이펙트를 등록하는 방법도 다룹니다. 다 읽고 나면 클립의 그림을 보정하고, 스타일을 입히고, 레이어로 쌓을 수 있습니다.

이펙트를 쓸 때마다 ClipEffect 하나를 씁니다.

{
"id": "alpha2.effect.vignette",
"version": 1,
"params": { "amount": { "mode": "constant", "value": { "type": "float", "value": 0.6 } } }
}
  • **id**와 **version**은 정확한 정의 하나를 가리킵니다. 내장 이펙트는 alpha2.effect.*, 버전 1입니다. 전체 목록과 파라미터는 이펙트 레퍼런스에 있습니다.
  • **params**는 이름이 있는 애니메이션 가능한 값입니다. 값마다 타입이 있습니다. float, vec2, vec4, color(0..1 범위의 straight sRGB RGBA), enum(정의에 있는 선택지의 인덱스), bool입니다. 생략한 파라미터는 정의의 기본값을 씁니다.
  • **resources**는 정의에 있는 리소스 이름을 프로젝트의 effect_resources에 대응시킵니다. 예를 들어 alpha2.effect.lut-3d에 .cube LUT를 연결합니다.

직접 해 보세요. 아래 프리뷰는 사진에 alpha2.effect.rgb-split을 적용합니다.

익스플로러에서 내장 이펙트를 골라 파라미터를 움직여 볼 수 있습니다.

클립은 이펙트를 직접 나열하지 않습니다. 클립은 컴포지션을 보여 줍니다. 컴포지션은 output이 하나인, 순환 없는 노드 그래프입니다. 각 노드에는 id와 op가 있으며, 노드는 서로를 id로 참조하므로 노드 순서에는 의미가 없습니다.

op.type 만드는 것
clip 컴포지션을 보여 주는 클립의 그림. 시간 대응을 거친 소스 시간의 소스 그림, 또는 제너레이터 소스가 그린 그림입니다.
media 다른 시각적 소스(비디오, 이미지, 제너레이터). start에 컴포지션 시간을 더한 시각을 읽습니다. 소리는 더하지 않습니다.
generator 엔진이 그리는 그림. 채우기, 셰이프, 텍스트, 레이아웃입니다.
effect input에 적용하는 등록된 이펙트. 추가로 샘플링하는 이름 있는 inputs를 받을 수 있습니다.
transition from과 to 사이의 등록된 트랜지션. 애니메이션할 수 있는 progress를 명시합니다.
transform 위치, 앵커, 크기, 회전, 불투명도로 배치한 input.
merge 아래에서 위로 합성하는 layers. 레이어마다 blend_mode와 opacity가 있으며 최대 32개입니다.
matte 두 번째 이미지의 알파나 휘도가 가리키는 곳만 남긴 input.
shape_mask 셰이프 안쪽만 남긴 input.
group 다른 컴포지션을 노드 하나로 그린 것. inputs로 이미지를 넣고 controls로 값을 정합니다.
group_input 그룹으로 쓰이는 컴포지션 안에서, 그룹 노드가 index번 입력으로 넘겨 준 이미지.

입력이 있는 노드는 "bypass": true로 입력을 그대로 통과시킬 수 있습니다. 에디터에서 이펙트를 끄고 켤 때 이 방법을 씁니다.

이펙트를 이으려면 각 노드의 input이 바로 앞 노드를 가리키게 하면 됩니다. 첫 렌더링에서 이 방식으로 color-controls를 vignette에 이었습니다.

이 컴포지션은 클립의 그림을 흑백으로 바꾸고 가장자리를 어둡게 한 뒤, 그 위에 두 번째 사진을 PIP(picture-in-picture)로 올립니다.

{
"id": "inset",
"nodes": [
{ "id": "main", "op": { "type": "clip" } },
{
"id": "mono",
"op": {
"type": "effect",
"input": "main",
"effect": { "id": "alpha2.effect.black-and-white", "version": 1 }
}
},
{
"id": "darker",
"op": {
"type": "effect",
"input": "mono",
"effect": {
"id": "alpha2.effect.vignette",
"version": 1,
"params": { "amount": { "mode": "constant", "value": { "type": "float", "value": 0.7 } } }
}
}
},
{ "id": "other", "op": { "type": "media", "source_id": "forest" } },
{
"id": "placed",
"op": {
"type": "transform",
"input": "other",
"transform": {
"position": { "mode": "constant", "value": { "x": 0.75, "y": 0.3 } },
"scale": { "mode": "constant", "value": { "x": 0.35, "y": 0.35 } }
}
}
},
{
"id": "stack",
"op": { "type": "merge", "layers": [{ "input": "darker" }, { "input": "placed" }] }
}
],
"output": "stack"
}

media 노드가 forest 소스를 읽고, transform이 이를 35%로 줄여 오른쪽 위로 옮기며, merge가 보정된 그림을 그 아래에 깝니다. merge 노드가 output입니다.

group 노드를 쓰면 컴포지션을 다른 컴포지션 안에서 다시 쓸 수 있습니다. 안쪽 컴포지션은 inputs(group_input 노드가 읽는 이미지 포트)와 controls(그룹 노드가 정할 수 있는 이름 있는 이펙트 파라미터. 각각 node와 param을 가리킴)를 선언합니다. 그러면 그룹 노드가 포트에 이미지를, 컨트롤에 값을 넣어 줍니다. 컴포지션은 최대 64단계까지 중첩할 수 있습니다.

"composition": { "id": "look", "offset": 0, "scale": { "num": 1, "den": 1 } }

offset과 scale은 클립의 시간을 컴포지션의 시간, 즉 애니메이션 설정이 쓰는 시계에 대응시킵니다. 둘 다 위의 값이 기본값입니다. 컴포지션 시간을 참고하세요.

컴포지션은 그 자체로 소스("kind": "composition")가 될 수도 있습니다. 다른 소스처럼 클립이 보여 주며, 소스 시간을 자신의 시간으로 삼아 그려집니다.

내장 카탈로그 외에도 WGSL로 이펙트를 정의해 런타임에 등록할 수 있습니다. 정의(alpha2/effects의 EffectDefinition)에는 id, version, kind("effect" 또는 "transition"), params, 선택 항목인 추가 inputs와 resources, 그리고 엔진의 이펙트 셰이더에 삽입되는 WGSL인 wgslBody가 들어갑니다. 이펙트 본문은 fn effect(uv: vec2f, src: vec4f) -> vec4f를 정의하고, 파라미터는 params.<name>으로 읽으며, 추가 입력 <name>은 sample_<name>(uv)로 샘플링합니다.

const tint = {
id: "example.warm",
version: 1,
kind: "effect",
params: [{ name: "amount", type: "float", default: 0.5, min: 0, max: 1, unit: "normalized" }],
wgslBody:
"fn effect(uv: vec2f, src: vec4f) -> vec4f { " +
"let warm = vec3f(src.r, src.g * 0.9, src.b * 0.7); " +
"return vec4f(mix(src.rgb, warm, params.amount), src.a); }",
};
// 브라우저에서, 렌더링에 쓰는 엔진에 등록합니다.
const key = engine.registerCompiledEffect(await engine.compileEffect(JSON.stringify(tint)));
// key === "example.warm@1". 이제 프로젝트에서 { "id": "example.warm", "version": 1 }로 씁니다.

본문이 컴파일되지 않으면 compileEffect가 셰이더 진단 정보와 함께 EFFECT_ERROR로 거부합니다. alpha2/effects의 RuntimeEffectRegistry는 같은 호출을 타입으로 감싼 래퍼입니다. 네이티브에서는 export할 때 정의를 넘깁니다. @alpha2-sh/node와 Python에서는 effects, 명령줄에서는 --effects effects.json입니다.

프로젝트를 불러올 때는 문서를 검사합니다. 참조, 구간, 키프레임 순서 등입니다. 이펙트 id나 파라미터 값은 검사하지 않습니다. 어떤 이펙트가 있는지는 레지스트리가 정하기 때문입니다. 렌더러에 등록되지 않은 이펙트를 그리거나 정의의 범위를 벗어난 파라미터 값을 쓰는 프레임은 렌더링할 때 EFFECT_ERROR로 실패합니다.