Community图像github.com

Wh1msicaly/roblox-sky-render

仅使用Roblox Studio视口截图为Roblox物品(模型、工具、配件、部件)渲染出带有干净透明背景的图像,通过将物品副本放置在空白天空中,并在白色和黑色背景前拍摄来实现。当用户想要获取某个Roblox地点的图标、缩略图、商店图片、库存图、剪影、精灵图或“渲染图”,希望批量渲染多个物品,或提到Photobooth插件技术——即使他们只说“给这把剑拍张照片”时,都可以使用。需要Roblox Studio MCP工具(execute_luau, screen_capture)。

roblox-sky-render 是什么?

该技能通过纯Studio截图方式,为Roblox物品生成透明PNG格式的渲染图,无需外部插件。它复现了Photobooth插件的核心技术:在黑色和白色背景前分别拍摄物品副本,通过像素级的颜色差异计算透明度,然后从黑色背景截图提取颜色,最终合成出干净透明背景的图像。技能会处理各种复杂情况,包括遮挡物、阴影、光照、天空效果、发光、粒子等,并将所有修改恢复原状。 该技能专为Claude Haiku 5.5模型设计(可大幅节省成本),并支持批量渲染。它需要Roblox Studio MCP工具,使用前会检查并安装渲染器和导出器脚本,执行完毕后会进行基准测试并报告总时间。通过可选的配置参数(如视角、光照模式、粒子保持等),用户可以灵活控制渲染结果。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Wh1msicaly/roblox-sky-render/tree/HEAD

Installed? Explore more 图像 skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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无法渲染透明背景,因此透明度是测量出来的:

  1. 物品副本先在一面平坦的黑色背景前拍摄,再在一面平坦的白色背景前拍摄。两拍之间其他任何内容都不改变。
  2. 逐个像素:如果它在一种颜色中是白色,在另一种中是黑色,那么它就是背景(alpha 0)。如果它在两者中完全相同,那么它就是实心物品(alpha 1)。介于两者之间:alpha = 1 - (白色 - 黑色) / 255。这处理了玻璃和抗锯齿边缘。
  3. 颜色来自在黑色背景上使用地点自身外观(泛光、色彩校正)拍摄的第三张照片,然后除以alpha。

导致失败的一切,都是两次拍摄之间除了背景之外有所不同的东西,或者是图片中除了物品之外的其他东西。这就是下一节要讲的内容。

留意世界

截图会捕捉相机前的任何东西以及照亮物品的任何东西。在渲染前后,请考虑:

  • 挡路的东西。 建筑物、地形、其他模型、底板、附近物体的粒子。脚本通过从不拍摄物品所在位置来避免所有这些:它克隆物品,将克隆体提升到空旷的天空中,并在其正后方放置一个背景平面。它会检查放置点是否确实空旷(在clearance虚化单位内没有部件,正上方没有东西,地面在下方很远),并一次爬升1500个虚化单位,直到找到空旷位置。阅读报告中的staged at Y=行;如果它说强制高度不清晰,或者找不到清晰的天空,在查看地图高度后自行选择一个altitude。
  • 投下阴影的东西。 物品和太阳之间的东西会给它投下阴影,即使它超出了画面。脚本会向太阳方向投射射线,并告诉您是否有东西被击中。
  • 光源来自哪里。 太阳(或月亮)是唯一的方向光,并且始终在地平线上方。lighting = "studio"(默认)会临时移动一天中的时间,使太阳位于相机的左肩上方,并提高环境光,这样无论地点如何,每个物品都以相同的方式被照亮。lighting = "world"会保留地点的光照;然后报告会说明太阳是在可见侧的前方还是后方。后方意味着渲染结果较暗。
  • 向上看。 lookStraightUp = true会旋转克隆体并将相机指向+Y方向,因此物品后面永远只能有天空。当地图太高或杂乱,难以找到侧面清晰的天空时使用它。代价是:相机现在位于下方,太阳永远无法到达其后方,报告中关键光对齐度会下降(约0.6而不是1.0)。实际上它仍然可以正常渲染;只是对阴影的控制较少。
  • 天空效果。 大气雾霾、云朵、太阳光、景深和模糊位于相机和背景之间。脚本在拍摄时会移除或禁用它们。
  • 发光。 背景是一个Neon部件,因为Neon忽略光照和阴影,提供精确的平坦颜色。但Neon会发光,并且发光会溢出到物品的薄部分,使它们被读取为透明。关闭BloomEffects并不能阻止这种情况(引擎会回退到默认发光);一个启用的、Intensity为0、Size为0的BloomEffect可以做到,并且脚本会为测量拍摄添加一个。如果实心物品出现幽灵般的效果,这是首先要怀疑的。
  • 会移动的东西。 粒子、光束、火、烟雾和轨迹在每次拍摄中位于不同的位置,会变成黑色斑点。默认情况下,它们在克隆体上被关闭。使用particles = true,它们会被保留并在拍摄时冻结。
  • Studio覆盖层。 选择框、悬停轮廓和StarterGui屏幕会出现在截图中。脚本会清除选择并隐藏它们,并且只使用视口居中的正方形区域,远离角落的小部件。
  • 视口本身。 输出尺寸受Studio视口较短边的限制,并且Studio必须处于编辑模式,且3D视图可见。图形模式"OpenGL"不起作用。

