🕌

三次元のライフゲームの可視化に苦労した話

2022/02/18に公開

最初に

前の記事で三次元のライフゲームの実装はできた。
https://zenn.dev/kaitoyama/articles/8d8ddacbbfe9e6

これを可視化するだけだから簡単だと思っていた。
リストになっているのだから、それを元にスケールを変化させるアニメーションをblender上でスクリプトで一気に作ればおしまい。
そう考えていた。
色々やって三分の一くらいまで短縮できたのでblender上でのスクリプト処理の高速化をしたい人の参考になれば嬉しい。


2022年3月1日 追記
この記事のコードだと二次元版と誕生の条件が変わっています。
後日それをそろえた記事も上げる予定です。


最終形

https://twitter.com/A4VhbmnuonHgbCN/status/1494638269346508802

最初のコード

一辺21の立方体を可視化しようとして

def set(scales):
    ob = bpy.context.object
    frame_num = 0
    frame_set=bpy.context.scene.frame_set
    keyframe_insert=ob.keyframe_insert

    for s in scales:
        frame_set(frame_num)
        if s==0:
            sc=(0,0,0)
        else:
            sc=(1,1,1)
        ob.scale = sc
        keyframe_insert(data_path = "scale",index = -1)
        frame_num += 20
        
all=numpy.loadtxt('途中経過の収容してあるcsvファイル')
size=21*21*21

cube_add=bpy.ops.mesh.primitive_cube_add

for counter in range(size):
    target=all[counter::size]
    cube_add(location=(21-counter//441, 21-(counter//21)%21, 21-counter%21), rotation=(0, 0, 0), size=1)
    set(target)
    print(counter)

print("end")

しかし、ここで思わぬ事態が起きた。
Blenderはオブジェクトの数が増えれば増えるほど、重くなるのである。
今回の場合最終的には9000個以上のキューブを作成するので、当然とても重くなり、4時間くらいかかってしまった。

ということで色々改善を施した。
やったことのメインは
bpyのコードを動かさず、不必要にオブジェクトを作らない

具体的な改善策

  1. 変化のないときにはキーを打たない
    最初のコードでは、毎フレーム、サイズのキーを打っていたが、これを変化した時以外は打たないようにした。
    if文が発生してしまうが、bpy絡みはそれよりも遥かに遅いので高速化につながる。
  2. それ以降消えたっきりなら、終わる
    地味にこれも大事だった。それ以降のフレームで消えたっきりならば、そこでforループを出ていくようにした。
  3. そもそも一度も登場しないなら、キューブを作らない
    キューブが増えるほど重くなるので、作らないようにした。実際に結果を見てみると初期状態にもよるが、およそ1000くらいはこの方法によってキューブが消えた。これが一番効果があったと思う。
  4. .での呼び出しを止める
    気紛らわしぐらいの効果しかないのだけれど、まぁないよりはマシだった。(圧倒的にbpyが重い。)

最終的なコード

アニメーションを少しゆっくりするようにしたという変更も入っている。

def set(scales):
    ob = bpy.context.object
    sum=numpy.sum
    frame_num = 0
    counter=0
    frame_set=bpy.context.scene.frame_set
    keyframe_insert=ob.keyframe_insert

    for s in scales:
        if sum(scales[counter:counter+2])==1 or sum(scales[counter-1:counter+1])==1:
            frame_set(frame_num)
            ob.scale = (s,s,s)
            keyframe_insert(data_path = "scale",index = -1)
            frame_num += 20
            frame_set(frame_num)
            keyframe_insert(data_path = "scale",index = -1)
            frame_num += 20
            
        else:
            frame_num += 40       
        if sum(scales[counter:])==0:
            #print("break")
            break
        counter+=1
        
all=numpy.loadtxt('途中経過の収容してあるcsvファイル')
size=21*21*21

cube_add=bpy.ops.mesh.primitive_cube_add
for counter in range(size):
    target=all[counter::size]
    if numpy.sum(target)==0:
        print("skip")
    else:
        temp=counter//21
        cube_add(location=(21-counter//441, 21-temp%21, 21-counter%21), rotation=(0, 0, 0), size=1)
        set(target)
        print(counter)

print("end")

これで大体1時間30分くらいにまで短くなった。

願望

blenderでおしゃれにレンダリングしようとするとオブジェクトの数が多いこともあってレンダリングに時間がかかる。これは諦めるしかないのだが、どうにかなってほしい。

Discussion