2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ごう.gif

Blenderを始めてたった2日。Pythonでここまでできた!?

Pythonは、「データ分析」とか「スクレイピング」みたいな業務で使うことが多かったのですが、
まさかポップコーンが飛び出す3DCGを作れるとは。

ChatGPTに教えてもらったものをベースに、blenderの機能使って簡単に編集したものです

追加事項
・容器の色をしましま模様にする
・平面を追加
・縁にベベルをかける

目標🍿

ポップコーンがはじけ飛んでいる3DCGをpythonで作る

① お、ポップコーンだ。でも容器が‥

まずは、ベースとなる素材を依頼しました

いち.gif

<ChatGPT依頼内容>
アメリカで食べれそうなポップコーンを描いてそれを弾けさせて。blender×pythonコードを書いて欲しい。ポップコーンはリアルを追求させて、ボコボコしていて、サイズもバラバラで、量も大量にして欲しい

全体コードはこちら↓
python
import bpy
import random
import math

# --------------------------------------
# 0. 初期化:全削除
# --------------------------------------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# --------------------------------------
# 1. 中が凹んだバケツを作成
# --------------------------------------
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=3, depth=3, location=(0, 0, 1.5))
box = bpy.context.active_object
box.name = "PopcornBucket"

# 内側に厚み
solidify = box.modifiers.new(name="Solidify", type='SOLIDIFY')
solidify.thickness = -0.3
solidify.offset = 1

# スムーズに
subsurf = box.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2

# 上部を凹ませる
displace = box.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("BowlShape", type='BLEND')
tex.use_color_ramp = True
tex.progression = 'SPHERICAL'
displace.texture = tex
displace.direction = 'Z'
displace.strength = -0.3

# 丸みを出す
cast = box.modifiers.new(name="Cast", type='CAST')
cast.cast_type = 'SPHERE'
cast.factor = 0.2

# 適用
bpy.ops.object.select_all(action='DESELECT')
box.select_set(True)
bpy.context.view_layer.objects.active = box
bpy.ops.object.convert(target='MESH')

# --------------------------------------
# 2. ポップコーンベース作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"

# ノイズ変形
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4

# スムーズ化
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2

# 適用
bpy.ops.object.select_all(action='DESELECT')
popcorn_base.select_set(True)
bpy.context.view_layer.objects.active = popcorn_base
bpy.ops.object.convert(target='MESH')

# --------------------------------------
# 3. ポップコーン複製(ランダム配置)
# --------------------------------------
num_corns = 120
popcorns = []

for i in range(num_corns):
    obj = popcorn_base.copy()
    obj.data = popcorn_base.data.copy()
    obj.location = (
        random.uniform(-2.5, 2.5),
        random.uniform(-2.5, 2.5),
        random.uniform(0.2, 2.0)
    )
    scale = random.uniform(0.5, 1.2)
    obj.scale = (
        scale * random.uniform(0.9, 1.1),
        scale * random.uniform(0.9, 1.1),
        scale * random.uniform(0.9, 1.2)
    )
    bpy.context.collection.objects.link(obj)
    popcorns.append(obj)

# ベース削除
bpy.data.objects.remove(popcorn_base, do_unlink=True)

# --------------------------------------
# 4. 弾けるアニメーション(半分だけ)
# --------------------------------------
for i, obj in enumerate(popcorns):
    if i % 2 == 0:
        continue  # バケツに残る半分

    start = random.randint(1, 20)
    end = start + 20
    jump_height = random.uniform(2.5, 5.5)

    obj.keyframe_insert(data_path="location", frame=start)
    obj.location.z += jump_height
    obj.keyframe_insert(data_path="location", frame=end)

    obj.rotation_euler = (0, 0, 0)
    obj.keyframe_insert(data_path="rotation_euler", frame=start)
    obj.rotation_euler = (
        random.uniform(0, math.pi * 2),
        random.uniform(0, math.pi * 2),
        random.uniform(0, math.pi * 2)
    )
    obj.keyframe_insert(data_path="rotation_euler", frame=end)

