Blenderスクリプトでプロシージャルにいのちを輝かせる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/6c3f3655-d953-998a-e222-47b2e1e0fe35.png)

　Blenderのスクリプトで、万博で話題になった**アレのような何か**を生み出していきます。乱数を基にプロシージャルに生み出しているので、無数のパターンのアレを生み出せます。Blenderのバージョンは2.8です。

# 第1段階　球の生成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/1c0e57af-dcc9-0420-5e5b-e6d4e60559e5.png)

```python
import bpy
import numpy as np

#ここで赤マテリアルを設定
bpy.data.materials.new(name = 'red')
mat_red = bpy.data.materials['red']
mat_red.use_nodes = False
mat_red.diffuse_color = (1,0,0,1)

#球の生成
bpy.ops.mesh.primitive_uv_sphere_add(radius=1,location=(0,0,0))
bpy.ops.object.material_slot_add()
bpy.context.object.active_material=mat_red
```

　プリミティブの生成と簡単なマテリアルの指定で赤い球を生み出していきます。`bpy.ops.mesh.primitive_uv_sphere_add`でUV球を生み出すことができます。

# 第2段階　球の増殖

　この赤球を無数に生み出したいところですが、単なるfor文でやるよりも、何らかの連続性があった方がいのちが輝いてる感が出てくるので、ここは再帰関数を使って実装していくことにします。再帰関数とは自分自身をコピーしながら生み出してくようなプログラムのことです（超適当）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/3586beb1-768a-a5bb-3649-da1f6b9b6d9d.png)

```python
import bpy
import numpy as np

#ここで赤マテリアルを設定
bpy.data.materials.new(name = 'red')
mat_red = bpy.data.materials['red']
mat_red.use_nodes = False
mat_red.diffuse_color = (1,0,0,1)

def add_sphere(r,x,y,z,count):
    if count > 20: #これがないと無限ループ
        return
    bpy.ops.mesh.primitive_uv_sphere_add(radius=r,location=(x,y,z))
    bpy.ops.object.material_slot_add()
    bpy.context.object.active_material=mat_red
    add_sphere(r,x+1,y,z,count+1) #自分自身のコピーを呼び出す
    return

add_sphere(1,0,0,0,1)
```

　`add_sphere`という関数で球を生成していますが、この中で更に`add_sphere`という関数を呼び出しています。しかしまったく同じではなく、自分自身よりxに1ずれたコピーを生み出します。そのコピーはさらに1ずらした……という無限ループが発生し、連続性を持ったプロセスをシンプルに記述できます。これは単に横移動しただけですが、例えば`add_sphere(1.1*r,y,x+1,z,count+1)`のようなコマンドにすれば、以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/0e7635ce-4641-daf0-6712-017281129fa6.png)

　生命の神秘を感じますね。

# 第3段階　ランダム性を加える

　しかしこれだと規則的すぎていまいち輝きを感じません。元ネタを見ると、いくつもの大きさが異なる赤球がつらなってリング状になっているようです。やはり大きさに多少バリエーションがあった方が生命感が出るようです。お互いの発生位置も、親から子にぎりぎり接しながら伸びていくような形にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/192e07db-ecee-a897-7d2d-09abbd603e26.png)

```python
import bpy
import numpy as np #numpyを追加インポート

#ここで赤マテリアルを設定
bpy.data.materials.new(name = 'red')
mat_red = bpy.data.materials['red']
mat_red.use_nodes = False
mat_red.diffuse_color = (1,0,0,1)

def add_sphere(r,x,y,z,count):
    if count > 20: #これがないと無限ループ
        return
    bpy.ops.mesh.primitive_uv_sphere_add(radius=r,location=(x,y,z))
    bpy.ops.object.material_slot_add()
    bpy.context.object.active_material=mat_red
    #ランダムに経度と緯度を指定
    theta = np.random.rand()*np.pi*2
    phi = (np.random.rand()-0.5)*np.pi
    #新しい座標を指定
    nr = r*(0.5 + np.random.rand())#子の球は親の0.5倍～1.5倍
    nx = x+(r+nr)*np.cos(theta)*np.cos(phi)
    ny = y+(r+nr)*np.sin(theta)*np.cos(phi)
    nz = z+(r+nr)*np.sin(phi)
    add_sphere(nr,nx,ny,nz,count+1)#自分自身のコピーを呼び出す
    return

add_sphere(1,0,0,0,1)
```

#第4段階　目玉を加える

　キュートな目玉を加えていきます。テクスチャを設定できれば楽ですが、ここはプリミティブだけで描画することにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/981f4297-0afe-4c2d-6b22-0e81f2c52a36.png)

　だいぶ輝いてきました。あとは少しシェーディングを整えてレンダリングすれば、冒頭のようないのち輝く画像を生成することができます。

　コードは、ちょっと冗長な部分が多くなるので、一部を関数化して簡略化しています。

```python
import bpy
import numpy as np #numpyを追加インポート

#マテリアルの設定を3つ書くのは長くなるので関数化
def create_material(color_name,r,g,b):
    bpy.data.materials.new(name = color_name)
    mat = bpy.data.materials[color_name]
    mat.use_nodes = False
    mat.diffuse_color = (r,g,b,1)
    return mat

mat_red = create_material('red',1,0,0)
mat_white = create_material('white',1,1,1)
mat_eye = create_material('eye',0,1.5,1)

#座標の指定を3つ書くのは長くなるので一部関数化
def next_params(x,y,z,theta,phi,dist):
    nx = x + dist*np.cos(theta)*np.cos(phi)
    ny = y + dist*np.sin(theta)*np.cos(phi)
    nz = z + dist*np.sin(phi)
    return nx,ny,nz

#UV球の生成を3つ書くのは長くなるので一部関数化
def create_sphere(r,x,y,z,mat):
    bpy.ops.mesh.primitive_uv_sphere_add(radius=r,location=(x,y,z))
    bpy.ops.object.material_slot_add()
    bpy.context.object.active_material=mat
    return

#ついでに角度の生成も関数化
def create_angular():
    theta = np.random.rand()*np.pi*2
    phi = (np.random.rand()-0.5)*np.pi
    return theta,phi

def add_sphere(r,x,y,z,count):
    if count > 20: #これがないと無限ループ
        return
    create_sphere(r,x,y,z,mat_red)
    theta,phi = create_angular()
    nr = r*(0.5 + np.random.rand())
    nx,ny,nz = next_params(x,y,z,theta,phi,r+nr)
    add_sphere(nr,nx,ny,nz,count+1)#自分自身のコピーを呼び出す
    #白目を生成（これはただの関数）
    theta,phi = create_angular()
    nx,ny,nz = next_params(x,y,z,theta,phi,r/3)
    create_sphere(0.8*r,nx,ny,nz,mat_white)
    #瞳を生成（これはただの関数）
    nx,ny,nz = next_params(x,y,z,theta,phi,r/2)
    create_sphere(0.65*r,nx,ny,nz,mat_eye)
    return

add_sphere(1,0,0,0,1)
```

# 第5段階　パラメータ調整

　途中で二股に分かれるようにしたり、子の球のサイズを小さくするようにすると、また違った生命の美しさを見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/288125/88743506-b0f5-b7f8-38b8-570979ba6b13.png)

　興味のある方は、色々とパラメータを変えて遊んでみるのも良いかもしれません。ただし、`count > 20`のあたりを変更すると、プログラムの組み方によってはオブジェクト量が指数関数的に増えてBlenderがフリーズすることがあるのでご注意ください。