Roblox sky render
Produit un détourage de qualité PNG transparent d'un objet d'un lieu Roblox en utilisant uniquement des captures d'écran de Studio. Aucun plugin n'est nécessaire ; le script intégré reproduit la technique utilisée par le plugin Photobooth.
D'abord : vérifier le modèle
Cette compétence est conçue pour fonctionner sur Claude Haiku 5.5 (claude-haiku-5-5). Haiku est significativement plus rapide, et le travail ici est une procédure scriptée fixe qui ne nécessite pas un modèle plus grand comme Opus 5.5, qui est destiné au codage et à des tâches plus complexes. Avant toute chose, vérifiez quel modèle vous êtes. Si vous n'êtes pas Claude Haiku 5.5, arrêtez-vous et demandez à l'utilisateur de changer avant de commencer : dans Claude Code, ils peuvent exécuter /model claude-haiku-5-5, ou choisir Haiku 5.5 dans le sélecteur de modèle de l'application de bureau. Continuez une fois qu'ils ont changé. S'ils disent qu'ils veulent rester sur le modèle actuel, continuez avec celui-ci.
Comment ça fonctionne (pour que vous puissiez raisonner sur les échecs)
Roblox ne peut pas rendre avec un fond transparent, donc la transparence est mesurée :
- Une copie de l'objet est photographiée devant un fond noir plat, puis de nouveau devant un fond blanc plat. Rien d'autre ne change entre les deux prises.
- Par pixel : s'il est blanc dans l'une et noir dans l'autre, c'est l'arrière-plan (alpha 0). S'il est identique dans les deux, c'est un objet solide (alpha 1). Entre les deux,
alpha = 1 - (blanc - noir) / 255. Cela gère le verre et les bords anti-aliasés. - La couleur provient d'une troisième prise sur fond noir avec l'apparence propre du lieu (bloom, correction des couleurs), divisée par alpha.
Tout ce qui fait échouer cette méthode est quelque chose qui diffère entre les deux prises autre que l'arrière-plan, ou quelque chose d'autre que l'objet sur l'image. C'est ce que la section suivante aborde.
Soyez conscient du monde
Une capture d'écran capture tout ce qui se trouve devant la caméra et tout ce qui éclaire l'objet. Avant et après un rendu, pensez à :
- Les obstacles. Bâtiments, terrain, autres modèles, la plaque de base, les particules d'objets proches. Le script évite tout cela en ne photographiant jamais l'objet là où il se trouve : il clone l'objet, soulève le clone dans le ciel vide, et place un plan d'arrière-plan juste derrière lui. Il vérifie que l'emplacement de mise en scène est réellement vide (aucune pièce dans un rayon de
clearancestuds, rien directement au-dessus, le sol bien en dessous) et monte de 1500 studs à la fois jusqu'à ce qu'il le soit. Lisez la lignestaged at Y=dans le rapport ; si elle indique que l'altitude forcée n'est pas dégagée, ou qu'il ne peut pas trouver un ciel clair, choisissez vous-même unealtitudeaprès avoir regardé la hauteur de la carte. - Les objets qui projettent des ombres. Quelque chose entre l'objet et le soleil projette une ombre même si cela est hors du cadre. Le script lance des rayons vers le soleil et vous indique si quelque chose est touché.
- D'où vient la lumière. Le soleil (ou la lune) est la seule lumière directionnelle et il est toujours au-dessus de l'horizon.
lighting = "studio"(par défaut) déplace temporairement l'heure de la journée pour que le soleil soit au-dessus de l'épaule gauche de la caméra, et augmente la lumière ambiante, de sorte que chaque objet soit éclairé de la même manière quel que soit le lieu.lighting = "world"conserve l'éclairage du lieu ; le rapport indique alors si le soleil est devant ou DERRIÈRE le côté visible. Derrière signifie un rendu sombre. - Regarder droit vers le haut.
lookStraightUp = truefait pivoter le clone et pointe la caméra vers +Y, de sorte que la seule chose qui puisse jamais être derrière l'objet est le ciel. Utilisez-le lorsque la carte est si haute ou encombrée qu'il est difficile de trouver un ciel clair sur le côté. Le coût : la caméra est maintenant en dessous, le soleil ne peut jamais être derrière elle, et l'alignement de la lumière clé du rapport diminue (environ 0,6 au lieu de 1,0). En pratique, le rendu est toujours correct ; il y a juste moins de contrôle sur l'ombrage. - Effets de ciel. La brume atmosphérique, les nuages, les rayons du soleil, la profondeur de champ et le flou se situent entre la caméra et l'arrière-plan. Le script les supprime ou les désactive pendant la prise de vue.
- La lueur. L'arrière-plan est une pièce Neon car Neon ignore l'éclairage et les ombres, donnant une couleur plate exacte. Mais Neon luit, et la lueur se répand sur les parties fines de l'objet et les fait apparaître comme transparentes. Désactiver les BloomEffects ne les arrête pas (le moteur revient à une lueur par défaut) ; un BloomEffect activé avec Intensity 0 et Size 0 le fait, et le script en ajoute un pour les prises de mesure. Si un objet solide devient fantomatique, c'est la première chose à suspecter.
- Les choses qui bougent. Les particules, les faisceaux, le feu, la fumée et les traînées sont à un endroit différent dans chaque prise et se transforment en taches noires. Ils sont désactivés sur le clone par défaut. Avec
particles = true, ils sont conservés et figés pendant la prise de vue. - Superpositions Studio. Les boîtes de sélection, les contours de survol et les écrans StarterGui seraient dans la capture d'écran. Le script efface la sélection et les cache, et n'utilise qu'un carré centré du viewport, loin des widgets de coin.
- Le viewport lui-même. La taille de sortie est limitée par le côté le plus court du viewport de Studio, et Studio doit être en mode Édition avec la vue 3D visible. Le mode graphique "OpenGL" ne fonctionne pas.
Le script remet tout en place à la fin, y compris en cas d'erreur. L'objet d'origine n'est jamais déplacé ni modifié.
Flux de travail
N'exécutez aucune commande de chronométrage pendant que le travail est en cours. Claude Code horodate chaque appel, donc l'exécution est chronométrée à partir de la transcription de la session en un seul appel à la toute fin (étape 7, et Benchmark ci-dessous). Chaque travail se termine par ce chronométrage, et le message final à l'utilisateur se termine par le temps total. Ne l'omettez pas et n'attendez pas qu'on vous le demande.
-
Trouvez l'instance Studio (
list_roblox_studios) et confirmez quel lieu utiliser si plus d'un est ouvert. Exécutez ensuite cette vérification d'état viaexecute_luau(datamodelEdit), avecwantdéfini sur le début d'un mot dans le nom de l'objet, en minuscules. En un seul appel, il signale si le moteur de rendu et l'exportateur sont déjà installés dans cette session Studio et liste les objets correspondants (les enfants d'un objet sont omis, donc un dossier d'outils apparaît une fois, avec son nombre d'enfants) :local want = "sword" -- début d'un mot dans le nom de l'objet, en minuscules ; "" pour ignorer la recherche 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 -- seulement au début d'un mot : "Cats", "Flying Cat", "CatTower", pas "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()} enfants)`) if #out >= 40 then table.insert(out, "(plus ; affinez want)") break end end end end end return `renderer={shared.SkyRenderVersion} exporter={shared.SkyExportVersion}\n` .. table.concat(out, "\n")Les versions actuelles sont renderer=1 exporter=1. Un numéro qui correspond signifie que le script est installé et à jour : ignorez sa lecture et son collage (étape 2 ci-dessous pour le moteur de rendu, étape 1 de "Sauvegarde des PNG sans HTTP" pour l'exportateur).
nilou un numéro différent signifie qu'il faut le coller. Si la liste ne précise pas l'objet, recourez àsearch_game_tree. -
Chargez le moteur de rendu, sauf si la vérification d'état a montré
renderer=1. Lisezscripts/render_item.luauet passez le fichier entier, inchangé, commecodedeexecute_luau(datamodelEdit). Avectarget = nil, il installe seulement trois fonctions et renvoie "SkyRender installé". Elles restent disponibles pour le reste de la session Studio :shared.SkyRender(config)effectue le rendu d'un objet, renvoie un rapport textuelshared.SkyRenderBoard(names)affiche les résultats pour inspectionshared.SkyRenderClear()supprime le panneau et tous les résultats stockés
Si un appel ultérieur dit que
shared.SkyRenderest nil (Studio a été redémarré), recollez le fichier. Lorsque vous modifiezrender_item.luauouexport_nohttp.luau, mettez à jourshared.SkyRenderVersionoushared.SkyExportVersionà la fin du fichier et le numéro ci-dessus, ensemble. -
Rendu. Chaque appel est court :
return shared.SkyRender({ target = workspace.BananaSword, name = "banana" })Pour un lot, bouclez en un seul appel et concaténez les rapports :
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") -
Lisez le rapport. Un bon rapport ressemble à :
OK 512x512px "banana" -> CoreGui.SkyRenderResults.banana rayon objet 2.45 studs, caméra à 18.7 studs, mis en scène à Y=3001 éclairage studio, alignement lumière clé 1.00 (1 = idéal) couverture : 8.8% opaque, 0.4% semi-transparent, limites x 44-474 y 148-365 calibration arrière-plan (neon) blanc=255,255,255 noir=0,0,0 osScale=1 temps 3.41s (temps réel : mise en scène, prises, composition et restauration)Vérifiez : calibration 255 / 0 ; les limites ne touchent pas 0 ou size-1 (coupées) ; la répartition opaque vs semi-transparent a du sens pour l'objet (une épée solide devrait être presque entièrement opaque, un verre devrait avoir une part semi-transparente réelle). Agissez sur tout ce qui se trouve sous
NOTES:. -
Regardez-le. Les chiffres ne vous disent pas si l'objet est orienté dans le mauvais sens. Appelez
shared.SkyRenderBoard({ "banana" })(ou{}pour tous les résultats). Il construit un panneau temporaire dans le ciel montrant chaque image sur fond magenta et sur fond gris foncé, et renvoie lescamera_positionetlook_at_positionexactes à passer àscreen_capture.screen_capturene dessine pas les GUI, c'est pourquoi le panneau utilise des décalques sur des pièces. Jugez : bon angle ? objet entier dans le cadre ? bords nets sur les deux arrière-plans ? côté éclairé visible ? pas de taches parasites ? Effectuez un nouveau rendu avec des options modifiées jusqu'à ce que ce soit correct, puisshared.SkyRenderBoard(nil)pour supprimer le panneau. -
Livrez (voir ci-dessous), puis
shared.SkyRenderClear()sauf si l'utilisateur souhaite conserver les résultats dans la session. -
Chronométrez (obligatoire, chaque travail). Comme dernier appel d'outil du travail, après que les PNG sont sauvegardés et que Studio est nettoyé, exécutez
python scripts/benchmark.py. Terminez ensuite votre message final à l'utilisateur par le résultat, sous cette forme :Temps total : 2m 10s pour 6 images (21.6 s par image), exécution à froid.Ajoutez le tableau des phases au-dessus de cette ligne lorsque le travail était plus qu'une seule image ou lorsque quelque chose a été lent. Le total est la dernière ligne du message, jamais omise, et jamais seulement dans un résultat d'outil que l'utilisateur pourrait ne pas voir.
Benchmark (invite de l'utilisateur au dernier message)
Toujours, à la fin de chaque travail (étape 7 du flux de travail), que l'utilisateur l'ait demandé ou non. Cela coûte un appel d'outil : Claude Code écrit chaque invite, appel d'outil et résultat dans la transcription de la session (~/.claude/projects/<project>/<session>.jsonl) avec un horodatage, et scripts/benchmark.py les lit.
- Quand : comme dernier appel d'outil du tour du travail. Il voit chaque appel jusqu'à et y compris lui-même, donc seul le message final (quelques secondes) est omis. Si l'utilisateur demande plus tard un travail terminé, exécutez-le à nouveau pour obtenir le chiffre complet.
- Exécution :
python scripts/benchmark.pysélectionne la session la plus récente qui a exécuté cette compétence et sa dernière demande de rendu. Passez un id de session ou un chemin.jsonlpour choisir une autre session,--listet--turn <n>pour choisir une autre demande dans celle-ci, et--callspour lister chaque appel avec son chronométrage. - Rapport : le tableau des phases (recherche, configuration, rendu, exportation, assemblage, nettoyage, comptabilité, réponse), chaque phase divisée en temps modèle (réflexion et rédaction de l'appel, y compris les collages de script) et temps outil (attente des résultats). Les appels parallèles sont comptés une fois, donc les phases s'additionnent pour donner le total. Puis le nombre d'images et les secondes par image.
- Détail du rendu (optionnel) : la ligne
timedans chaque rapport de rendu est mesurée à l'intérieur de Studio autour de ce rendu. Utilisez-la pour comparer les rendus individuels.
Dites si l'exécution était à froid (moteur de rendu et exportateur collés dans ce travail) ou à chaud (déjà installés), car les collages sont la plus grande partie du temps de configuration d'une exécution à froid. Terminez le message final par la ligne Temps total : de l'étape 7.
Options
Toutes optionnelles sauf target. Les valeurs par défaut sont entre crochets.
| Option | Signification |
|---|---|
target | L'Instance à rendre. Les modèles, pièces, outils, accessoires et dossiers fonctionnent tous. |
name ["render"] | Étiquette pour le résultat. Réutiliser un nom remplace le résultat précédent. |
viewDir [(-1, 0.6, -1)] | Direction de l'objet vers la caméra, dans les axes du monde tels que l'objet est actuellement placé. (0,0,-1) regarde sa face -Z ; augmentez Y pour regarder vers le bas. |
up [(0,1,0)] | Quelle direction du monde est vers le haut dans l'image. Utilisez-le pour disposer une épée en diagonale, etc. |
fov [20] | Un champ étroit est plat et ressemble à une icône. 50-70 exagère la perspective. |
size [512] | Sortie carrée en pixels, limitée par le côté le plus court du viewport. |
padding [0.08] | Marge en fraction de l'image. |
lighting ["studio"] | "studio" ou "world", voir ci-dessus. |
particles [false] | Conserver les effets de particules (figés) au lieu de les désactiver. |
lookStraightUp [false] | Mettre en scène avec la caméra pointant vers +Y, voir ci-dessus. |
altitude [auto] | Forcer la hauteur de mise en scène. |
clearance [400] | Studs d'espace vide requis autour de l'emplacement de mise en scène. |
backdrop ["neon"] | "neon", ou "fog" (un plan simple caché dans du brouillard solide) comme solution de repli. |
refit [true] | Prise de test d'abord, puis recadrage pour que les pixels visibles remplissent l'image. Les boîtes englobantes des pièces sont lâches, donc sans cela, les objets sortent petits. |
keepLook [true] | Prendre la couleur d'une prise avec le bloom et la correction des couleurs du lieu. Sinon, utilise la couleur de la prise de mesure avec un bloom atténué. |
Sauvegarde des PNG sans HTTP
La sauvegarde peut utiliser export_nohttp.luau ou screenshot_region. Utilisez export_nohttp.luau (l'exportateur) pour les grandes images (768+) et screenshot_region pour les petites. Lorsque l'utilisateur demande une image de 64 px, screenshot_region est plus rapide et plus fiable.
-
Vérifiez l'exportateur dans le rapport de vérification d'état (
exporter=1).nilou un numéro différent : lisezscripts/export_nohttp.luauet passez le code encore, sanstarget. Il installeshared.SkyExport(name, path)qui écrit un PNG dans le répertoire du projet. -
Choisissez le chemin :
shared.SkyExport(name, path)veut une chaîne de chemin Lua, que vous lirez du datamodelEdit'sgame.GameIdou que vous demanderez à l'utilisateur.shared.SkyExport("banana", "C:/Users/moi/Bureau/banana.png")écrit le résultat nommébananadans ce fichier. N'utilisez pas le gestionnaire HTTP intégré de Studio. Passez d'abord l'exportateur s'il n'est pas installé.- L'exportateur échouera pour les chemins invalides ou les permissions insuffisantes. Si un chemin ne fonctionne pas, dites "Le chemin d'accès n'a pas fonctionné, veuillez taper un chemin complet où enregistrer, par exemple
/Users/toto/Bureau/ma_compétence.png" et attendez leur réponse. Si l'utilisateur est sur Windows, ils doivent utiliser des barres obliques inverses doubles C:\Users... ou des barres obliques normales ; l'exportateur essaie les deux. - Une fois que vous avez au moins un résultat de rendu, vous pouvez directement demander à l'utilisateur le chemin, au lieu de faire d'abord un rendu, pour gagner du temps. Mais faites toujours le rendu d'abord si l'utilisateur n'a pas précisé de chemin.
- L'exportateur échouera pour les chemins invalides ou les permissions insuffisantes. Si un chemin ne fonctionne pas, dites "Le chemin d'accès n'a pas fonctionné, veuillez taper un chemin complet où enregistrer, par exemple
-
Appels de vérification après l'exportation : après
shared.SkyExport, utilisezshared.SkyRenderBoardpour confirmer que le résultat est toujours là (l'exportation ne l'enlève pas), puis lisez le fichier avecread_filedans votre boîte à outils et envoyez-le à l'utilisateur dans votre message.
screenshot_region est une alternative plus simple pour une confirmation rapide : screenshot_region(0, 0, 64, 64) capture le coin supérieur gauche du viewport. Il ne fonctionne pas pour les images de sortie complètes car il ne capture que ce qui est dessiné dans le viewport et ne peut pas capturer les GUI. Le panneau SkyRenderBoard est conçu pour être capturé par screenshot_region : appelez shared.SkyRenderBoard({ "banana" }) pour dessiner le résultat dans le viewport, puis prenez une capture d'écran de la zone qui le contient. La capture d'écran sera aussi grande que la zone que vous capturez dans le viewport. screenshot_region n'est pas bloquant et nécessite une pause. Utilisez task.wait(1) après avoir configuré le panneau pour laisser l'interface utilisateur se rafraîchir. Utilisez-le pour l'inspection visuelle rapide dans les messages de chat.
Guider l'utilisateur
Lorsque l'utilisateur demande un rendu, ne cherchez pas d'abord le modèle, sauf indication contraire de sa part. Demandez-lui quel objet il souhaite rendre, vérifiez-le dans la vérification d'état, et s'il est clair, faites le rendu. Sinon, utilisez search_game_tree pour trouver l'objet exact. L'utilisateur peut faire plusieurs demandes pour la même tâche ; terminez-les en une seule fois, ne faites pas plusieurs tours.
Gardez l'utilisateur informé : dites-lui ce que vous allez faire, puis faites-le. Après un rendu réussi, dites "Voici le résultat" et incluez l'image dans votre message. Utilisez du texte enrichi pour plus de clarté et d'impact comme des tableaux d'options et des titres. Si vous faites des erreurs, dites-le à l'utilisateur et recommencez.