# --------------------------------------
# 5. マテリアル(ポップコーン色)
# --------------------------------------
mat = bpy.data.materials.new(name="PopcornMat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.8

for obj in popcorns:
    if not obj.data.materials:
        obj.data.materials.append(mat)

② あれ、貝みたい

容器の形を変化と、弾けなかったポップコーンを表現したく依頼しました

に.gif

<ChatGPT依頼内容>
箱があってそこから弾け飛ぶイメージ。  箱は、角を取って凹ませてバケツっぽくして欲しい。またポップコーンを弾けさせると同時にちょっとは箱の中に残して欲しい。リアルな箱+弾けポップコーン+残るポップコーン

全体コードはこちら↓
python
import bpy
import random
import math

# --------------------------------------
# 0. 初期化(全削除)
# --------------------------------------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# --------------------------------------
# 1. 箱を作成(ボウル代わり)
# --------------------------------------
bpy.ops.mesh.primitive_cube_add(size=6, location=(0, 0, 0))
box = bpy.context.active_object
box.name = "PopcornBox"
box.scale.z = 0.5
bpy.ops.object.transform_apply(scale=True)

# 箱を非表示にするならここで box.hide_render = True

# --------------------------------------
# 2. ポップコーンベース作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 0.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"

# ノイズ変形
displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4

# サブディビジョン
subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2

# 適用
bpy.ops.object.select_all(action='DESELECT')
popcorn_base.select_set(True)
bpy.context.view_layer.objects.active = popcorn_base
bpy.ops.object.convert(target='MESH')

# --------------------------------------
# 3. ランダムに大量複製(箱の中)
# --------------------------------------
num_corns = 120
popcorns = []

for _ in range(num_corns):
    obj = popcorn_base.copy()
    obj.data = popcorn_base.data.copy()
    obj.location = (
        random.uniform(-2.5, 2.5),
        random.uniform(-2.5, 2.5),
        random.uniform(0, 1.0)
    )
    scale = random.uniform(0.5, 1.2)
    obj.scale = (
        scale * random.uniform(0.9, 1.1),
        scale * random.uniform(0.9, 1.1),
        scale * random.uniform(0.9, 1.2)
    )
    bpy.context.collection.objects.link(obj)
    popcorns.append(obj)

# 元は削除
bpy.data.objects.remove(popcorn_base, do_unlink=True)

# --------------------------------------
# 4. 弾けるアニメーション(箱から飛び出す)
# --------------------------------------
for obj in popcorns:
    start = random.randint(1, 20)
    end = start + 20
    jump_height = random.uniform(3.0, 5.5)

    # 位置
    obj.keyframe_insert(data_path="location", frame=start)
    obj.location.z += jump_height
    obj.keyframe_insert(data_path="location", frame=end)

    # 回転
    obj.rotation_euler = (0, 0, 0)
    obj.keyframe_insert(data_path="rotation_euler", frame=start)
    obj.rotation_euler = (
        random.uniform(0, math.pi * 2),
        random.uniform(0, math.pi * 2),
        random.uniform(0, math.pi * 2)
    )
    obj.keyframe_insert(data_path="rotation_euler", frame=end)

# --------------------------------------
# 5. マテリアル設定(ポップコーン色)
# --------------------------------------
mat = bpy.data.materials.new(name="PopcornMat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.8

for obj in popcorns:
    if not obj.data.materials:
        obj.data.materials.append(mat)

③ ダーク感強め

容器っぽくなりましたが、ポップコーンに躍動感がなくなりました

さん.gif

<ChatGPT依頼内容>
(ポップコーンの箱のURL)URLみたいな容器にしてほしい。そこからポップコーンが飛び出るようなアニメーションにして欲しい。

全体コードはこちら↓
python
import bpy
import random
import math

# --------------------------------------
# 0. 初期化:全削除
# --------------------------------------
if bpy.ops.object.mode_set.poll():
    bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# --------------------------------------
# 1. 紙製ポップコーンボックス作成(赤白ストライプ付き)
# --------------------------------------
# 下が細く、上が広がった箱
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 1))
box = bpy.context.active_object
box.name = "PopcornBox"
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.transform.resize(value=(1.2, 1.2, 1))
bpy.ops.object.mode_set(mode='OBJECT')

# 斜めに広げる(上だけスケール)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_mode(type="VERT")
bpy.ops.object.mode_set(mode='OBJECT')
mesh = box.data

for v in mesh.vertices:
    if v.co.z > 0:
        v.co.x *= 1.2
        v.co.y *= 1.2

bpy.ops.object.mode_set(mode='OBJECT')

# Solidifyで厚み
solidify = box.modifiers.new(name="Solidify", type='SOLIDIFY')
solidify.thickness = 0.05
solidify.offset = 1

# 赤白ストライプマテリアル
mat = bpy.data.materials.new(name="StripeMaterial")
mat.use_nodes = True
nodes = mat.node_tree.nodes
links = mat.node_tree.links
bsdf = nodes.get("Principled BSDF")

# 既存ノード除去
for node in nodes:
    if node.name != "Principled BSDF":
        nodes.remove(node)

