Blender scenes for Ring X, Wonder, PixelZ and Junior, CPU test renders, and resuming a stopped render
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
With frames, renders just those as stills to look at. Without, renders the whole 7 seconds as images.
|
||||
The logo goes on afterwards, in compose_ident.sh, so it stays perfectly sharp.
|
||||
"""
|
||||
import bpy, math, random, sys
|
||||
import bpy, math, os, random, sys
|
||||
from mathutils import Vector
|
||||
|
||||
args = sys.argv[sys.argv.index('--') + 1:]
|
||||
@@ -19,7 +19,8 @@ prefs = bpy.context.preferences.addons['cycles'].preferences
|
||||
prefs.compute_device_type = 'OPTIX'
|
||||
prefs.get_devices()
|
||||
for device in prefs.devices: device.use = device.type == 'OPTIX'
|
||||
scene.cycles.device = 'GPU'
|
||||
# CPU=1 renders on the processor, for test stills while the graphics card is busy with a film.
|
||||
scene.cycles.device = 'CPU' if os.environ.get('CPU') else 'GPU'
|
||||
scene.cycles.samples = 48
|
||||
scene.cycles.use_denoising = True
|
||||
scene.cycles.denoiser = 'OPTIX'
|
||||
@@ -946,6 +947,184 @@ def petals(count, seed, area):
|
||||
one.location = (x, y, z); one.rotation_euler = (rng.random() * 6, rng.random() * 6, rng.random() * 6); one.keyframe_insert('location', frame=1); one.keyframe_insert('rotation_euler', frame=1)
|
||||
one.location = (x + rng.uniform(1, 6), y + rng.uniform(-2, 2), z - rng.uniform(10, 22)); one.rotation_euler = (rng.random() * 12, rng.random() * 12, rng.random() * 12); one.keyframe_insert('location', frame=350); one.keyframe_insert('rotation_euler', frame=350)
|
||||
|
||||
def aim(obj, target, frame):
|
||||
"""Points a beam's tip at [target], keyed at [frame]."""
|
||||
obj.rotation_euler = (Vector(target) - obj.location).to_track_quat('Z', 'Y').to_euler(); obj.keyframe_insert('rotation_euler', frame=frame)
|
||||
|
||||
def fall_in(obj, frame, drop, settle=14):
|
||||
"""Drops from [drop] above into place, landing at [frame] with a small bounce."""
|
||||
rest = obj.location.copy()
|
||||
obj.location = rest + Vector((0, 0, drop)); obj.keyframe_insert('location', frame=1); obj.keyframe_insert('location', frame=max(2, frame - 24))
|
||||
obj.location = rest; obj.keyframe_insert('location', frame=frame)
|
||||
obj.location = rest + Vector((0, 0, drop * .04)); obj.keyframe_insert('location', frame=frame + settle // 2)
|
||||
obj.location = rest; obj.keyframe_insert('location', frame=frame + settle)
|
||||
|
||||
def wrestling_ring(at, mat_canvas, mat_apron, mat_post, mat_rope, mat_pad):
|
||||
x, y = at
|
||||
box('apron', x, y, 12.8, 12.8, 1.55, mat_apron); box('canvas', x, y, 12, 12, 1.6, mat_canvas)
|
||||
corners = [(x + a * 6, y + b * 6) for a, b in ((-1, -1), (1, -1), (1, 1), (-1, 1))]
|
||||
for i, (cx, cy) in enumerate(corners):
|
||||
box(f'post{i}', cx, cy, .36, .36, 4.6, mat_post)
|
||||
for j, z in enumerate((2.6, 3.4, 4.2)): box(f'pad{i}{j}', cx, cy, .6, .6, .32, mat_pad, z - .16)
|
||||
for i in range(4):
|
||||
(ax, ay), (bx, by) = corners[i], corners[(i + 1) % 4]
|
||||
for j, z in enumerate((2.6, 3.4, 4.2)): rope(f'rope{i}{j}', (ax, ay, z), (bx, by, z), mat_rope, sag=.06)
|
||||
|
||||
def stands(centre, inner, rows, mat, seed):
|
||||
"""Tiered seating all round, dark, with the odd phone light in the crowd."""
|
||||
rng = random.Random(seed); x0, y0 = centre
|
||||
lights = glowing('phones', '#DDE6FF', 6)
|
||||
for r in range(rows):
|
||||
reach = inner + r * 2.4
|
||||
for side, (dx, dy, w, d) in enumerate(((0, -1, 2, 0), (-1, 0, 0, 2), (1, 0, 0, 2))):
|
||||
box(f'tier{r}{side}', x0 + dx * reach, y0 + dy * reach, (reach * 2 if w else 2.4), (reach * 2 if d else 2.4), 1.2 + r * 1.1, mat)
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=.045); first = bpy.context.active_object; first.data.materials.append(lights)
|
||||
for i in range(260):
|
||||
one = first if i == 0 else clone(first)
|
||||
r = rng.randrange(rows); reach = inner + r * 2.4 + rng.uniform(-.8, .8); side = rng.choice((0, 2, 3)); along = rng.uniform(-reach, reach)
|
||||
px, py = (x0 + along, y0 + (reach if side else -reach)) if side < 2 else (x0 + (reach if side == 3 else -reach), y0 + along)
|
||||
one.location = (px, py, 1.4 + r * 1.1 + rng.uniform(.2, 1.4))
|
||||
|
||||
def ring_x():
|
||||
"""A wrestling arena: the ring under the lights, the crowd in the dark, and the entrance stage with its red X."""
|
||||
scene.world = bpy.data.worlds.new('arena'); scene.world.use_nodes = True; scene.world.node_tree.nodes['Background'].inputs['Color'].default_value = (.002, .002, .004, 1)
|
||||
floor = stone('floor', '#08080A', .5)
|
||||
box('floor', 0, 40, 400, 400, .1, floor, -.1)
|
||||
wrestling_ring((14, 40), stone('canvas', '#D4D6DC', .7), stone('apron', '#6A0810', .6), stone('steel', '#9EA2AA', .25, .9), stone('rope', '#E01428', .35), stone('pad', '#1A1A20', .5))
|
||||
stands((14, 40), 15, 9, stone('seats', '#0C0C12', .9), 3)
|
||||
# The entrance: a stage and ramp, a red X of light behind it, and sparks going up as the camera passes.
|
||||
box('stage', 44, 92, 30, 8, 1.2, stone('stage', '#121218', .4, .3)); box('ramp', 44, 72, 4, 32, .9, stone('ramp', '#16161E', .4, .3))
|
||||
red = glowing('xlight', '#FF1A30', 9)
|
||||
# The entrance glows for the walk in, then dims so the ring has the light.
|
||||
level = red.node_tree.nodes['Emission'].inputs['Strength']
|
||||
for frame, value in ((1, 9), (150, 9), (230, .2), (350, .2)): level.default_value = value; level.keyframe_insert('default_value', frame=frame)
|
||||
for i, lean in enumerate((1, -1)):
|
||||
bar = flat(f'xbar{i}', [[(-1.4, -13), (1.4, -13), (1.4, 13), (-1.4, 13)]], (44, 97, 15), (14, -50, 15), red); bar.rotation_euler.y = math.radians(36 * lean)
|
||||
# Gone once the lights are down, so it never sits behind the logo.
|
||||
bar.hide_render = False; bar.keyframe_insert('hide_render', frame=1); bar.hide_render = True; bar.keyframe_insert('hide_render', frame=232)
|
||||
for i, x in enumerate((32, 56)):
|
||||
sparks((x, 93, 1.2), 30 + i * 10, 90, 10 + i, 16); sparks((x, 93, 1.2), 190 + i * 10, 90, 20 + i, 16)
|
||||
# The rig over the ring, and its spotlights hunting round the canvas.
|
||||
rig = stone('rig', '#2A2C32', .4, .8)
|
||||
for i, (dx, dy, w, d) in enumerate(((0, -9, 18, .5), (0, 9, 18, .5), (-9, 0, .5, 18), (9, 0, .5, 18))):
|
||||
box(f'truss{i}', 14 + dx, 40 + dy, w, d, .5, rig, 26)
|
||||
for i, (dx, dy) in enumerate(((-9, -9), (9, -9), (9, 9), (-9, 9))):
|
||||
shaft = beam(f'spot{i}', (14 + dx, 40 + dy, 26), 30, 5, '#FFF2E0', .5, narrow=.12)
|
||||
shaft.rotation_mode = 'XYZ'
|
||||
for frame, (tx, ty) in ((1, (dx * .2, dy * -.6)), (120, (dx * -.5, dy * .3)), (240, (dx * .4, dy * .4)), (350, (dx * -.1, dy * -.1))):
|
||||
aim(shaft, (14 + tx, 40 + ty, -30), frame)
|
||||
data = bpy.data.lights.new('ringlight', 'SPOT'); data.energy = 260000; data.spot_size = math.radians(40); data.spot_blend = .5; data.color = hexrgb('#FFF4E8')[:3]
|
||||
top = bpy.data.objects.new('ringlight', data); bpy.context.collection.objects.link(top); top.location = (14, 40, 40); top.rotation_euler = (0, 0, 0)
|
||||
for i, x in enumerate((-60, 90)):
|
||||
data = bpy.data.lights.new(f'wash{i}', 'SPOT'); data.energy = 160000; data.spot_size = math.radians(30); data.color = hexrgb('#FF2438')[:3]
|
||||
wash = bpy.data.objects.new(f'wash{i}', data); bpy.context.collection.objects.link(wash); wash.location = (x, 60, 30); wash.rotation_euler = (Vector((14, 40, 0)) - wash.location).to_track_quat('-Z', 'Y').to_euler()
|
||||
camera_path([(1, (18, 112, 5), (14, 40, 3)), (120, (2, 70, 7), (12, 40, 3)), (285, (-7, 22, 7), (3, 43, 4)), (350, (-7, 21, 7), (3, 43, 4))], lens=28)
|
||||
|
||||
def fireflies(count, seed, area):
|
||||
rng = random.Random(seed)
|
||||
bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=2, radius=.1); first = bpy.context.active_object; first.data.materials.append(glowing('firefly', '#FFC24A', 22))
|
||||
for i in range(count):
|
||||
one = first if i == 0 else clone(first)
|
||||
x, y, z = rng.uniform(*area[0]), rng.uniform(*area[1]), rng.uniform(*area[2])
|
||||
for frame in (1, 120, 240, 350):
|
||||
one.location = (x + rng.uniform(-1.4, 1.4), y + rng.uniform(-1.4, 1.4), z + rng.uniform(-.8, .8)); one.keyframe_insert('location', frame=frame)
|
||||
|
||||
def wonder():
|
||||
"""A storybook night: hills, fireflies, and a castle with an observatory dome, its windows lit, under a big soft moon."""
|
||||
global world_tint
|
||||
world_tint = ('#101640', '#26306A')
|
||||
stars_world(.8)
|
||||
sun((.5, .6, -.5), 1.6, '#9AB4FF')
|
||||
hills = stone('hills', '#0E1C30', 1)
|
||||
rng = random.Random(7)
|
||||
for i in range(14):
|
||||
bump = sphere(f'hill{i}', 1, 32); bump.data.materials.append(hills); bump.location = (-300 + i * 50, 240 + rng.uniform(-40, 40), -4); bump.scale = (rng.uniform(50, 90), 50, rng.uniform(14, 36))
|
||||
near = sphere('rise', 1, 64); near.data.materials.append(stone('grass', '#16301E', 1)); near.location = (60, 150, -30); near.scale = (70, 60, 52)
|
||||
ground('meadow', '#0C1A14', '#14261C', y=200)
|
||||
wall, roof, lit = stone('castle', '#B8B4D4', .8), stone('roof', '#3A3A8A', .5), glowing('window', '#FFC870', 7)
|
||||
top = 22
|
||||
for i, (x, y, r, h) in enumerate(((46, 138, 4.6, 34), (74, 138, 4.6, 30), (60, 150, 6.2, 44), (50, 160, 3.6, 24), (70, 162, 3.6, 26))):
|
||||
bpy.ops.mesh.primitive_cylinder_add(vertices=40, radius=r, depth=h, location=(x, y, top + h / 2)); bpy.context.active_object.data.materials.append(wall)
|
||||
bpy.ops.mesh.primitive_cone_add(vertices=40, radius1=r * 1.25, radius2=0, depth=r * 2.6, location=(x, y, top + h + r * 1.3)); bpy.context.active_object.data.materials.append(roof)
|
||||
for k in range(int(h // 5)):
|
||||
box(f'win{i}{k}', x, y - r - .02, 1.3, .2, 2.2, lit, top + 3 + k * 5)
|
||||
box('keep', 60, 146, 30, 12, 20, wall, top)
|
||||
for k in range(6): box(f'keepwin{k}', 49 + k * 4.4, 139.9, 1.4, .2, 2.4, lit, top + 8)
|
||||
dome = sphere('dome', 7, 64); dome.location = (60, 150, top + 44 + 2); dome.data.materials.append(stone('gold', '#E8B850', .25, 1))
|
||||
bpy.ops.mesh.primitive_cylinder_add(vertices=24, radius=.6, depth=9, location=(56, 150, top + 50), rotation=(0, math.radians(-50), 0)); bpy.context.active_object.data.materials.append(stone('brass', '#C89A40', .3, 1))
|
||||
flare((96, 300, 120), 30, '#F4F6FF', 4)
|
||||
# A path of little lights winding up the hill to the gate.
|
||||
path_light = glowing('pathlight', '#FFD890', 5)
|
||||
for k in range(40):
|
||||
u = k / 39; bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=.22, location=(10 + 50 * u + 8 * math.sin(u * 7), 40 + 100 * u, .3 + 21 * u ** 1.6)); bpy.context.active_object.data.materials.append(path_light)
|
||||
fireflies(220, 5, ((-30, 60), (-30, 80), (.5, 12)))
|
||||
camera_path([(1, (-6, -40, 2), (20, 120, 8)), (285, (-16, 20, 10), (10, 150, 34)), (350, (-16, 22, 10), (10, 150, 34))], lens=30)
|
||||
|
||||
def pixel_world():
|
||||
"""A block world that drops into place, column by column, with gems spinning over it at sunset."""
|
||||
sky_world([(0, '#FF9AD0'), (.1, '#B55AE8'), (.3, '#4A2AA8'), (.55, '#120E40')])
|
||||
sun((.3, .7, -.6), 4.5, '#FFE0F0')
|
||||
flare((330, 400, 60), 30, '#FFB0E0', 2.6)
|
||||
ground('void', '#120E30', '#1A1440')
|
||||
m, n, l = material('column'); bsdf = n['Principled BSDF']; bsdf.inputs['Roughness'].default_value = .7
|
||||
up = n.new('ShaderNodeTexCoord'); part = n.new('ShaderNodeSeparateXYZ'); l.new(up.outputs['Generated'], part.inputs[0])
|
||||
layers = ramp(n, [(0, '#7A5038'), (.8, '#9A6644'), (.86, '#9AF04A'), (1, '#C8FF6A')]); layers.color_ramp.interpolation = 'CONSTANT'; l.new(part.outputs['Z'], layers.inputs['Fac']); l.new(layers.outputs['Color'], bsdf.inputs['Base Color'])
|
||||
water = stone('water', '#24C8FF', .1); water.node_tree.nodes['Principled BSDF'].inputs['Emission Color'].default_value = hexrgb('#24C8FF'); water.node_tree.nodes['Principled BSDF'].inputs['Emission Strength'].default_value = .5
|
||||
rng = random.Random(9)
|
||||
size = 2.0
|
||||
for gx in range(-6, 30):
|
||||
for gy in range(0, 30):
|
||||
x, y = gx * size, 20 + gy * size
|
||||
h = 1 + int(3.2 + 2.6 * math.sin(gx * .45) + 2.2 * math.cos(gy * .38 + gx * .12) + rng.random())
|
||||
if h < 3:
|
||||
box(f'w{gx}_{gy}', x, y, size, size, 2.4, water); continue
|
||||
column = box(f'c{gx}_{gy}', x, y, size, size, h * size * .5, m)
|
||||
land = 30 + int(math.hypot(gx - 12, gy - 2) * 7) + rng.randint(0, 8)
|
||||
for frame, height in ((1, .001), (land - 14, .001), (land, 1.12), (land + 8, 1)): column.scale.z = height; column.keyframe_insert('scale', index=2, frame=frame)
|
||||
for i, (x, y, z, colour) in enumerate(((22, 40, 16, '#FF4AE0'), (36, 56, 20, '#4AE8FF'), (12, 66, 18, '#B8FF4A'), (48, 34, 14, '#FFB84A'))):
|
||||
gem = sphere(f'gem{i}', 1.2, 4); gem.location = (x, y, z); gem.rotation_euler = (.6, .6, 0); gem.data.materials.append(glowing(f'gem{i}', colour, 5)); spin(gem, 'z', 1.5)
|
||||
gem.scale = (0, 0, 0); gem.keyframe_insert('scale', frame=150 + i * 12); gem.scale = (1, 1, 1); gem.keyframe_insert('scale', frame=170 + i * 12)
|
||||
camera_path([(1, (-26, -24, 30), (14, 50, 0)), (285, (-22, -4, 18), (4, 56, 6)), (350, (-22, -2, 18), (4, 56, 6))], lens=30)
|
||||
|
||||
def toy_blocks(at, seed):
|
||||
"""A tower of chunky toy blocks, dropped in one at a time."""
|
||||
rng = random.Random(seed); x0, y0 = at
|
||||
colours = ('#FF4A4A', '#FFC93A', '#3AB8FF', '#5AD46A', '#FF8A3D', '#B47DFF')
|
||||
mats = [stone(f'block{i}', c, .35) for i, c in enumerate(colours)]
|
||||
count = 0
|
||||
for level, (n_blocks, spread) in enumerate(((3, 3.2), (2, 1.6), (1, 0))):
|
||||
for k in range(n_blocks):
|
||||
x = x0 + (k - (n_blocks - 1) / 2) * 4 + rng.uniform(-.2, .2)
|
||||
block = box(f'toy{level}{k}', x, y0 + rng.uniform(-.3, .3), 3, 3, 3, mats[count % 6], level * 3)
|
||||
block.scale = (1.25, 1.25, 1.25); block.location.z *= 1.25
|
||||
bevel = block.modifiers.new('round', 'BEVEL'); bevel.width = .35; bevel.segments = 4
|
||||
block.rotation_euler.z = rng.uniform(-.2, .2)
|
||||
fall_in(block, 60 + count * 22, 30)
|
||||
count += 1
|
||||
|
||||
def junior():
|
||||
"""A sunny toy land: green hills, a rainbow, fluffy clouds, and a tower of building blocks stacking itself."""
|
||||
sky_world([(0, '#EAF8FF'), (.12, '#9AD8FF'), (.4, '#3A9AF0')])
|
||||
sun((-.4, .6, -.6), 3.4, '#FFF2D8')
|
||||
flare((150, 420, 110), 26, '#FFF0B0', 3)
|
||||
ground('lawn', '#5CC04C', '#72D45A')
|
||||
rng = random.Random(11)
|
||||
for i in range(12):
|
||||
bump = sphere(f'hill{i}', 1, 48); bump.data.materials.append(stone(f'hillc{i}', ('#5CC04C', '#4CAE44', '#6CCB5A')[i % 3], .9)); bump.location = (-200 + i * 40, 180 + rng.uniform(-30, 40), -6); bump.scale = (rng.uniform(34, 60), 34, rng.uniform(14, 30))
|
||||
for i, colour in enumerate(('#FF4A4A', '#FF8A3D', '#FFD93A', '#5AD46A', '#3AB8FF', '#9A6AFF')):
|
||||
bpy.ops.mesh.primitive_torus_add(major_radius=48 - i * 1.6, minor_radius=.85, major_segments=160, minor_segments=12, location=(56, 150, 0), rotation=(math.pi / 2, 0, 0))
|
||||
arc = bpy.context.active_object; arc.data.materials.append(glowing(f'rainbow{i}', colour, 1.4)); arc.visible_shadow = False
|
||||
cloud = stone('cloud', '#FFFFFF', 1)
|
||||
for c, (x, y, z) in enumerate(((-30, 120, 40), (20, 140, 52), (70, 110, 46), (110, 160, 36))):
|
||||
for k in range(6):
|
||||
puff = sphere(f'puff{c}{k}', 1, 32); puff.data.materials.append(cloud); r = rng.uniform(4, 7)
|
||||
puff.scale = (r, r * .8, r * .8); puff.location = (x + k * 4 - 10, y, z + rng.uniform(-1.5, 1.5) - abs(k - 2.5))
|
||||
puff.keyframe_insert('location', frame=1); puff.location.x += 8; puff.keyframe_insert('location', frame=350)
|
||||
toy_blocks((13, 26), 2)
|
||||
for i, (x, y) in enumerate(((10, 24), (34, 22), (16, 40))):
|
||||
ball = sphere(f'ball{i}', 1.3, 48); ball.location = (x, y, 1.3); ball.data.materials.append(stone(f'ballc{i}', ('#FF4A4A', '#3AB8FF', '#FFC93A')[i], .25))
|
||||
camera_path([(1, (-6, -40, 3), (20, 60, 8)), (285, (-16, -12, 6), (8, 60, 10)), (350, (-16, -10, 6), (8, 60, 10))], lens=30)
|
||||
|
||||
def archive():
|
||||
"""A long, dark records room: filing cabinets each side, strip lights failing overhead."""
|
||||
steel = stone('cabinet', '#3A4048', .45, .7); front = stone('drawer', '#4A5058', .5, .6); handle = stone('handle', '#B8BCC0', .3, 1); label = stone('label', '#D8D0B8', .8); red = glowing('stamp', '#C81828', 1.4)
|
||||
@@ -1249,6 +1428,14 @@ elif SCENE == 'orbit-detour':
|
||||
desert_road()
|
||||
flare((170, 980, 26), 30, '#FFD27A', 3)
|
||||
camera_path([(1, (0, -60, 2.2), (0, 60, 3)), (285, (-3, 116, 3.2), (6, 240, 15)), (350, (-3, 120, 3.2), (6, 240, 15))], lens=30)
|
||||
elif SCENE == 'orbit-ring-x':
|
||||
ring_x()
|
||||
elif SCENE == 'wonder':
|
||||
wonder()
|
||||
elif SCENE == 'orbit-pixelz':
|
||||
pixel_world()
|
||||
elif SCENE == 'orbit-junior':
|
||||
junior()
|
||||
else:
|
||||
raise SystemExit('unknown scene')
|
||||
|
||||
@@ -1259,4 +1446,6 @@ if FRAMES:
|
||||
bpy.ops.render.render(write_still=True)
|
||||
else:
|
||||
scene.render.filepath = f'{OUT}/frame_'
|
||||
# START_FRAME picks up a render that stopped part way, keeping the frames already made.
|
||||
scene.frame_start = int(os.environ.get('START_FRAME', scene.frame_start))
|
||||
bpy.ops.render.render(animation=True)
|
||||
|
||||
Reference in New Issue
Block a user