脚本在完成时会将所有这些恢复原状,包括在出错时。原始物品永远不会被移动或修改。

工作流程

在任务进行期间,不要运行任何计时命令。Claude Code会为每次调用添加时间戳,因此运行时间在最后一步(步骤7和基准测试)的单个调用中从会话记录中计时。每次任务都以该计时结束,并且给用户的最终消息以总时间结束。 不要跳过它,也不要等待被询问。

  1. 找到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, "(更多;缩小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。

  2. 加载渲染器,除非状态检查显示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以及上面的数字。

  3. 渲染。每次调用都很简短:

    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")
    
  4. 阅读报告。一个良好的报告看起来像:

    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:下的任何内容采取行动。

  5. 查看它。数字不会告诉您物品朝向错误的方向。调用shared.SkyRenderBoard({ "banana" })(或使用{}获取所有结果)。它在天空中构建一个临时展板,在洋红色和深灰色背景上显示每个图像,并返回精确的camera_position和look_at_position,以传递给screen_capture。screen_capture不绘制GUI,这就是展板使用部件上的贴花的原因。判断:角度正确吗?整个物品在画面中吗?两种背景上的边缘干净吗?照亮侧可见吗?没有杂散斑点吗?使用更改的选项重新渲染,直到正确为止,然后使用shared.SkyRenderBoard(nil)移除展板。

  6. 交付(见下文),然后使用shared.SkyRenderClear(),除非用户希望结果保留在会话中。

  7. 计时(必需,每次任务)。 作为任务的最后一次工具调用,在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会读取它们。

  1. 时间: 作为任务回合的最后一次工具调用。它可以看到直到自身在内的所有调用,因此只有最终消息(几秒钟)被排除在外。如果用户后来询问已完成的任务,请再次运行它以获得完整数据。
  2. 运行: python scripts/benchmark.py会选择运行了此技能的最新会话及其上次渲染请求。传递会话id或.jsonl路径以选择其他会话,传递--list和--turn <n>以选择其中的其他请求,传递--calls以列出每次调用及其计时。
  3. 报告: 阶段表(查找、设置、渲染、导出、组装、清理、簿记、回复),每个都分为模型时间(思考和编写调用,包括脚本粘贴)和工具时间(等待结果)。并行调用只计数一次,因此各阶段加起来等于总时。然后是图像计数和每秒图像数。
  4. 渲染细节(可选): 每次渲染报告中的time行是在Studio内部围绕该渲染测量的。使用它来比较单个渲染。

说明运行是冷启动(渲染器和导出器在此任务中粘贴)还是热启动(已经安装),因为粘贴是冷启动设置时间中最大的一部分。使用来自步骤7的Total time:行结束最终消息。

选项

除了target外全部可选。默认值在方括号中。

选项含义
target要渲染的实例。模型、部件、工具、配件和文件夹都可以。
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]从带有地点泛光和". . . "

…

[为翻译截断]

相关技能