Avatar: tune Roblox proportions + walk cycle from real rendered screenshots
checkpoint / checkpoint (push) Successful in 1m47s
checkpoint / checkpoint (push) Successful in 1m47s
Now that cloudbison can render (Radeon GPU via Xvfb, or llvmpipe software), added scripts/tools/screenshot_rig.gd + scenes/dev/ so a scene can be booted, run a few frames, and its rendered frame saved to PNG -- caught three things the headless smoke never could: - limbs floated detached from the torso: widened the torso and overlapped the arm/leg boxes into it, and pulled limb depth in behind the torso front so they stop z-fighting. - proportions were scrawny: reworked to Roblox R6 ratios (2-stud torso, 2-stud arms/legs, 1-stud head) with the head clearly pegged on top. - the walk swing was a 40-deg lunge: STRIDE 0.7->0.42 rad, separate 0.32 arm swing, KNEE 1.0->0.5. Reads as a walk now, not a crouch. test_level lighting was near-black on the shaded side (ambient was disabled) -- colour ambient at 0.8 + a stronger key light, so the avatar reads in-world. avatar_preview.tscn (AGP_PREVIEW_WALK=1) renders the assembled avatar walking in place for quick visual checks. Verified: avatar smoke 14/14 source + exported pack; fresh-clone boot PASS; eyeballed in rendered screenshots (game scene + walk sequence). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
extends Node
|
||||
## Dev-only screenshot harness. Boots a target scene inside the real (GPU)
|
||||
## renderer, lets it run a few frames, then reads the rendered frame back off
|
||||
## the viewport and writes it to disk. Nothing in the shipped game touches this.
|
||||
##
|
||||
## Driven entirely by environment variables so it needs no CLI parsing:
|
||||
## AGP_SHOT_SCENE res:// path of the scene to capture (default: game.tscn)
|
||||
## AGP_SHOT_OUT output PNG path (absolute) (default: /tmp/agp_shot.png)
|
||||
## AGP_SHOT_FRAMES frames to settle before the first shot (default: 45)
|
||||
## AGP_SHOT_SEQ "count:gap" -> take <count> shots <gap> frames apart,
|
||||
## suffixing the filename _01.._NN (default: single shot)
|
||||
##
|
||||
## Launch (hardware Vulkan on cloudbison's Radeon, via Xvfb):
|
||||
## Xvfb :99 -screen 0 1280x720x24 &
|
||||
## sg render -c "sg video -c 'DISPLAY=:99 AGP_SHOT_SCENE=res://scenes/game.tscn \
|
||||
## AGP_SHOT_OUT=/abs/shot.png ~/godot/godot --path . \
|
||||
## --rendering-driver vulkan --gpu-index 0 --resolution 1280x720 \
|
||||
## res://scenes/dev/screenshot_rig.tscn'"
|
||||
|
||||
func _ready() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var target := OS.get_environment("AGP_SHOT_SCENE")
|
||||
if target.is_empty():
|
||||
target = "res://scenes/game.tscn"
|
||||
var out := OS.get_environment("AGP_SHOT_OUT")
|
||||
if out.is_empty():
|
||||
out = "/tmp/agp_shot.png"
|
||||
var settle := int(OS.get_environment("AGP_SHOT_FRAMES"))
|
||||
if settle <= 0:
|
||||
settle = 45
|
||||
|
||||
var seq_count := 1
|
||||
var seq_gap := 0
|
||||
var seq_env := OS.get_environment("AGP_SHOT_SEQ")
|
||||
if seq_env.contains(":"):
|
||||
var parts := seq_env.split(":")
|
||||
seq_count = maxi(1, int(parts[0]))
|
||||
seq_gap = maxi(1, int(parts[1]))
|
||||
|
||||
var packed: PackedScene = load(target)
|
||||
if packed == null:
|
||||
push_error("screenshot_rig: cannot load %s" % target)
|
||||
get_tree().quit(1)
|
||||
return
|
||||
var inst: Node = packed.instantiate()
|
||||
get_tree().root.add_child(inst)
|
||||
get_tree().current_scene = inst
|
||||
|
||||
for i in settle:
|
||||
await get_tree().process_frame
|
||||
|
||||
for shot in seq_count:
|
||||
# get_image() on the viewport texture is a GPU->CPU readback of the
|
||||
# frame that was just rendered -- no window presentation needed, which
|
||||
# is why this works over Xvfb (no DRI3).
|
||||
var img: Image = get_viewport().get_texture().get_image()
|
||||
var path := out
|
||||
if seq_count > 1:
|
||||
path = "%s_%02d.png" % [out.get_basename(), shot + 1]
|
||||
var err := img.save_png(path)
|
||||
if err != OK:
|
||||
push_error("screenshot_rig: save_png(%s) failed: %d" % [path, err])
|
||||
else:
|
||||
print("SHOT %s (%dx%d)" % [path, img.get_width(), img.get_height()])
|
||||
if shot < seq_count - 1:
|
||||
for j in seq_gap:
|
||||
await get_tree().process_frame
|
||||
|
||||
get_tree().quit()
|
||||
Reference in New Issue
Block a user