วิธีสร้างสตูดิโอโมชัน 2.5D ด้วย Opus 5.5

พรอมต์เดียว ไฟล์ HTML เดียว สคริปต์ exporter เพียง 25 บรรทัด: สร้างสตูดิโอมูชัน 2.5D บนเบราว์เซอร์ด้วย Opus 5.5 ที่ขับเคลื่อน Claude Code เรนเดอร์ออกมาเป็น MP4 แล้วทดสอบการเรียก shot-list API ของ Opus 5.5 ด้วย Apidog CLI

Ahmed Waheed

Ahmed Waheed

12 November 2026

วิธีสร้างสตูดิโอโมชัน 2.5D ด้วย Opus 5.5

Apidog สำหรับองค์กร

การติดตั้งแบบ On-Premises

SSO & RBAC

รองรับมาตรฐาน SOC 2

สำรวจ Apidog Enterprise

คุณสร้างสตูดิโอมูชัน 2.5D ด้วย Opus 5.5 ได้ภายใน Claude Code เซสชันเดียว: ไฟล์ HTML ไฟล์เดียวที่ซ้อนเลเยอร์ 2 มิติไว้ในความลึกที่ต่างกัน เคลื่อนกล้องที่กำหนดคีย์เฟรมไว้ผ่านเลเยอร์เหล่านั้นด้วย easing และสคริปต์ Playwright 25 บรรทัดที่จับทุกเฟรมแล้วส่งต่อไปยัง ffmpeg งานสร้างของเราเรนเดอร์ MP4 ขนาด 720p ความยาว 6 วินาที (180 เฟรม) ในเวลาราว 20 วินาทีบนแล็ปท็อป

คู่มือนี้จะแสดงพรอมป์ที่ใช้ โค้ดที่เอเจนต์เขียน บั๊กที่เจอ และวิธีตรวจสอบ API call ของ Opus 5.5 ที่อยู่เบื้องหลังฟีเจอร์ “อธิบายช็อต” ด้วย Apidog CLI

สตูดิโอมูชัน 2.5D คืออะไร?

สตูดิโอมูชัน 2.5D คือเครื่องมือแอนิเมชันที่วางเลเยอร์ 2D แบบแบน (ท้องฟ้า เนินเขา เมือง การ์ดชื่อเรื่อง ใบไม้ด้านหน้า) ไว้ในความลึกที่ต่างกันในพื้นที่ 3 มิติ แล้วเคลื่อนกล้องเสมือนผ่านเลเยอร์เหล่านั้น เลเยอร์ที่อยู่ใกล้กล้องจะเลื่อนเร็วกว่าเลเยอร์ที่อยู่ไกล ซึ่งสร้างภาพพารัลแลกซ์และความรู้สึกของความลึกโดยไม่ต้องใช้โมเดล 3 มิติเลย

สามส่วนที่ทำให้มันเป็น “สตูดิโอ” ไม่ใช่แอนิเมชันแบบใช้ครั้งเดียวทิ้ง:

  1. Scene graph: เลเยอร์ที่มีความลึก z, ขนาด, ตำแหน่ง และเนื้อหา HTML
  2. Timeline: คีย์เฟรมสำหรับกล้อง (dolly, truck, orbit) และสำหรับแต่ละเลเยอร์ (opacity, scale, position) พร้อมเส้นโค้ง easing ประกอบ
  3. Renderer แบบ deterministic: ฟังก์ชัน renderAt(t) ที่วาดเฟรมที่แน่นอนสำหรับเวลา t ใดก็ได้ ทำให้การส่งออกไม่ขึ้นอยู่กับความเร็วในการเล่นของเครื่อง

ข้อสามเป็นสิ่งที่บทเรียนส่วนใหญ่มักข้าม และมันคือตัวชี้ว่าว่า MP4 ของคุณจะกระตุกหรือไม่

ตั้งค่า Claude Code บน Opus 5.5

Opus 5.5 เป็นโมเดลที่ควรเริ่มต้นสำหรับงานลักษณะนี้ ภาพรวมโมเดล ของ Anthropic อธิบายว่าเหมาะ “สำหรับงานเขียนโค้ดแบบ agentic และงานด้านความรู้ที่รันยาว” มี context window 1 ล้านโทเคน เอาต์พุตสูงสุด 128K และราคา $4 / $20 ต่อโทเคนอินพุต / เอาต์พุต ล้านโทเคน

ระบุโมเดลให้ชัดเจน เพื่อไม่ให้การเปลี่ยน alias ไปสลับโมเดลกลางโครงการ:

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

