Cách Xây Dựng Studio Đồ Họa Chuyển Động 2.5D Với Opus 5.5

Chỉ với một prompt, một tệp HTML và một trình xuất video 25 dòng: xây dựng studio hoạt hình 2.5D ngay trên trình duyệt với Opus 5.5 điều khiển Claude Code, xuất ra MP4, và kiểm thử lệnh gọi API shot-list của Opus 5.5 bằng Apidog CLI.

Ahmed Waheed

Ahmed Waheed

12 tháng 11 2026

Cách Xây Dựng Studio Đồ Họa Chuyển Động 2.5D Với Opus 5.5

Apidog cho doanh nghiệp

Triển khai tại chỗ

SSO & RBAC

Tuân thủ SOC 2

Khám phá Apidog Enterprise

Bạn có thể xây dựng một motion studio 2.5D với Opus 5.5 chỉ trong một phiên Claude Code: một tệp HTML duy nhất xếp các lớp phẳng ở các độ sâu khác nhau, di chuyển camera keyframe xuyên qua chúng với easing, cùng một script Playwright 25 dòng chụp từng khung hình và đưa vào ffmpeg. Bản dựng của chúng tôi đã render một tệp MP4 720p dài 6 giây (180 khung hình) trong khoảng 20 giây trên laptop.

Hướng dẫn này sẽ giới thiệu các prompt, mã nguồn agent đã viết, các lỗi gặp phải, và cách kiểm tra các lệnh gọi API Opus 5.5 phía sau tính năng "mô tả một shot" bằng Apidog CLI.

Motion studio 2.5D là gì?

Motion studio 2.5D là công cụ hoạt hình đặt các lớp 2D phẳng (bầu trời, đồi, thành phố, thẻ tiêu đề, tán lá tiền cảnh) ở các độ sâu khác nhau trong không gian 3D, sau đó di chuyển camera ảo xuyên qua chúng. Các lớp gần camera trượt nhanh hơn các lớp ở xa, tạo hiệu ứng parallax và cảm giác chiều sâu mà không cần bất kỳ mô hình 3D nào.

Ba thành phần biến nó thành một studio thay vì một bản hoạt hình đơn lẻ:

  1. Đồ thị cảnh (scene graph): các lớp có độ sâu z, kích thước, vị trí và nội dung HTML.
  2. Timeline: keyframe cho camera (dolly, truck, orbit) và cho từng lớp (opacity, scale, vị trí), mỗi cái có một đường cong easing.
  3. Renderer xác định (deterministic): hàm renderAt(t) vẽ chính xác khung hình tại bất kỳ thời điểm t nào, để quá trình xuất không phụ thuộc vào tốc độ phát lại của máy.

Điểm thứ ba là điều hầu hết các hướng dẫn bỏ qua, và nó quyết định MP4 của bạn có bị giật hay không.

Cài đặt Claude Code với Opus 5.5

Opus 5.5 là mô hình nên bắt đầu với loại công việc này. Tổng quan về các mô hình của Anthropic mô tả nó là "dành cho các công việc coding agentic và công việc tri thức chạy lâu dài", với cửa sổ ngữ cảnh 1 triệu token, đầu ra tối đa 128K, và giá $4 / $20 cho mỗi triệu token đầu vào / đầu ra.

Ghim mô hình để việc thay đổi alias không làm đổi mô hình giữa chừng trong dự án:

claude --model claude-opus-5-5 --effort high

Theo tài liệu về effort, Opus 5.5 mặc định ở mức medium. Nâng lên high cho lần build đầu tiên, khi agent cần tính toán phép chiếu chính xác. Hạ xuống medium cho các chỉnh sửa nhỏ về keyframe và màu sắc. Nếu bạn muốn tìm hiểu về mô hình này trước, hãy đọc Claude Opus 5.5 là gì và cách prompt caching thay đổi chi phí của nó.

Prompt xây dựng studio

Một prompt cụ thể hiệu quả hơn năm prompt mơ hồ. Đây là đặc tả bản dựng của chúng tôi, viết thành một prompt duy nhất:

Build a 2.5D motion studio as one file, studio.html, with no dependencies.
- Scene: 6 layers (sky, moon, hills, city skyline, a title card "Ship it.",
  foreground foliage), each at its own z depth, using CSS perspective and
  preserve-3d.
- Camera: keyframes for x, y, z (dolly) and yaw (orbit around the card).
- Per-layer tracks: opacity, scale, y. Easings: linear, easeInOutCubic,
  easeInOutSine, easeOutBack.
- A timeline bar with keyframe markers, a playhead, click to scrub,
  space to play or pause.
- renderAt(t) must be pure in t. Expose window.__seek(t) that resolves after
  the frame paints, and a ?capture mode that hides the UI.
Then write export.mjs: Playwright opens ?capture, seeks every frame at 30 fps,
screenshots it and pipes PNGs to ffmpeg as H.264.

Hai dòng cuối quan trọng nhất: chúng biến việc xuất video thành một phần của thiết kế, chứ không phải phần bổ sung.

