renderable type 선택: shape, sprite, mesh
2D renderer는 모든 것을 같은 방식으로 그리지 않습니다. rectangle, image, vector path, custom geometry를 어떤 renderable로 표현할지 먼저 정해야 합니다.
node를 renderable로 변환하는 코드
document node type과 renderer renderable type은 다를 수 있습니다.
function toRenderable(node, rendererState) {
if (node.type === "image") {
return { kind: "sprite", textureId: node.assetId, bounds: node.bounds };
}
if (node.type === "rect" && !node.effects?.length) {
return { kind: "shape", shape: "rect", fill: node.fill, bounds: node.bounds };
}
if (node.type === "vector" && rendererState.hasTessellation(node.id)) {
return { kind: "mesh", meshId: node.id, material: node.fill };
}
return { kind: "sprite", textureId: rendererState.getPreviewTexture(node.id), bounds: node.bounds };
}
세 가지 선택지
shape: 편집 가능한 도형, 단순 path
sprite: texture가 있는 image/preview
mesh: custom vertex와 shader가 필요한 node
초기 editor에서는 rectangle과 image를 구분하는 것만으로도 충분합니다. 하지만 vector, filter, text preview가 들어오면 renderable type 정책이 필요해집니다.
renderable을 batch key로 묶는다
function batchKeyOf(renderable) {
if (renderable.kind === "shape") {
return `shape:${renderable.shape}:${renderable.fill}`;
}
if (renderable.kind === "sprite") {
return `sprite:${renderable.textureId}`;
}
return `mesh:${renderable.material}:${renderable.meshId}`;
}
function buildBatches(renderables) {
const batches = new Map();
for (const renderable of renderables) {
const key = batchKeyOf(renderable);
if (!batches.has(key)) batches.set(key, []);
batches.get(key).push(renderable);
}
return [...batches.entries()].map(([key, items]) => ({ key, items }));
}
renderable type을 나누는 이유는 그리기 방식이 다르기 때문입니다. 이 구분이 있어야 shape batch, sprite batch, mesh draw path를 명확히 나눌 수 있습니다.
오늘의 핵심
문서 모델의 node type과 renderer의 renderable type은 1:1이 아닙니다. renderer는 비용과 품질 기준으로 표현 방식을 선택합니다.