Opus 5.5 ตั้งค่า effort เริ่มต้นเป็น medium ตามเอกสารเกี่ยวกับ effort ให้เพิ่มเป็น high สำหรับการสร้างครั้งแรก ซึ่งเอเจนต์ต้องคำนวณคณิตศาสตร์ของ projection ให้ถูก แล้วค่อยลดกลับไป medium สำหรับการแก้คีย์เฟรมและสีเล็กน้อย หากอยากรู้จักโมเดลนี้มาก่อน อ่านบทความ Claude Opus 5.5 คืออะไร และวิธีที่ prompt caching เปลี่ยนต้นทุน

พรอมป์ที่สร้างสตูดิโอ

พรอมป์เดียวที่เจาะจงชนะพรอมป์คลุมเครือห้าพรอมป์ นี่คือสเปกการสร้างของเรา เขียนเป็นพรอมป์เดียว:

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.

สองบรรทัดสุดท้ายสำคัญที่สุด: พวกมันทำให้การส่งออกเป็นส่วนหนึ่งของดีไซน์ ไม่ใช่สิ่งที่เพิ่มทีหลัง

โค้ดที่เอเจนต์เขียน

สตูดิโอนี้มีขนาดราว 150 บรรทัด มีสามส่วนที่สำคัญ

คีย์เฟรมและ easing

แต่ละ track เป็นรายการคีย์เฟรมที่เรียงลำดับไว้ ฟังก์ชัน sample() จะหาคีย์สองตัวที่ล้อมรอบ t ใช้ easing ของคีย์ต้นทาง แล้ว interpolate ทุกฟิลด์ตัวเลข:

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];
}

เทคนิคเรื่องความลึก