Mã nguồn agent đã viết

Cái studio chỉ khoảng 150 dòng. Ba phần quan trọng nhất:

Keyframe và easing

Mỗi track là một danh sách keyframe đã sắp xếp. sample() tìm hai key bao quanh t, áp dụng easing của key xuất phát, và nội suy mọi trường số:

function sample(keys, t) {
  if (t <= keys[0].t) return keys[0];
  for (let i = 0; i < keys.length - 1; i++) {
    const a = keys[i], b = keys[i + 1];
    if (t <= b.t) {
      const p = (easings[a.ease] || easings.linear)((t - a.t) / (b.t - a.t));
      const out = {};
      for (const k in b) if (typeof b[k] === 'number' && k !== 't') out[k] = a[k] + (b[k] - a[k]) * p;
      return out;
    }
  }
  return keys[keys.length - 1];
}

Mẹo tạo chiều sâu

Camera không bao giờ di chuyển. Thế giới di chuyển theo hướng ngược lại. Mỗi lớp nhận một translate theo trục z cộng với một hệ số scale bù trừ:

const PERSPECTIVE = 1200; // must match #viewport perspective
world.style.transform =
  `translate3d(${-cam.x}px, ${-cam.y}px, ${cam.z}px) rotateY(${-cam.yaw}deg)`;
const comp = 1 - L.z / PERSPECTIVE;
el.style.transform = `translate(-50%,-50%) ` +
  `translate3d(${x * comp}px, ${y * comp}px, ${L.z}px) scale(${comp * s})`;

Với perspective của CSS đặt là 1200px, một lớp ở z = -900 được render ở tỷ lệ 1200 / 2100, tức 57% kích thước của nó. Nhân scale và offset x/y của nó với 1 + 900/1200 = 1.75 sẽ triệt tiêu điều đó. Mọi lớp xuất hiện đúng kích thước và vị trí bạn đã thiết kế khi camera đứng yên, và vẫn di chuyển theo tốc độ riêng khi camera chuyển động. Đó chính là toàn bộ ảo thuật.

The 2.5D motion studio in the browser: a title card in front of a moon, hills and a city skyline, with a timeline showing camera keyframes in blue, layer keyframes in yellow and a red playhead at 2.10s

Lỗi đáng biết

Phiên bản đầu tiên scale từng lớp nhưng không scale offset, nên ngọn đồi trồi lên che hai phần ba bầu trời. Cách sửa: nhân cả x và y với comp. Chúng tôi phát hiện ra từ một khung hình render, chứ không phải từ việc đọc mã, vì vậy hãy yêu cầu agent của bạn render ảnh tĩnh ở đầu, giữa và cuối trước khi tin tưởng bất kỳ chuyển động camera nào.

Xuất timeline ra MP4

Phát lại theo thời gian thực dùng requestAnimationFrame, thứ sẽ bỏ sót khung hình khi máy bận. Xuất video thì không thể như vậy. Vì vậy exporter điều khiển thời gian theo chỉ số khung hình và đợi mỗi khung hình vẽ xong trước khi chụp ảnh bằng Playwright:

import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import { pathToFileURL } from 'node:url';

const out = process.argv[2] || 'out.mp4';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
await page.goto(pathToFileURL('studio.html').href + '?capture');

const { duration, fps } = await page.evaluate(() => window.__scene);
const ffmpeg = spawn('ffmpeg', ['-y', '-f', 'image2pipe', '-framerate', String(fps),
  '-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', out],
  { stdio: ['pipe', 'ignore', 'inherit'] });

const frames = Math.round(duration * fps);
for (let i = 0; i < frames; i++) {
  await page.evaluate(t => window.__seek(t), i / fps);
  ffmpeg.stdin.write(await page.screenshot({ type: 'png' }));
}
ffmpeg.stdin.end();
await new Promise(r => ffmpeg.on('close', r));
await browser.close();

window.__seek(t) chỉ resolve sau hai animation frame, để mỗi ảnh chụp thấy các transform mới. image2pipe (xem tài liệu formats của ffmpeg) đọc PNG từ stdin, và yuv420p giữ cho tệp phát được trong trình duyệt.

Kết quả chạy của chúng tôi: 180 khung hình, 1280x720, 30 fps, 6,0 giây, một tệp MP4 420 KB, trong 20 giây thời gian thực. Chạy npx playwright install chromium một lần nếu Playwright chưa có trình duyệt.

Three frames from the exported MP4 at 0.3s, 2.5s and 5.9s: the camera pushes in on the title card while the city skyline and foreground hill shift at different speeds, then orbits so the card tilts

Các tòa nhà trong thành phố dịch chuyển ít hơn ngọn đồi tối màu phía trước, thứ trượt ra khỏi khung hình hoàn toàn. Đó chính là parallax.

Thêm tính năng "mô tả một shot" với API Opus 5.5

