refactor: cinematic render — linear keyframes, 3 segments, 250 frames, white bg

Changes per user feedback:
- Keyframe interpolation: BEZIER → LINEAR (all fcurves set to LINEAR)
- Removed segment 4 (closeup) — now 3 segments only
- Frame count: 480 → 250 (10 seconds at 25fps)
- FPS: 24 → 25
- Easing removed — pure linear interpolation between segment params
- White background by default (World node Color = white)
- Transparent bg still available as override

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 21:57:42 +01:00
parent 75ad397c09
commit 458c6cd813
2 changed files with 38 additions and 42 deletions
+2 -2
View File
@@ -563,8 +563,8 @@ def render_cinematic_to_file(
import time import time
# Cinematic parameters are fixed # Cinematic parameters are fixed
frame_count = 480 frame_count = 250
fps = 24 fps = 25
blender_bin = find_blender() blender_bin = find_blender()
if not blender_bin: if not blender_bin:
+36 -40
View File
@@ -249,14 +249,11 @@ def _apply_material_library(parts, mat_lib_path, mat_map, part_names_ordered=Non
# ── Cinematic camera animation ─────────────────────────────────────────────── # ── Cinematic camera animation ───────────────────────────────────────────────
TOTAL_FRAMES = 480 TOTAL_FRAMES = 250
SEGMENT_LENGTH = 120 # frames per segment NUM_SEGMENTS = 3
SEGMENT_LENGTH = TOTAL_FRAMES // NUM_SEGMENTS # ~83 frames per segment
# Segment definitions: (start_azimuth_offset, end_azimuth_offset, # 3 segments (no closeup): establishing, detail sweep, crane up
# start_elevation, end_elevation,
# start_dist_factor, end_dist_factor,
# start_lens, end_lens,
# use_dof)
SEGMENTS = [ SEGMENTS = [
# Segment 1: Establishing shot — orbit 45deg, push in, 50mm # Segment 1: Establishing shot — orbit 45deg, push in, 50mm
{ {
@@ -282,25 +279,9 @@ SEGMENTS = [
"lens_start": 35.0, "lens_end": 35.0, "lens_start": 35.0, "lens_end": 35.0,
"dof": False, "dof": False,
}, },
# Segment 4: Hero close — push in, settle down
{
"az_start": 120.0, "az_end": 120.0,
"el_start": 35.0, "el_end": 25.0,
"dist_start": 3.0, "dist_end": 2.2,
"lens_start": 65.0, "lens_end": 65.0,
"dof": False,
},
] ]
def _ease_in_out(t: float) -> float:
"""Cubic ease in-out, t in [0, 1]."""
if t < 0.5:
return 4.0 * t * t * t
else:
return 1.0 - (-2.0 * t + 2.0) ** 3 / 2.0
def _lerp(a: float, b: float, t: float) -> float: def _lerp(a: float, b: float, t: float) -> float:
"""Linear interpolation.""" """Linear interpolation."""
return a + (b - a) * t return a + (b - a) * t
@@ -322,21 +303,10 @@ def _get_segment_params(frame: int, bsphere_radius: float):
Returns (azimuth_deg, elevation_deg, distance, lens_mm, use_dof). Returns (azimuth_deg, elevation_deg, distance, lens_mm, use_dof).
""" """
# Determine which segment (0-3) and local t (0-1) # Determine which segment and local t (0-1) — linear interpolation
seg_index = min((frame - 1) // SEGMENT_LENGTH, len(SEGMENTS) - 1) seg_index = min((frame - 1) // SEGMENT_LENGTH, len(SEGMENTS) - 1)
local_frame = (frame - 1) - seg_index * SEGMENT_LENGTH local_frame = (frame - 1) - seg_index * SEGMENT_LENGTH
raw_t = local_frame / max(SEGMENT_LENGTH - 1, 1) t = local_frame / max(SEGMENT_LENGTH - 1, 1) # linear, no easing
# Apply easing: smooth start for segment 1, smooth stop for segment 4,
# ease-in-out for segments 2 and 3
if seg_index == 0:
# Smooth start: ease-out (decelerate into motion)
t = _ease_in_out(raw_t)
elif seg_index == 3:
# Smooth stop: ease-in-out with emphasis on deceleration
t = _ease_in_out(raw_t)
else:
t = _ease_in_out(raw_t)
seg = SEGMENTS[seg_index] seg = SEGMENTS[seg_index]
@@ -409,7 +379,21 @@ def _setup_cinematic_camera(parts, bbox_center, bsphere_radius, total_frames):
cam_obj.data.dof.keyframe_insert(data_path="focus_distance", frame=frame) cam_obj.data.dof.keyframe_insert(data_path="focus_distance", frame=frame)
cam_obj.data.dof.keyframe_insert(data_path="aperture_fstop", frame=frame) cam_obj.data.dof.keyframe_insert(data_path="aperture_fstop", frame=frame)
print(f"[cinematic_render] camera keyframed: {total_frames} frames across 4 segments") # Set all keyframes to LINEAR interpolation (no bezier smoothing)
if cam_obj.animation_data and cam_obj.animation_data.action:
for fcurve in cam_obj.animation_data.action.fcurves:
for kp in fcurve.keyframe_points:
kp.interpolation = 'LINEAR'
if cam_obj.data.animation_data and cam_obj.data.animation_data.action:
for fcurve in cam_obj.data.animation_data.action.fcurves:
for kp in fcurve.keyframe_points:
kp.interpolation = 'LINEAR'
if cam_obj.data.dof.animation_data and cam_obj.data.dof.animation_data.action:
for fcurve in cam_obj.data.dof.animation_data.action.fcurves:
for kp in fcurve.keyframe_points:
kp.interpolation = 'LINEAR'
print(f"[cinematic_render] camera keyframed: {total_frames} frames across {len(SEGMENTS)} segments (LINEAR interpolation)", flush=True)
return cam_obj return cam_obj
@@ -828,13 +812,25 @@ def main():
scene.render.resolution_percentage = 100 scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG' scene.render.image_settings.file_format = 'PNG'
# ── Transparent background ─────────────────────────────────────────────── # ── White background (default for cinematic) ────────────────────────────
# Set world to white unless template provides its own
if not template_path:
world = bpy.data.worlds.new("CinematicWorld")
scene.world = world
world.use_nodes = True
bg_node = world.node_tree.nodes.get("Background")
if bg_node:
bg_node.inputs["Color"].default_value = (1.0, 1.0, 1.0, 1.0)
bg_node.inputs["Strength"].default_value = 1.0
print("[cinematic_render] white background set", flush=True)
# ── Transparent background (override if requested) ────────────────────
if bg_color or transparent_bg: if bg_color or transparent_bg:
scene.render.film_transparent = True scene.render.film_transparent = True
if bg_color: if bg_color:
print(f"[cinematic_render] film_transparent=True for FFmpeg bg_color compositing ({bg_color})") print(f"[cinematic_render] film_transparent=True for FFmpeg bg_color compositing ({bg_color})", flush=True)
else: else:
print("[cinematic_render] transparent_bg enabled (alpha PNG frames)") print("[cinematic_render] transparent_bg enabled (alpha PNG frames)", flush=True)
# ── Persistent data (Cycles BVH caching between frames) ────────────────── # ── Persistent data (Cycles BVH caching between frames) ──────────────────
scene.render.use_persistent_data = True scene.render.use_persistent_data = True