กล้องไม่เคลื่อนที่เอง โลกคือสิ่งที่เคลื่อนไปทางตรงข้าม ทุกเลเยอร์จะได้รับ translate ตามแกน z พร้อม scale ชดเชย:

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})`;

เมื่อตั้ง CSS perspective ไว้ที่ 1200px เลเยอร์ที่ z = -900 จะถูกเรนเดอร์ที่ขนาด 1200 / 2100 หรือ 57% ของขนาดจริง การคูณ scale และออฟเซ็ต x/y ด้วย 1 + 900/1200 = 1.75 จะตัดผลนั้นทิ้ง ทำให้ทุกเลเยอร์แสดงที่ขนาดและตำแหน่งที่คุณกำหนดไว้เมื่อกล้องหยุดนิ่ง และยังเคลื่อนที่ด้วยความเร็วของตัวเองเมื่อกล้องเคลื่อน นั่นคือทั้งหมดของภาพลวงตานี้

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

บั๊กที่ควรรู้

เวอร์ชันแรกคูณ scale ของแต่ละเลเยอร์แต่ไม่คูณออฟเซ็ต ทำให้เนินเขาลอยขึ้นมาบังท้องฟ้าไปสองในสาม วิธีแก้: คูณ x และ y ด้วย comp ด้วย เราจับบั๊กนี้ได้จากเฟรมที่เรนเดอร์ออกมาหนึ่งเฟรม ไม่ได้จับจากการอ่านโค้ด ดังนั้นให้สั่งเอเจนต์ของคุณเรนเดอร์ภาพนิ่งที่ต้น กลาง และท้าย ก่อนที่คุณจะเชื่อการเคลื่อนกล้องแบบใดก็ตาม

ส่งออก timeline เป็น MP4

การเล่นแบบเรียลไทม์ใช้ requestAnimationFrame ซึ่งทิ้งเฟรมเมื่อเครื่องไม่ว่าง แต่การส่งออกทำแบบนั้นไม่ได้ เอกซ์พอร์เตอร์จึงควบคุมเวลาจากดัชนีเฟรม และรอให้แต่ละเฟรมวาดเสร็จก่อนจับ สกรีนช็อตด้วย 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) จะ resolve หลังผ่านไปสอง animation frame เพื่อให้แต่ละสกรีนช็อตเห็น transform ใหม่ image2pipe (ดูเอกสารฟอร์แมตของ ffmpeg) อ่าน PNG จาก stdin และ yuv420p ทำให้ไฟล์เล่นได้ในเบราว์เซอร์

ผลการรันของเรา: 180 เฟรม, 1280x720, 30 fps, 6.0 วินาที, ไฟล์ MP4 ขนาด 420 KB ใช้เวลาจริง 20 วินาที รัน npx playwright install chromium หนึ่งครั้งหาก Playwright ยังไม่มีเบราว์เซอร์

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

ตึกเมืองเลื่อนน้อยกว่าเนินเขาสีเข้มด้านหน้า ซึ่งเลื่อนออกจากเฟรมไปเลย นั่นแหละคือพารัลแลกซ์

เพิ่มฟีเจอร์ “อธิบายช็อต” ด้วย Opus 5.5 API

ต่อไป ให้นักเขียนพิมพ์คำอธิบายช็อตแล้วได้คีย์เฟรมกลับมา สตูดิโอเรียก 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." }]
  }'

มีสองรายละเอียดจากเอกสาร effort ที่สำคัญตรงนี้ output_config.effort คือตำแหน่งที่ระดับ effort ถูกกำหนดใน request ของ Messages API และ Opus 5.5 ใช้ adaptive thinking เสมอ (request ที่ส่ง thinking: {"type": "disabled"} จะได้ 400 กลับมา) ทำให้ thinking tokens นับรวมใน max_tokens ถ้าตั้งค่าต่ำเกินไป JSON จะถูกตัดกลางอาร์เรย์พร้อม stop_reason: "max_tokens" รายการช็อตเป็นงานเล็กที่ระบุชัดเจน ดังนั้น effort ระดับ low เป็นค่าเริ่มต้นที่สมเหตุสมผล แต่ควรทดสอบเปรียบเทียบด้วยตัวเองก่อนตกลงใช้ค่านั้น

ให้ Claude Code ทดสอบ API call ด้วย Apidog CLI

คำตอบที่ถูกตัดหรืออ้อนวอนเกินไปจะทำให้ sample() พังตอนเรนเดอร์ ดังนั้น call นี้ต้องมี contract test เซสชัน Claude Code เดิมสามารถเขียนและรันมันได้ด้วย Apidog CLI

ติดตั้งและล็อกอินหนึ่งครั้ง:

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

แล้วสั่งเอเจนต์ด้วยพรอมป์เดียว:

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.

เอเจนต์จะอ่าน payload schemas แทนการเดา ตรวจสอบไฟล์ของมัน แล้วสร้างและรันทุกอย่าง:

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

ส่งคีย์ตอนรันด้วย --env-var "api_key=$ANTHROPIC_API_KEY" เพื่อไม่ให้คีย์ถูกเก็บไว้ในโปรเจกต์ รัน test case ซ้ำเมื่อคุณเปลี่ยน system prompt หรือระดับ effort เพราะทั้งสองอย่างเปลี่ยนผลลัพธ์ที่ได้กลับมา ใช้ apidog run เดิมกับ -r junit ฝังใน CI ได้ ดูคู่มือ CI/CD ของ Apidog CLI และApidog CLI ใน Claude Code แผน Free ของ Apidog รองรับผู้ใช้ได้ถึง 4 คน

ก้าวต่อไปของสตูดิโอ

คำถามที่พบบ่อย

2.5D ในงาน motion graphics หมายถึงอะไร? หมายถึงการวางภาพ 2D แบบแบนไว้ที่ความลึกต่างกันแล้วถ่ายด้วยกล้องที่เคลื่อนที่ คุณได้พารัลแลกซ์และสัญญาณบอกความลึกโดยไม่ต้องมีโมเดล 3 มิติ แสง หรือ 3D engine

ทำไมถึงใช้ Opus 5.5 แทนโมเดลที่เล็กกว่าสำหรับงานสร้างนี้? การสร้างครั้งแรกรวมคณิตศาสตร์ projection, เอนจินคีย์เฟรม และ pipeline การจับเฟรมไว้ในรอบเดียว Anthropic วางตำแหน่ง Opus 5.5 สำหรับงานเขียนโค้ดแบบ agentic ที่รันยาว และเซสชันของเราได้สตูดิโอที่ใช้งานได้จากพรอมป์เดียวบวกการแก้หนึ่งรอบ ส่วนการแก้ไขเล็กน้อยหลังจากนั้น ระดับ effort ที่ต่ำกว่าก็เพียงพอ

ทำไมไม่อัดหน้าจอแทนการจับเฟรม? การอัดหน้าจอเดินตามเวลาจริง เฟรมที่ช้าจึงกลายเป็นเฟรมที่ถูกทิ้งหรือแสดงซ้ำ การ seek ไปที่ i / fps และจับแต่ละเฟรมทำให้ผลลัพธ์เหมือนกันทุกเครื่อง

ใช้ฟีเจอร์ “อธิบายช็อต” ได้ไหมหากไม่มี API key? ไม่ได้ ฟีเจอร์นี้เรียก Messages API ซึ่งต้องใช้ Anthropic API key ของคุณเอง ส่วนอื่นของสตูดิโอและการส่งออก MP4 รันในเครื่องได้โดยไม่ต้องมีคีย์

ควรใช้ effort ระดับใดกับการเรียกสร้างรายการช็อต? เริ่มที่ low เพราะงานสั้นและ schema ผลลัพธ์ตายตัว เพิ่มระดับก็ต่อเมื่อการทดสอบใน Apidog แสดงว่าได้คีย์เฟรมที่ผิดรูปแบบหรือไม่ตรงคำสั่ง

ฝึกการออกแบบ API แบบ Design-first ใน Apidog

ค้นพบวิธีที่ง่ายขึ้นในการสร้างและใช้ API