Tiếp theo, cho phép người viết nhập mô tả shot và nhận lại keyframe. Studio gọi trực tiếp Messages API:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2026-10-21" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 4096,
    "output_config": { "effort": "low" },
    "system": "Return only JSON: {\"camera\": [{\"t\",\"x\",\"y\",\"z\",\"yaw\",\"ease\"}], \"tracks\": {}}. Times in seconds, 0 to 6.",
    "messages": [{ "role": "user", "content": "Slow push-in, slight orbit right, title fades up in the first second." }]
  }'

Hai chi tiết từ tài liệu về effort đáng lưu ý ở đây. output_config.effort là nơi đặt mức effort trong một request Messages API. Và Opus 5.5 luôn dùng adaptive thinking (một request với thinking: {"type": "disabled"} sẽ trả về lỗi 400), vì vậy các token thinking cũng tính vào max_tokens. Đặt quá thấp và JSON sẽ bị cắt giữa mảng với stop_reason: "max_tokens". Danh sách shot là một tác vụ nhỏ, có đặc tả rõ ràng, nên mức effort low là lựa chọn hợp lý ban đầu; hãy tự chạy so sánh trước khi chốt lựa chọn.

Cho Claude Code kiểm tra lệnh gọi API bằng Apidog CLI

Một phản hồi bị cắt hoặc dài dòng sẽ làm hỏng sample() lúc render, nên lệnh gọi này cần một bài kiểm thử hợp đồng (contract test). Cùng phiên Claude Code đó có thể viết và chạy nó với Apidog CLI.

Cài đặt và đăng nhập một lần:

npm install -g apidog-cli@latest
apidog auth login --with-token <YOUR_TOKEN>
apidog skill install

Sau đó đưa cho agent một prompt:

In Apidog project <projectId>, create an endpoint for POST /v1/messages and a
test case that sends the "describe a shot" request above with model
claude-opus-5-5. Assert: status 200, body.model is "claude-opus-5-5",
stop_reason is not "max_tokens", and content[0].text parses as JSON with a
non-empty camera array whose t values rise from 0 to 6. Use an environment
with base URL https://api.anthropic.com and the key in a variable. Run it and
summarize failures.

Agent đọc các schema payload thay vì đoán, xác thực các tệp của mình, rồi tạo và chạy tất cả:

apidog cli-schema get endpoint-create
apidog cli-schema get test-case-create
apidog cli-schema validate test-case-create --file case.json
apidog environment create anthropic --project <projectId> --base-url https://api.anthropic.com
apidog endpoint create --project <projectId> --file endpoint.json
apidog test-case create --project <projectId> --file case.json
apidog run --test-case <caseId> --project <projectId> -e <envId> -r cli,json

Truyền khóa tại thời điểm chạy với --env-var "api_key=$ANTHROPIC_API_KEY" để nó không bao giờ nằm trong dự án. Chạy lại test case khi bạn thay đổi system prompt hoặc mức effort, vì cả hai đều thay đổi kết quả trả về. Cùng lệnh apidog run với -r junit tích hợp vào CI; xem hướng dẫn Apidog CLI CI/CD và Apidog CLI trong Claude Code. Gói Free của Apidog hỗ trợ tối đa 4 người dùng.

Bước tiếp theo cho studio

Câu hỏi thường gặp

2.5D trong motion graphics nghĩa là gì? Nó nghĩa là artwork 2D phẳng được đặt ở các độ sâu khác nhau và được ghi lại bằng camera chuyển động. Bạn có parallax và các tín hiệu chiều sâu mà không cần mô hình 3D, ánh sáng hay engine 3D.

Tại sao dùng Opus 5.5 thay vì mô hình nhỏ hơn cho bản dựng này? Bản dựng đầu tiên kết hợp phép chiếu, engine keyframe và pipeline ghi khung hình trong một lần chạy. Anthropic định vị Opus 5.5 cho các công việc coding agentic chạy lâu dài, và phiên của chúng tôi đã tạo ra một studio hoạt động chỉ từ một prompt cộng một vòng sửa lỗi. Với các chỉnh sửa nhỏ sau đó, mức effort thấp hơn là đủ.

Tại sao không quay màn hình thay vì chụp từng khung hình? Quay màn hình tuân theo thời gian thực, nên bất kỳ khung hình chậm nào cũng trở thành khung hình bị bỏ sót hoặc nhân đôi. Seek đến i / fps và chụp từng khung hình giúp đầu ra giống hệt nhau trên mọi máy.

Có thể dùng tính năng "mô tả một shot" mà không cần khóa API không? Không. Tính năng này gọi Messages API, cần khóa API Anthropic của riêng bạn. Phần còn lại của studio và xuất MP4 chạy cục bộ mà không cần khóa.

Nên dùng mức effort nào cho lệnh gọi tạo danh sách shot? Bắt đầu với low, vì tác vụ ngắn và schema đầu ra cố định. Chỉ nâng lên nếu bài kiểm tra Apidog của bạn cho thấy keyframe sai định dạng hoặc lệch yêu cầu.

Thực hành thiết kế API trong Apidog

Khám phá cách dễ dàng hơn để xây dựng và sử dụng API