סצנות תלת־ממד
צרו מפרומפט סצנת תלת־ממד שאפשר לערוך, ערכו אותה ורנדרו אותה ל-MP4, והריצו רינדור תלת־ממד Pro מ-TypeScript עם client.scene3d ורכיבי התלת־ממד.
client.scene3d מפיק סצנות תלת־ממד שאפשר לערוך: הוא יוצר סצנה מפרומפט, עורך אותה לפי הוראה או בפעולות מדויקות, ומרנדר גרסה שלה ל-MP4. הוא גם מריץ רינדור תלת־ממד Pro, פעולה אחת שיוצרת סצנה ומרנדרת אותה, וקורא את הקבצים שרינדור מסר. המתודות משתמשות ברכיבים יצירת סצנת תלת־ממד, עריכת סצנת תלת־ממד, רינדור וידאו ורינדור תלת־ממד Pro. נקודות הקצה מפורטות בדף ה-API סצנות תלת־ממד.
מתודות
| מתודה | מה היא עושה |
|---|---|
capabilities() | קריאת המנועים והאפשרויות של Pro שההתקנה הזו מציעה |
generate(params) ו-generateAndWait() | יצירת סצנה חדשה מפרומפט |
edit(params) ו-editAndWait() | יצירת גרסה חדשה של סצנה |
render(params) ו-renderAndWait() | רינדור של גרסה ל-MP4, בלי יצירה |
applyEdits(revisionId, params) | שמירת עריכות מדויקות בלי יצירה ובלי חיוב |
quotePro(params) | אומדן מחיר להרצה של רינדור תלת־ממד Pro |
runPro(params, options?) | התחלת הרצה של רינדור תלת־ממד Pro שקיבלה אומדן מחיר |
renderProAndWait(params, options?) | אומדן מחיר, הרצה והמתנה בקריאה אחת |
getDelivery(jobId) | קריאת הקבצים שרינדור מסר |
deliveryAssetBytes(jobId, asset, options?) | הורדה של קובץ אחד שנמסר |
retainedRecipe(jobId, options?) | קריאת המתכון ששמרה הרצת Pro שנדחתה |
assetBytes(revisionId, asset, options?) | הורדה של קובץ מגרסה של סצנה |
sourceBytes(revisionId, options?) | הורדה של קובץ המקור של גרסה, בפורמט המקורי של המנוע |
מפרומפט ל-MP4
const created = await client.scene3d.generateAndWait({
prompt: "Orbit a single box on a floor over four seconds",
durationSeconds: 4,
fps: 24,
aspectRatio: "16:9",
})
const edited = await client.scene3d.editAndWait({
scenePlan: created.scenePlan,
expectedRevisionId: created.scenePlan.revisionId,
operations: [{ op: "set-camera", changes: { focalLengthMm: 50 } }],
})
const clip = await client.scene3d.renderAndWait({ planType: "3d-scene", plan: edited.scenePlan })
console.log(clip.videoUrl)אותם שלושה שלבים עובדים גם עם client.nodes.runAndWait() ועם סוגי הרכיבים generate-3d-scene, edit-3d-scene ו-render-video, שיש להם פרמטרים עם טיפוסים. כדי לאפשר לאנשים לסובב ולכוונן סצנה בדף שלכם, השתמשו בהטמעת התצוגה התלת־ממדית. ההטמעה לא צריכה עותק של מנוע הרינדור, ובהודעות שלה אין אסימונים.
client.scene3d
capabilities()
מחזירה מה ההתקנה הזו יכולה ליצור ולרנדר (GET /v1/3d-scene/capabilities).
capabilities(): Promise<Scene3DCapabilities>const caps = await client.scene3d.capabilities()
if (caps.advanced) console.log(caps.advanced.engines) // for example ["blender-cloud"]
if (caps.pro) console.log("3D Render Pro is available")basicהוא המנוע הדטרמיניסטי:{ available, sceneSchemaVersions }.advancedמפרט מנועים אופציונליים, וערכוnullכשאין כאלה. בקשה מפורשת למנוע שלא זמין נדחית עוד לפני יצירה במנוע הבסיסי ולפני בדיקות הקרדיטים שלה.proמתאר את רינדור תלת־ממד Pro: המנועים, פרופילי האיכות, הסגנונות ויחסי הגובה־רוחב שההתקנה הזו מספקת. הציעו רק אותם. כשהשדהproחסר, הרכיב לא זמין.
generate(params) ו-generateAndWait(params, options?)
יוצרות סצנה חדשה מפרומפט. המתודה generate() מחזירה את המשימה מיד, והמתודה generateAndWait() ממתינה לסיום שלה ומחזירה את scenePlan ו-changeSummary אופציונלי.
generate(params: GenerateScene3DParams): Promise<RunNodeResult>
generateAndWait(params: GenerateScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>מאפיין
סוג
const { scenePlan } = await client.scene3d.generateAndWait({
prompt: "A paper boat drifts across a puddle as rain starts",
references: [{ id: "look", kind: "image", role: "appearance", url: moodImageUrl }],
})inputAssets בוחר מודלי תלת־ממד בפורמט GLB שמותר לכם להשתמש בהם, לפי המזהים הקבועים שלהם; תמונות המראה וסרטוני התנועה עדיין עוברים ב-references. אל תשלחו כתובות URL או גיבובים של הקבצים: השרת מספק בעצמו את הרשומות של הקבצים. המנוע הבסיסי ומנועים שלא יודעים לייבא דוחים את inputAssets לפני כל חיוב. כשאתם מבקשים אומדן מחיר של Pro, שלחו את אותם פריטים של inputAssets.
edit(params) ו-editAndWait(params, options?)
יוצרות גרסה חדשה של סצנה. הסצנה שאתם מעבירים אף פעם לא משתנה. תנו הוראה ב-prompt, או operations מדויקות, ואף פעם לא את שניהם.
edit(params: EditScene3DParams): Promise<RunNodeResult>
editAndWait(params: EditScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>מאפיין
סוג
const { scenePlan: next, changeSummary } = await client.scene3d.editAndWait({
scenePlan,
expectedRevisionId: scenePlan.revisionId,
prompt: "Make the rain heavier and lower the camera",
})render(params) ו-renderAndWait(params, options?)
מרנדרות ל-MP4 בדיוק את הגרסה שאתם מעבירים, בלי ליצור או לבנות מחדש שום דבר (POST /v1/render-video/plan).
render(params: { planType: "3d-scene"; plan: Scene3DPlan; workflowId?: string; nodeId?: string }): Promise<RunNodeResult>
renderAndWait(params: RenderScene3DParams, options?: RunAndWaitOptions): Promise<NodeJobOutput>מאפיין
סוג
const { videoUrl } = await client.scene3d.renderAndWait({ planType: "3d-scene", plan: scenePlan })המחיר נקבע לפי ה-width וה-height של התוכנית עצמה:
| פריים | קרדיטים | מזהה המחיר |
|---|---|---|
| עד 1,920 פיקסלים בצד הארוך | 55 | render-video |
| גדול יותר, עד 5.12 מגה־פיקסלים | 83 | render-video:3d-large |
| מעל 5.12 מגה־פיקסלים | 138 | render-video:3d-xlarge |
את המחירים הנוכחיים של המזהים האלה מקבלים עם client.credits.modelCosts().
applyEdits(revisionId, params)
שומרת עריכות מדויקות בגרסה שמורה, בלי יצירה ובלי חיוב (POST /v1/3d-scene/revisions/:id/edits). המתודה מחזירה את ה-scenePlan החדש ו-changeSummary.
applyEdits(revisionId: string, params: {
newRevisionId: string
expectedContentHash: string
operations: Scene3DV2EditOperation[]
lockedObjectIds?: string[]
}): Promise<{ scenePlan: Scene3DPlanV2; changeSummary: string }>מאפיין
סוג
const { scenePlan: saved } = await client.scene3d.applyEdits(revisionId, {
newRevisionId: crypto.randomUUID(),
expectedContentHash,
operations,
})אמצו את הסצנה שחזרה רק אם המשתמש עדיין עורך את הגרסה שממנה התחלתם. בקובצי הגאומטריה והמצלמה נעשה שימוש חוזר. תמונות הפוסטר, תוצאות הבדיקה וקובצי ההורדה בפורמט המקורי מצורפים שוב רק אחרי שהם נוצרים לגרסה החדשה.
רינדור תלת־ממד Pro
רינדור תלת־ממד Pro הוא פעולה אחת: שולחים source, ומשימה אחת מסתיימת גם עם scenePlan, הקומפוזיציה המדויקת, וגם עם videoUrl, ה-MP4. התוצאה כוללת גם את הגרסה, תמונת פוסטר, shotStills, תוצאות הבדיקה ופרטי מנוע הרינדור. המתודות client.nodes.run("pro-3d-render") ו-runAndWait מגיעות לאותו נתיב, עם אותם פרמטרים ואותם טיפוסים.
בהתקנה בלי המנוע, הבקשה נדחית עם 503 SCENE_CAPABILITY_UNAVAILABLE, ו-Nodaro לעולם לא עובר למנוע הבסיסי כחלופה. בהתקנה שלא נקבע בה מחיר, הבקשה נדחית עם 503 price_not_configured לפני שמשהו משוריין. אין הגדרה של מודל או של מאמץ: השרת קובע את המתכנן.
quotePro(params)
נותנת אומדן מחיר להרצה בלי להתחיל אותה. על הקריאה הזו לא משוריינים ולא נגבים קרדיטים. התשובה כוללת quoteId, תקרה ב-maxCredits, פירוט ב-breakdown שאפשר להציג, וגיבוב של הקלט שמולו ההרצה תיבדק אחר כך.
quotePro(params: Pro3DRenderParams): Promise<Pro3DRenderQuote>מאפיין
סוג
ל-source יש אחת משלוש צורות:
{ kind: "prompt", prompt, references? }יוצר סצנה חדשה.{ kind: "scene", revisionId, sourceJobId }בליeditPromptמייצא סצנה שמורה, בלי חיוב על יצירה. הוספה שלeditPromptמשנה קודם את הסצנה. השדהsourceJobIdנדרש בסצנות של המנוע הבסיסי שקיימות רק בהיסטוריית המשימות.{ kind: "local-export", exportId, connectionId }משתמש במחשב שולחני מקושר.
const quote = await client.scene3d.quotePro(params)
showPrice(quote.maxCredits, quote.breakdown)runPro(params, options?)
מתחילה הרצה שקיבלה אומדן מחיר. המתודה צריכה את ה-quoteId מ-quotePro(), כך ששום הרצה לא מתחילה במחיר שאף אחד לא ראה. היא שולחת Idempotency-Key: מפתח חדש בכל קריאה, או המפתח שלכם מ-options.idempotencyKey. כשאתם מנסים שוב קריאה שנתקלה בפסק זמן, השתמשו שוב במפתח שלכם.
runPro(params: Pro3DRenderParams & { quoteId: string }, options?: { idempotencyKey?: string }): Promise<RunNodeResult>מאפיין
סוג
await client.scene3d.runPro({ ...params, quoteId: quote.quoteId })renderProAndWait(params, options?)
נותנת אומדן מחיר כשהפרמטר params לא כולל quoteId, מריצה וממתינה: כל הפעולה בקריאה אחת. המתודה שולחת שתי בקשות לכל היותר ומתחילה משימה אחת בתשלום, שמאושרת מול גיבוב של בדיוק מה שקיבל אומדן מחיר.
renderProAndWait(params: Pro3DRenderParams | Pro3DRenderRunParams, options?: RunAndWaitOptions & { idempotencyKey?: string }): Promise<Pro3DRenderJobOutput>מאפיין
סוג
const caps = await client.scene3d.capabilities()
if (caps.pro?.available) {
const shot = await client.scene3d.renderProAndWait({
source: {
kind: "prompt",
prompt: "A red suitcase rolls behind a central pillar and reappears",
references: [{ id: "look", kind: "image", role: "appearance", url: appearanceImageUrl }],
},
durationSeconds: 30,
fps: 24,
aspectRatio: "21:9",
maxRepairPasses: 2,
acceptedSceneSchemaVersions: [2],
})
console.log(shot.videoUrl) // the MP4
console.log(shot.sceneRevisionId) // export it again later, with no authoring charge
}תמונות סטילס של השוטים. השדה shotStills הוא רשימה של { shotIndex, frame, assetId, url }: תמונת סטילס אחת לכל שוט, בפריים שבו השוט נפתח. אותה הרצה יוצרת אותן, בלי עלות נוספת. בסצנה עם שוט אחד יש תמונת סטילס אחת, בפריים 0. בתוצאות שנוצרו לפני שהשדה נוסף אין תמונות כאלה, ולכן השתמשו ב-shot.shotStills ?? [].
כל url הוא נקודת קצה בהתקנה שלכם שדורשת אימות, ולא קישור ציבורי. הורידו את הקובץ עם אותם פרטי גישה ששימשו אתכם להרצה; בקשה מתג img או משירות של צד שלישי נדחית עם 401:
for (const still of shot.shotStills ?? []) {
const res = await fetch(still.url, { headers: { Authorization: `Bearer ${token}` } })
const bytes = await res.arrayBuffer() // use the bytes, or store them where your pipeline can read them
}עדיין אפשר להשתמש בתמונת סטילס כייחוס למודל: העבירו את הכתובת שלה כמו שקיבלתם אותה, ב-referenceImageUrls או דרך הפלט stills של הרכיב. הפלטפורמה נותנת להרצה הזו הרשאה קצרת מועד לקרוא את הקובץ האחד הזה, בשמכם. ההרשאה פגה כעבור כמה דקות, ולכן שמרו את הכתובת שדורשת אימות, ולעולם לא את ההרשאה.
מה הרצת Pro מדווחת על עצמה
הרצה שיצרה סצנה מדווחת מה היא הניחה ומה היא עשתה. התייחסו לכל שדה כאופציונלי: בהתקנה בלי מנוע מתקדם, או בתוצאה ישנה יותר, אין אף אחד מהם.
const summary = shot.metadata?.summary // what the planner says it built
const repairs = shot.repairPasses // 0 means accepted the first time
const retries = shot.admissionRetries // planner retries before a build
const mechanical = shot.mechanicalPasses
const restored = shot.restoredAssertions
const assumptions = (shot.validation?.warnings ?? [])
.filter((w) => w.code === "SCENE_AUTHORING_ASSUMPTION")
.map((w) => w.message)- אזהרות
SCENE_AUTHORING_ASSUMPTIONמפרטות מה הפרומפט לא אמר, ולכן ההרצה החליטה בעצמה. קודי אזהרה חדשים יכולים להתווסף: התייחסו לקוד לא מוכר כאל מידע, לא כאל שגיאה. repairPassesסופר תיקונים, לא מעברי יצירה, ולכן0פירושו שהסצנה התקבלה כבר בפעם הראשונה. השדהadmissionRetriesסופר משהו אחר: כמה פעמים ביקשו מהמתכנן מתכון חדש, לפני שהתחילה בנייה כלשהי.mechanicalPassesסופר תיקונים שהמנוע החיל לפי התיקון שהמהדר עצמו הציע, בלי המתכנן. יש להם מכסה משלהם באומדן המחיר, של עד 2 מעברים, שמשתחררת אם לא נזקקים לה, וכל מעבר כזה מוסיף אזהרתREMEDY_AUTO_APPLIED. בהרצה שקיבלה אומדן מחיר לפני שהמכסה הזו נוספה, שילמו על מעברים כאלה כמו על תיקונים; כדי לדעת אם זה המצב, בדקו את אומדן המחיר שקיבלתם.restoredAssertionsמפרט בדיקות חובה שהמנוע החזיר למקומן אחרי שהמתכנן שינה בדיקה שלא ביקשו ממנו לשנות. כל אחת מהן מוסיפה גם אזהרתASSERTION_RESTORED.- ייצוא של רינדור בלבד לא יצר שום דבר, ולכן אין בו ספירות ואין בו סיכום. במיוחד ב-
mechanicalPasses, שדה חסר לא אומר0.
בתוצאות של generateAndWait() מופיעים אותם שדות כשמנוע מתקדם יצר את הסצנה. המנוע הבסיסי לא פונה לשום מודל, ואין בתוצאות שלו אף אחד מהשדות האלה.
מסירה שהבדיקה החזותית לא אישרה
תוצאה שהושלמה יכולה להגיע בלי אישור של הבדיקה החזותית. בשני המקרים הסרטון אמיתי והקרדיטים נגבו. השדה metadata.review.verdict מציין באיזה מקרה מדובר:
"refused": תקציב התיקונים נגמר, כל בדיקות החובה עברו, והבדיקה החזותית עדיין הסתייגה. הסצנה נמסרה, וההכרעה הזו מצורפת אליה."unavailable": הבדיקה לא נתנה הכרעה שאפשר להשתמש בה. הערך שלreasonהוא"provider"כשהבדיקה בכלל לא הגיעה לספק שלה, ו-"unusable"כשאי אפשר היה להשתמש בתשובה. השדהattemptsמציין כמה פעמים פנו אליה. אף אחד לא שפט את הסצנה הזו.
import { scene3DReviewNote, scene3DReviewVerdictOf } from "@nodaro/shared"
const review = scene3DReviewVerdictOf(shot)
if (review) {
console.log(scene3DReviewNote(review)) // one user-safe sentence for either verdict
if (review.verdict === "unavailable") console.log(`unreviewed (${review.reason}) after ${review.attempts} attempts`)
for (const objection of review.objections) console.log(objection.category, objection.what, objection.correction)
}השתמשו בשתי פונקציות העזר מ-@nodaro/shared במקום לקרוא את השדות בעצמכם, כי יש שלוש דרכים לקרוא אותם שנראות נכונות, אבל הן שגויות:
- הערך של
validation.statusהוא עדיין"passed". בדיקות החובה אכן עברו, ולכן הסצנה נמסרה. - הרשימה
objectionsיכולה להיות ריקה. גם הסתייגות שלא ציינה שום דבר מסוים היא הסתייגות, ולכן ספירה של אזהרותSCENE_REVIEW_REFUSEDמחמיצה אותה. - ההסתייגויות בהכרעת
"unavailable"הן לא ההכרעה. הן מגיעות מאצוות בדיקה שענו לפני שאחת מהן נכשלה. רשימה ריקה שם פירושה שתיקה, לא אישור.
כל הסתייגות היא { category, what, correction?, frames }. בהכרעת "unavailable", אזהרת SCENE_REVIEW_UNAVAILABLE מופיעה ראשונה ב-validation.warnings. הסתייגות של הבדיקה החזותית כבר לא מכשילה לבדה את המשימה. הקוד SCENE_QUALITY_FAILED פירושו שבדיקת חובה נכשלה או שהמהדר דחה את המתכון. בדף של רכיב רינדור תלת־ממד Pro מפורט מה תוצאה כזו שנכשלה שומרת.
מסירות וקבצים
המתודות האלה מחזירות קבצים שהרצה כבר מסרה. הן אף פעם לא מתחילות רינדור. כל קריאה בודקת מחדש את הגישה שלכם גם למסירה וגם למקור שלה, אפילו אחרי שגרסת המקור נמחקה.
getDelivery(jobId)
מחזירה את הרשומה של מסירה (GET /v1/3d-scene/deliveries/:jobId): ה-sourceKind שלה, גרסת המקור המדויקת שלה והקבצים שקובעו בה.
getDelivery(jobId: string): Promise<Scene3DDelivery>מאפיין
סוג
const delivery = await client.scene3d.getDelivery(jobId)
const stills = delivery.assets.filter((a) => a.kind === "shot-still")מופיעים ארבעה סוגי קבצים: הסוג validation-report בכל מסירה, poster בכל מסירה חוץ ממסירה של יצירה שנדחתה, shot-still פעם אחת לכל שוט, כל אחד עם shotIndex, frame, width ו-height משלו, ו-source-json רק במסירה מסוג refused-authoring. הערך של sourceKind הוא retained-revision, job-output או refused-authoring. במסירה של יצירה שנדחתה, השדות sceneRevisionId ו-sourcePlanSha256 הם null ואין תמונת פוסטר, כי שום דבר לא עבר הידור.
deliveryAssetBytes(jobId, asset, options?)
מורידה קובץ אחד שמופיע ברשימה של המסירה, עם אימות מחדש ומגבלת גודל.
deliveryAssetBytes(jobId: string, asset: Scene3DDeliveryAsset, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>מאפיין
סוג
const bytes = await client.scene3d.deliveryAssetBytes(jobId, stills[0])retainedRecipe(jobId, options?)
מחזירה את המתכון ששמרה הרצה של רינדור תלת־ממד Pro שנדחתה, מפוענח, או null כשאין כזה. המתכון של הרצה שנדחתה אף פעם לא עבר הידור, ולכן אין גרסת סצנה, אין תמונת פוסטר ואין קובץ מקור; המתכון הוא מה שההרצה משאירה אחריה.
retainedRecipe(jobId: string, options?: { signal?: AbortSignal }): Promise<unknown | null>מאפיין
סוג
const recipe = await client.scene3d.retainedRecipe(jobId)- המתודה דורשת הרשאת עריכה בתהליך של המשימה. מי שיש לו הרשאה נמוכה יותר לא רואה את המתכון בכלל, ולכן התשובה היא
null, ולא שגיאה. - המשימה שנכשלה מראה אם יש טעם לבקש. הערך של
validation.sourceRetainedשלה הואtrueכשמתכון נשמר. - המתכון הוא תיעוד, לא קלט. הרצה שנדחתה לא פרסמה שום גרסה, ולכן אי אפשר להריץ אותה שוב מהמתכון. בדקו אותו כדי לראות מה נוסה וכדי לשפר את הפרומפט הבא. הקריאה לא עולה קרדיטים.
assetBytes(revisionId, asset, options?)
מורידה קובץ של גרסת סצנה שמורה: מודל תלת־ממד בפורמט GLB, מסלול מצלמה, תמונת הפוסטר או דוח הבדיקה. העבירו את המתאר המדויק מאותה גרסה. ה-SDK מגביל את ההורדה לגודל המוצהר, ומנוע הרינדור של הסצנה גם בודק את גיבוב ה-SHA-256.
assetBytes(revisionId: string, asset: Scene3DAssetRef, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>מאפיין
סוג
const glb = await client.scene3d.assetBytes(scenePlan.revisionId, glbAsset)sourceBytes(revisionId, options?)
מורידה את קובץ המקור של גרסה בפורמט המקורי של המנוע, למשל קובץ .blend, עם בדיקת הרשאה נפרדת משלו. המתודה דורשת אותה הרשאת עריכה כמו מתכון שנשמר. קובץ מקור זמין רק כשהוא מייצג בדיוק את אותה גרסה שהתקבלה.
sourceBytes(revisionId: string, options?: { signal?: AbortSignal }): Promise<ArrayBuffer>מאפיין
סוג
const blend = await client.scene3d.sourceBytes(revisionId)שתי המתודות האלה, שמורידות בייטים, משתמשות בפרטי גישה עדכניים, מכבדות ביטול, וזורקות כרגיל שגיאות עם טיפוסים.
שאלות נפוצות
איך הופכים פרומפט לאנימציית תלת־ממד עם ה-SDK?
הריצו את המתודה client.scene3d.generateAndWait עם פרומפט כדי לקבל תוכנית סצנה שאפשר לערוך, שנו אותה עם editAndWait אם תרצו, ואז רנדרו אותה ל-MP4 עם renderAndWait.
כמה עולה רינדור של סצנת תלת־ממד?
המחיר של רינדור סצנת תלת־ממד נקבע לפי גודל הפריים בתוכנית: 55 קרדיטים עד 1,920 פיקסלים בצד הארוך, 83 קרדיטים עד 5.12 מגה־פיקסלים ו-138 קרדיטים מעל זה. את המחירים הנוכחיים מקבלים מה-API של עלויות המודלים.
מה זה רינדור תלת־ממד Pro?
רינדור תלת־ממד Pro הוא פעולה אחת שיוצרת סצנה ומרנדרת אותה: התוצאה כוללת גם את תוכנית הסצנה וגם את ה-MP4. קבלו קודם אומדן מחיר עם המתודה quotePro. כדי לדעת אם ההתקנה שלכם מציעה אותו, בדקו את capabilities().pro.
האם אפשר לפתוח כתובת של תמונת סטילס של שוט בתג img?
לא. תמונות הסטילס של השוטים וקבצי מסירה אחרים מוגשים מנקודת קצה שדורשת אימות. הורידו אותם עם אותם פרטי גישה שבהם הרצתם את המשימה, ואז הציגו או שמרו את הבייטים.
דפים קשורים
יצירת סצנת תלת־ממד
רינדור תלת־ממד Pro
הטמעת התצוגה התלת־ממדית
סצנות תלת־ממד
סצנות תלת־ממד
עודכן לאחרונה