# テクスチャ座標 → 分離XYZ → 数学(mod)でストライプ作成
tex_coord = nodes.new(type='ShaderNodeTexCoord')
separate_xyz = nodes.new(type='ShaderNodeSeparateXYZ')
math_mod = nodes.new(type='ShaderNodeMath')
math_mod.operation = 'MODULO'
math_mod.inputs[1].default_value = 0.3  # ストライプ幅

color_ramp = nodes.new(type='ShaderNodeValToRGB')
color_ramp.color_ramp.elements[0].color = (1, 0, 0, 1)  # 赤
color_ramp.color_ramp.elements[1].color = (1, 1, 1, 1)  # 白

# 接続
links.new(tex_coord.outputs['Object'], separate_xyz.inputs[0])
links.new(separate_xyz.outputs['X'], math_mod.inputs[0])
links.new(math_mod.outputs[0], color_ramp.inputs[0])
links.new(color_ramp.outputs[0], bsdf.inputs['Base Color'])

box.data.materials.append(mat)

# --------------------------------------
# 2. ポップコーン作成(ボコボコ球)
# --------------------------------------
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"

displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4

subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2

bpy.ops.object.convert(target='MESH')

# --------------------------------------
# 3. ポップコーンをボックス内に複製配置
# --------------------------------------
popcorns = []
num_corns = 120

for i in range(num_corns):
    obj = popcorn_base.copy()
    obj.data = popcorn_base.data.copy()
    obj.location = (
        random.uniform(-0.6, 0.6),
        random.uniform(-0.6, 0.6),
        random.uniform(0.3, 1.8)
    )
    scale = random.uniform(0.5, 1.0)
    obj.scale = (scale, scale, scale * random.uniform(0.8, 1.2))
    bpy.context.collection.objects.link(obj)
    popcorns.append(obj)

bpy.data.objects.remove(popcorn_base, do_unlink=True)

# --------------------------------------
# 4. 弾けるアニメーション(上方向にジャンプ)
# --------------------------------------
for i, obj in enumerate(popcorns):
    if i % 2 == 0:
        continue
    start = random.randint(1, 20)
    end = start + 20
    height = random.uniform(2.5, 5)

    obj.keyframe_insert(data_path="location", frame=start)
    obj.location.z += height
    obj.keyframe_insert(data_path="location", frame=end)

    obj.rotation_euler = (0, 0, 0)
    obj.keyframe_insert(data_path="rotation_euler", frame=start)
    obj.rotation_euler = (
        random.uniform(0, math.pi),
        random.uniform(0, math.pi),
        random.uniform(0, math.pi)
    )
    obj.keyframe_insert(data_path="rotation_euler", frame=end)

# --------------------------------------
# 5. マテリアル(ポップコーン色)
# --------------------------------------
pop_mat = bpy.data.materials.new(name="PopcornMat")
pop_mat.use_nodes = True
bsdf = pop_mat.node_tree.nodes.get("Principled BSDF")
bsdf.inputs["Base Color"].default_value = (1.0, 0.9, 0.6, 1)
bsdf.inputs["Roughness"].default_value = 0.7

for obj in popcorns:
    if not obj.data.materials:
        obj.data.materials.append(pop_mat)

④ 完成

お、できた

よん.gif

<ChatGPT依頼内容>
ポップコーンがランダムな位置からジャンプし、頂点で跳ねた後、落下して箱の外に落ちていく感じ。弧を描くように。
ポップコーンにあたる部分はもっといろんなところに散らばる感じで。でも箱の中にはちょっと残っていてリアルな感じで書いて欲しい 

全体コードはこちら↓
python
import bpy
import random
import math

# 0. 初期化
if bpy.ops.object.mode_set.poll():
    bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# 1. 箱作成
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1.2, depth=2.5, location=(0, 0, 1.25))
box = bpy.context.active_object
box.name = "PopcornBox"

bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1.0, depth=2.4, location=(0, 0, 1.25))
inner = bpy.context.active_object
inner.name = "PopcornBoxInner"

bool_mod = box.modifiers.new(type='BOOLEAN', name='BooleanInner')
bool_mod.operation = 'DIFFERENCE'
bool_mod.object = inner
bpy.context.view_layer.objects.active = box
bpy.ops.object.modifier_apply(modifier='BooleanInner')

bpy.data.objects.remove(inner, do_unlink=True)

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')

bpy.context.tool_settings.use_proportional_edit = True
bpy.context.tool_settings.proportional_edit_falloff = 'SMOOTH'
bpy.ops.transform.resize(value=(1.1, 1.1, 1), orient_type='GLOBAL', proportional_size=2)
bpy.context.tool_settings.use_proportional_edit = False
bpy.ops.object.mode_set(mode='OBJECT')

