Roblox 天空渲染
僅使用 Studio 截圖,從 Roblox 場景中產出單一物品的透明 PNG 級別去背圖片。無需任何插件;內附的腳本重現了 PhotoBooth 插件所使用的技術。
第一步:檢查模型
此技能設計在 Claude Haiku 5.5 (claude-haiku-5-5) 上運行。Haiku 速度顯著更快,而這裡的工作是固定的、腳本化的程序,不需要像 Opus 5.5 這樣的較大模型(適合用於編寫程式碼和更複雜的任務)。在做任何事之前,請先檢查你是哪個模型。如果您不是 Claude Haiku 5.5,請停止並要求使用者切換後再開始:在 Claude Code 中,他們可以執行 /model claude-haiku-5-5,或在桌面應用程式的模型選擇器中選擇 Haiku 5.5。一旦他們切換完成再繼續。如果他們說要留在當前模型,就在當前模型上繼續。
運作原理(以便您可以推理失敗原因)
Roblox 無法渲染出透明背景,因此透明度是用 測量 的:
- 複製的物品分別在純黑色和純白色背景前拍攝一次。兩次拍攝之間沒有其他變化。
- 逐像素:如果一個像素在一張圖中是白色,在另一張中是黑色,則它是背景(alpha 0)。如果在兩張圖中完全相同,則是實心物品(alpha 1)。介於兩者之間時,
alpha = 1 - (white - black) / 255。這可以處理玻璃和抗鋸齒邊緣。 - 顏色來自第三張在黑色背景上、使用場景自身外觀(泛光、色彩校正)的拍攝,再除以 alpha。
任何導致此方法失敗的原因,都是兩次拍攝之間除了背景之外有所差異的因素,或是畫面中包含物品以外的東西。這就是下一節要討論的內容。
注意場景因素
螢幕截圖會捕捉相機前的所有東西,以及照亮物品的所有東西。在渲染前後,請考慮:
- 擋在中間的東西。 建築物、地形、其他模型、底板、附近物體的粒子。腳本透過不在物品原本位置拍攝來避免所有這些問題:它會複製物品,將複本提升到空曠的天空中,並在複本正後方放置一個背景平面。它會檢查搭建位置是否確實空曠(在
clearance個螺柱內沒有零件,正上方沒有東西,下方地面很遠),並且會以每次 1500 個螺柱為單位向上攀升,直到找到空曠位置為止。請閱讀報告中的staged at Y=行;如果它說強制高度不明確,或找不到空曠的天空,請在查看地圖高度後自行選擇一個altitude。 - 會投射陰影的東西。 物品和太陽之間的物體即使不在畫面中,也會在物品上投射陰影。腳本會向太陽方向進行射線投射,並告訴您是否有任何物體被擊中。
- 光線來自何處。 太陽(或月亮)是唯一的定向光源,並且始終在地平線之上。
lighting = "studio"(預設)會暫時移動一天中的時間,使太陽位於相機左肩上方的位置,並提高環境光,這樣無論場景為何,每個物品都會以相同的方式被照亮。lighting = "world"會保留場景的光照;然後報告會說明太陽是在可見側的前方還是後方。在後方意味著渲染結果會很暗。 - 直視上方。
lookStraightUp = true會旋轉複本並將相機指向 +Y 軸,這樣物品背後永遠只會是天空。當地圖太高或太雜亂,以至於在側邊難以找到空曠的天空時使用。代價是:相機現在在下方,太陽永遠無法在它後面,並且報告的關鍵光線對齊度會下降(約 0.6,而非 1.0)。實際上渲染效果仍然可以;只是對陰影的控制較少。 - 天空效果。 大氣霧霾、雲朵、太陽光線、景深和模糊效果會存在於相機和背景之間。腳本會在拍攝期間移除或停用它們。
- 發光效果。 背景使用的是 Neon 零件,因為 Neon 會忽略光照和陰影,提供精確的平面色彩。但 Neon 會發光,這種發光會擴散到物品較薄的部分,使它們看起來像是透明的。關閉 BloomEffects 並不能阻止這種情況(引擎會回退到預設的發光效果);一個啟用的 BloomEffect,其 Intensity 和 Size 都設為 0 就可以,並且腳本會為測量用的拍攝添加一個。如果一個實心物品最終看起來像幽靈一樣,這是首先要懷疑的地方。
- 會動的東西。 粒子、光束、火焰、煙霧和軌跡在每次拍攝中都處於不同的位置,會變成黑色的團塊。預設情況下,它們會在複本上被關閉。使用
particles = true時,它們會被保留並在拍攝期間凍結。 - Studio 覆蓋層。 選取方框、懸停輪廓和 StarterGui 畫面會出現在截圖中。腳本會清除選取並隱藏它們,並且只使用視窗中心的一個正方形區域,避開角落的小工具。
- 視窗本身。 輸出尺寸受 Studio 視窗較短邊的限制,且 Studio 必須處於編輯模式且 3D 視圖可見。圖形模式 "OpenGL" 無法運作。
腳本會在完成後恢復所有這些設定,即使發生錯誤也是如此。原始物品永遠不會被移動或修改。
工作流程
在作業進行期間,請勿執行任何計時命令。Claude Code 會為每次呼叫加上時間戳記,因此執行時間是從階段 7(以及下面的基準測試)的最後一次呼叫中的對話記錄中計算出來的。每次作業都以該時間結束,並且發送給使用者的最終訊息也包含總時間。 不要跳過它,也不要等待被詢問。
-
找到 Studio 實例 (
list_roblox_studios) 並確認要使用哪個場景(如果開啟了多個)。然後透過execute_luau(資料模型Edit)執行此狀態檢查,其中want設定為物品名稱中某個單詞的起始字母(小寫)。在一次呼叫中,它會報告渲染器和匯出器是否已安裝在此 Studio 工作階段中,並列出相符的物品(物品自己的子項目被排除,因此一個工具資料夾只會顯示一次,並附上其子項目數量):local want = "sword" -- 物品名稱中某個單詞的起始字母(小寫);"" 跳過搜尋 local function matches(d) if not (d:IsA("Model") or d:IsA("Tool") or d:IsA("Accessory") or d:IsA("BasePart") or d:IsA("Folder")) then return false end local name, i = d.Name, string.find(string.lower(d.Name), want, 1, true) while i do -- 僅在單詞開頭:"Cats", "Flying Cat", "CatTower",不是 "Location" if i == 1 or not string.match(string.sub(name, i - 1, i - 1), "%a") or string.match(string.sub(name, i, i), "%u") then return true end i = string.find(string.lower(name), want, i + 1, true) end return false end local out, hits = {}, {} if want ~= "" then for _, d in workspace:GetDescendants() do if matches(d) then local p, inside = d.Parent, false while p and not inside do inside, p = hits[p] == true, p.Parent end if not inside then hits[d] = true table.insert(out, `{d:GetFullName()} ({d.ClassName}, {#d:GetChildren()} children)`) if #out >= 40 then table.insert(out, "(more; narrow want)") break end end end end end return `renderer={shared.SkyRenderVersion} exporter={shared.SkyExportVersion}\n` .. table.concat(out, "\n")當前版本是 renderer=1 exporter=1。匹配的數字表示該腳本已安裝且為最新:跳過其讀取和貼上(下方的步驟 2 用於渲染器,"在不使用 HTTP 的情況下儲存 PNG" 的步驟 1 用於匯出器)。
nil或不同的數字表示需要貼上它。如果列表沒有清楚指出是哪個物品,則回退到search_game_tree。 -
載入渲染器,除非狀態檢查顯示為
renderer=1。讀取scripts/render_item.luau並將**整個檔案(不變)**作為execute_luau(資料模型Edit)的code參數傳遞。使用target = nil時,它只會安裝三個函數並返回 "SkyRender installed"。它們在 Studio 工作階段的剩餘時間內保持可用:shared.SkyRender(config)渲染一個物品,返回文字報告shared.SkyRenderBoard(names)顯示結果供檢查shared.SkyRenderClear()移除面板和所有儲存的結果
如果後續呼叫顯示
shared.SkyRender為 nil(Studio 已重新啟動),請再次貼上該檔案。當您更改render_item.luau或export_nohttp.luau時,請同時更新檔案末尾和上述的shared.SkyRenderVersion或shared.SkyExportVersion數字。 -
渲染。每次呼叫都很簡短:
return shared.SkyRender({ target = workspace.BananaSword, name = "banana" })對於批次處理,在單次呼叫中循環並合併報告:
local out = {} for _, tool in workspace.Mug:GetChildren() do table.insert(out, shared.SkyRender({ target = tool, name = tool.Name })) end return table.concat(out, "\n\n") -
閱讀報告。一份好的報告看起來像:
OK 512x512px "banana" -> CoreGui.SkyRenderResults.banana item radius 2.45 studs, camera 18.7 studs away, staged at Y=3001 studio lighting, key light alignment 1.00 (1 = ideal) coverage: 8.8% opaque, 0.4% semi-transparent, bounds x 44-474 y 148-365 backdrop (neon) calibration white=255,255,255 black=0,0,0 osScale=1 time 3.41s (wall clock: staging, shots, composite and restore)檢查:校正值為 255 / 0;邊界未接觸 0 或 size-1(未裁剪);不透明與半透明的比例對物品而言合理(實心劍應幾乎全為不透明,玻璃應有相當比例的半透明)。對
NOTES:下的任何內容採取行動。 -
查看結果。數字不會告訴您物品的方向是否錯誤。呼叫
shared.SkyRenderBoard({ "banana" })(或使用{}查看所有結果)。它會在天空中建立一個臨時面板,分別在洋紅色和深灰色背景上顯示每個圖片,並返回要傳遞給screen_capture的確切camera_position和look_at_position。screen_capture不會繪製 GUI,這就是為什麼面板使用零件上的貼花。判斷:角度正確嗎?整個物品都在畫面中嗎?兩個背景的邊緣都乾淨嗎?光照面可見嗎?有沒有奇怪的團塊?使用更改後的選項重新渲染,直到滿意為止,然後使用shared.SkyRenderBoard(nil)移除面板。 -
傳送(見下文),然後使用
shared.SkyRenderClear(),除非使用者希望在工作階段中保留結果。 -
計時(必需,每次作業)。 作為作業的最後一次工具呼叫,在 PNG 儲存完畢且 Studio 清理完畢後,執行
python scripts/benchmark.py。然後,在給使用者的最終訊息中,以以下形式附上結果:Total time: 2m 10s for 6 images (21.6 s per image), cold run.當作業不只一張圖片或任何步驟較慢時,請在上述行之前加上階段表。總時間是訊息的最後一行,絕不省略,也絕不只放在使用者可能看不到的工具結果中。
基準測試(使用者對最後一則訊息的提示)
始終在每次作業結束時(工作流程步驟 7)執行,無論使用者是否要求。這需要一次工具呼叫:Claude Code 將每個提示、工具呼叫和結果(附時間戳記)寫入工作階段記錄檔(~/.claude/projects/<project>/<session>.jsonl),而 scripts/benchmark.py 會讀取它們。
- 何時執行: 作為該輪作業的最後一次工具呼叫。它會看到直到並包括自己在內的所有呼叫,因此只有最終訊息(幾秒鐘)被排除在外。如果使用者稍後詢問已完成的工作,請再次執行它以獲得完整數據。
- 執行:
python scripts/benchmark.py會挑選執行此技能的最新工作階段及其最後一個渲染請求。傳入工作階段 ID 或.jsonl路徑以選擇另一個工作階段,使用--list和--turn <n>選擇其中的另一個請求,使用--calls列出每次呼叫及其時間。 - 報告: 階段表(尋找、設定、渲染、匯出、組合、清理、記帳、回覆),每個階段分為模型時間(思考和撰寫呼叫,包括腳本貼上)和工具時間(等待結果)。並行呼叫只計算一次,因此階段時間加總即為總時間。然後是圖片數量和每秒圖片數。
- 渲染細節(可選): 每個渲染報告中的
time行是在 Studio 內部針對該次渲染測量的。用它來比較單次渲染。
說明該次執行是冷啟動(渲染器和匯出器在此作業中貼上)還是熱啟動(已安裝),因為貼上是冷啟動設定時間的最大部分。以步驟 7 中的 Total time: 行結束最終訊息。
選項
除 target 外皆為可選。預設值以括號表示。
| 選項 | 意義 |
|---|---|
target | 要渲染的 Instance。模型、零件、工具、配件和資料夾均可。 |
name ["render"] | 結果的標籤。重複使用名稱會取代先前的結果。 |
viewDir [(-1, 0.6, -1)] | 從物品朝向相機的方向,以物品當前所在的世界軸表示。(0,0,-1) 看向其 -Z 面;提高 Y 值則向下看。 |
up [(0,1,0)] | 圖片中哪個世界方向是上方。用於將劍傾斜擺放等。 |
fov [20] | 窄視角較為平面,像圖示。50-70 會誇張透視效果。 |
size [512] | 正方形輸出像素數,受視窗較短邊的限制。 |
padding [0.08] | 邊距,以圖像尺寸的比例表示。 |
lighting ["studio"] | "studio" 或 "world",見上文。 |
particles [false] | 保留粒子效果(凍結),而不是將其關閉。 |
lookStraightUp [false] | 搭建時將相機指向 +Y 軸,見上文。 |
altitude [auto] | 強制搭建高度。 |
clearance [400] | 搭建點周圍所需的空曠空間(螺柱數)。 |
backdrop ["neon"] | "neon",或 "fog"(一個隱藏在實心霧中的簡單平面)作為備用。 |
refit [true] | 先進行測試拍攝,然後重新構圖,使可見像素填滿圖像。零件邊界框較鬆散,因此沒有此功能物品會顯得很小。 |
keepLook [true] | 從一張使用了場景泛光與色彩校正的拍攝中取得顏色。 |
不透過 HTTP 儲存 PNG
由於 Roblox Studio 不允許來自 Luau 的 HTTP 請求("請求已停用"),此技能使用 screen_capture 從 3D 視圖擷取圖像,並透過直接操控 Studio 的編輯器功能來儲存。
-
載入匯出器(除非您在上方的步驟 1 中看到
exporter=1)。讀取scripts/export_nohttp.luau並貼上到execute_luau。它會安裝shared.SkyExport(results, path)。 -
準備存放路徑。 使用
execute_luau建立輸出資料夾:local path = "C:\\Users\\me\\Pictures\\SkyRenders" local result = shared.SkyExport({ "banana" }, path) return result對於批次作業,傳入名稱列表:
{ "sword", "shield" }。路徑必須已經存在,否則匯出器會失敗。 -
傳送。 在最終訊息中,使用此格式:
香蕉劍渲染完成。
C:\\Users\\me\\Pictures\\SkyRenders\\banana.png輸出始終為 PNG。
提示
這些是模型在執行此技能時可以依據的內部指引,即使使用者沒有完全遵循確切的結構化提示。
- 始終執行基準測試(步驟 7)。 不要略過。使用者不會要求它,但訊息必須包含總時間。
- 檢查模型。 在開始之前,請確保您在正確的模型上,如果尚未切換則要求切換。
- 查看界限。 如果緩衝區欄位接觸到 0 或 size-1,則物品被裁剪;重新渲染時調整相機距離、視角或邊距。
- 檢查角度。 使用
SkyRenderBoard在提交之前確認方向。 - 批次處理。 使用
workspace.Folder:GetChildren()或類似方法進行批次渲染,而不是逐個請求。 - 清理。 除非使用者明確要求保留,否則在每次作業結束時呼叫
shared.SkyRenderClear()。 - 不要發明。 不要建立外部後處理腳本或變換。使用提供的腳本和 Studio 的感測器。