tools: visual-review screenshot pipeline
- AGigLineShotActor (dev-only): walks a fixed camera list in -game mode, captures via FScreenshotRequest, quits. Test content only. - ue_build_review_map.py builds /Game/Test/ReviewMap from current content (gray floor, key light, sky, MPFB character, 1 m scale cubes). - render_screenshots.sh: build map -> run game -> collect PNGs into gitignored Screenshots/. - Screenshots/README.md (committed) describes each capture with approval state; AGENTS.md gains the visual-review instructions.
This commit is contained in:
1 parent
774d810dab
commit
bbb3e147b3
12 files changed
+525
-1
No files matched your search
@@ -0,0 +1,136 @@
|
||||
# UE-side screenshot renderer for visual review (commandlet, real RHI).
|
||||
#
|
||||
# Builds a small review scene in the default world (floor, sky, key light,
|
||||
# MPFB character, the three scale-test cubes) and renders three fixed camera
|
||||
# angles synchronously via SceneCapture2D render targets, writing PNGs
|
||||
# directly into <repo>/Screenshots/ (gitignored).
|
||||
#
|
||||
# Invoked by Tools/render_screenshots.sh (UnrealEditor-Cmd -run=pythonscript).
|
||||
# NOTE: no -nullrhi — rendering needs the real Vulkan RHI. In 5.8 the
|
||||
# commandlet swallows print(); results go to /tmp/ue-screenshots-result.txt.
|
||||
# Do NOT load_map here: after a map load the commandlet's spawn context dies
|
||||
# and every spawn_actor call returns None.
|
||||
|
||||
import os
|
||||
|
||||
import unreal
|
||||
|
||||
RESULT = "/tmp/ue-screenshots-result.txt"
|
||||
lines = []
|
||||
|
||||
|
||||
def log(msg):
|
||||
lines.append(str(msg))
|
||||
|
||||
|
||||
REPO = os.path.abspath(os.path.join(unreal.Paths.project_dir(), "..", ".."))
|
||||
OUT_DIR = os.path.join(REPO, "Screenshots")
|
||||
WORLD = unreal.EditorLevelLibrary.get_editor_world()
|
||||
|
||||
|
||||
def spawn(cls, loc, label=None, scale=None, rot=None):
|
||||
a = unreal.EditorLevelLibrary.spawn_actor_from_class(cls, loc)
|
||||
if a is None:
|
||||
log(f"spawn {cls.__name__} returned None")
|
||||
return None
|
||||
if rot is not None:
|
||||
a.set_actor_rotation(rot, False)
|
||||
if scale is not None:
|
||||
a.set_actor_scale3d(scale)
|
||||
if label:
|
||||
a.set_actor_label(label)
|
||||
return a
|
||||
|
||||
|
||||
def spawn_mesh(mesh, loc, label=None, scale=None):
|
||||
a = unreal.EditorLevelLibrary.spawn_actor_from_object(mesh, loc)
|
||||
if a is None:
|
||||
log(f"spawn mesh {label} returned None")
|
||||
return None
|
||||
if scale is not None:
|
||||
a.set_actor_scale3d(scale)
|
||||
if label:
|
||||
a.set_actor_label(label)
|
||||
return a
|
||||
|
||||
|
||||
# --- stage content -----------------------------------------------------------
|
||||
|
||||
try:
|
||||
floor_mesh = unreal.EditorAssetLibrary.load_asset("/Game/Test/ScaleTest/ScaleTest_A")
|
||||
if floor_mesh:
|
||||
spawn_mesh(floor_mesh, unreal.Vector(450, 450, -50.0), "ReviewFloor", scale=(45.0, 45.0, 0.1))
|
||||
log("staged: floor (45 m plane from ScaleTest_A)")
|
||||
|
||||
dl = spawn(unreal.DirectionalLight, unreal.Vector(0, 0, 900), "RenderKeyLight",
|
||||
rot=unreal.Rotator(pitch=-38.0, yaw=35.0, roll=0.0))
|
||||
if dl:
|
||||
log("staged: DirectionalLight")
|
||||
if spawn(unreal.SkyLight, unreal.Vector(0, 0, 1200), "RenderFillSky"):
|
||||
log("staged: SkyLight")
|
||||
if spawn(unreal.SkyAtmosphere, unreal.Vector(0, 0, 0), "RenderSky"):
|
||||
log("staged: SkyAtmosphere")
|
||||
|
||||
char_mesh = unreal.EditorAssetLibrary.load_asset("/Game/Test/Character/character")
|
||||
if char_mesh:
|
||||
if spawn_mesh(char_mesh, unreal.Vector(0, 0, 0), "ReviewCharacter"):
|
||||
log("staged: MPFB character at origin")
|
||||
else:
|
||||
log("MISSING: /Game/Test/Character/character")
|
||||
|
||||
for name, x, y in (("ScaleTest_A", 0.0, 0.0), ("ScaleTest_B", 400.0, 0.0), ("ScaleTest_C", 0.0, 400.0)):
|
||||
m = unreal.EditorAssetLibrary.load_asset(f"/Game/Test/ScaleTest/{name}")
|
||||
if m:
|
||||
spawn_mesh(m, unreal.Vector(x, y, 50.0), f"Cube_{name}")
|
||||
log(f"staged: {name} at ({x}, {y}, 50)")
|
||||
except Exception as exc: # noqa
|
||||
log(f"staging failed: {exc!r}")
|
||||
|
||||
# --- render ------------------------------------------------------------------
|
||||
|
||||
|
||||
def render_shot(name, loc, target, fov=70.0):
|
||||
rt = unreal.RenderingLibrary.create_render_target2d(
|
||||
WORLD, 1920, 1080, unreal.TextureRenderTargetFormat.RTF_RGBA8_SRGB,
|
||||
unreal.LinearColor(0.0, 0.0, 0.0, 1.0))
|
||||
cap = spawn(unreal.SceneCapture2D, loc)
|
||||
if cap is None:
|
||||
log(f"{name}: no capture actor")
|
||||
return
|
||||
cap.set_actor_rotation(unreal.MathLibrary.find_look_at_rotation(loc, target), False)
|
||||
comp = cap.get_component_by_class(unreal.SceneCaptureComponent2D)
|
||||
comp.set_editor_property("texture_target", rt)
|
||||
comp.set_editor_property("capture_source", unreal.SceneCaptureSource.SCS_FINAL_COLOR_LDR)
|
||||
comp.set_editor_property("fov_angle", fov)
|
||||
comp.set_editor_property("capture_every_frame", False)
|
||||
comp.set_editor_property("capture_on_movement", False)
|
||||
try:
|
||||
pps = comp.get_editor_property("post_process_settings")
|
||||
pps.set_editor_property("auto_exposure_method", unreal.AutomaticExposureMethod.AEM_MANUAL)
|
||||
pps.set_editor_property("auto_exposure_bias", 1.0)
|
||||
comp.set_editor_property("post_process_settings", pps)
|
||||
except Exception as exc: # noqa
|
||||
log(f"{name}: exposure control skipped ({exc!r})")
|
||||
comp.capture_scene()
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
ok = unreal.RenderingLibrary.export_render_target(WORLD, rt, OUT_DIR, name)
|
||||
out = os.path.join(OUT_DIR, f"{name}.png")
|
||||
size = os.path.getsize(out) if os.path.exists(out) else 0
|
||||
log(f"rendered {name}: export ok={ok}, {size} bytes")
|
||||
unreal.EditorLevelLibrary.destroy_actor(cap)
|
||||
|
||||
|
||||
SHOTS = [
|
||||
("01-overview", unreal.Vector(900, -1100, 500), unreal.Vector(200, 100, 80), 70.0),
|
||||
("02-character", unreal.Vector(180, -260, 160), unreal.Vector(0, 0, 85), 55.0),
|
||||
("03-scale-cubes", unreal.Vector(550, -700, 150), unreal.Vector(200, 200, 50), 60.0),
|
||||
]
|
||||
|
||||
for shot_name, loc, target, fov in SHOTS:
|
||||
try:
|
||||
render_shot(shot_name, loc, target, fov)
|
||||
except Exception as exc: # noqa
|
||||
log(f"shot {shot_name} failed: {exc!r}")
|
||||
|
||||
with open(RESULT, "w") as f:
|
||||
f.write("\n".join(lines) + "\n")
|
||||
Reference in new issue
Block a user