mat_box = bpy.data.materials.new(name="BoxMat")
mat_box.use_nodes = True
bsdf_box = mat_box.node_tree.nodes.get("Principled BSDF")
bsdf_box.inputs["Base Color"].default_value = (1.0, 1.0, 1.0, 1)
bsdf_box.inputs["Roughness"].default_value = 0.8
box.data.materials.append(mat_box)

# 2. ポップコーンベース作成
bpy.ops.mesh.primitive_uv_sphere_add(radius=0.2, location=(0, 0, 1.5))
popcorn_base = bpy.context.active_object
popcorn_base.name = "PopcornBase"

displace = popcorn_base.modifiers.new(name="Displace", type='DISPLACE')
tex = bpy.data.textures.new("PopcornNoise", type='CLOUDS')
displace.texture = tex
displace.strength = 0.4

subsurf = popcorn_base.modifiers.new(name="Subsurf", type='SUBSURF')
subsurf.levels = 2

bpy.ops.object.convert(target='MESH')

# 3. ポップコーン配置
popcorns = []
num_popcorn = 160

for i in range(num_popcorn):
    obj = popcorn_base.copy()
    obj.data = popcorn_base.data.copy()
    obj.location = (
        random.uniform(-0.7, 0.7),
        random.uniform(-0.7, 0.7),
        random.uniform(0.3, 2.0)
    )
    scale = random.uniform(0.4, 1.0)
    obj.scale = (
        scale,
        scale,
        scale * random.uniform(0.8, 1.2)
    )
    bpy.context.collection.objects.link(obj)
    popcorns.append(obj)

bpy.data.objects.remove(popcorn_base, do_unlink=True)

# 4. ポップコーンアニメーション(弧を描いて箱外に落ちる)
ground_z = 0.0

for obj in popcorns:
    start_frame = random.randint(1, 30)
    peak_frame = start_frame + random.randint(10, 20)
    end_frame = peak_frame + random.randint(15, 25)

    base_x, base_y, base_z = obj.location

    obj.location = (base_x, base_y, base_z)
    obj.keyframe_insert(data_path="location", frame=start_frame)

    jump_x = base_x + random.uniform(-1.5, 1.5)
    jump_y = base_y + random.uniform(-1.5, 1.5)
    jump_z = base_z + random.uniform(4.0, 7.0)

    obj.location = (jump_x, jump_y, jump_z)
    obj.keyframe_insert(data_path="location", frame=peak_frame)

    land_x = jump_x + random.uniform(-1.0, 1.0)
    land_y = jump_y + random.uniform(-1.0, 1.0)
    land_z = ground_z

    obj.location = (land_x, land_y, land_z)
    obj.keyframe_insert(data_path="location", frame=end_frame)

    obj.rotation_euler = (0, 0, 0)
    obj.keyframe_insert(data_path="rotation_euler", frame=start_frame)

    rot_end = (
        random.uniform(0, math.pi * 6),
        random.uniform(0, math.pi * 6),
        random.uniform(0, math.pi * 6)
    )
    obj.rotation_euler = rot_end
    obj.keyframe_insert(data_path="rotation_euler", frame=end_frame)

# 5. ポップコーンマテリアル
mat_pop = bpy.data.materials.new(name="PopcornMat")
mat_pop.use_nodes = True
bsdf_pop = mat_pop.node_tree.nodes.get("Principled BSDF")
bsdf_pop.inputs["Base Color"].default_value = (1.0, 0.9, 0.5, 1)
bsdf_pop.inputs["Roughness"].default_value = 0.7

for obj in popcorns:
    if not obj.data.materials:
        obj.data.materials.append(mat_pop)

# 6. カメラ・ライト・背景
bpy.ops.object.camera_add(location=(0, -7, 5), rotation=(math.radians(75), 0, 0))
bpy.context.scene.camera = bpy.context.active_object

bpy.ops.object.light_add(type='AREA', location=(0, 0, 8))
light = bpy.context.active_object
light.data.energy = 1000

bpy.context.scene.world.use_nodes = True
bg = bpy.context.scene.world.node_tree.nodes["Background"]
bg.inputs[0].default_value = (1, 1, 1, 1)

# 7. レンダリング設定
scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.frame_end = 80

最後に

まだまだ完璧とは言えませんが、Pythonだけでもアニメーションのベースはしっかり作れることが分かりました。
あとはコードの動きをもっと理解して、ロジックを自分で組み立てられるようになれば、さらに表現の幅は広がりそうです。

3DCGの分野にも、Pythonの活躍の場がしっかりある。
そんな可能性を感じたチャレンジでした!

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?