GPGPUでライフゲーム
2017/06/23
glsl, openFrameworks
GPGPU Game of Life from rystylee on Vimeo.
ここ最近は人工生命に興味があり、卒業制作は人工生命(というか遺伝的アルゴリズム)をテーマに作品を制作しようと思っています。
そこで、勉強がてら有名なライフゲームをGPGPUで実装してみました。
ライフゲームの説明とか実装方法は調べればいくらでも出てくるので見てみてください〜〜
こちらのサイトではPythonのソースコードと共にライフゲームについて説明してくださっています。
・ライフゲーム – 人工知能に関する断創録
今回は最もシンプルな以下の4つのルールで実装しました。
・誕生…死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
・過疎…生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
・生存…生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
・過密…生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。
独自のオレオレルールを加えていくともっと面白くなりそうです。
ソースコードはこちらにアップしてます。
・https://github.com/rystylee/GPGPUGameOfLife
ライフゲームでは1フレーム前のセルの状態を保存しておく必要があるので、ピンポンバッファーで2つのFBOを切り替えながらセルの状態の保存・更新を行っています。
シェーダーで次の世代のセルの状態を計算してテスクチャに書き込み、oF側でそれを読み込み板ポリゴンに貼り付けて描画しています。
ここらへんの解説はこちらの記事がとても参考になると思います。
・GPGPU でパーティクルを大量に描く
以下、一部を抜粋して簡単な解説を加えます。
まずはpingPongBuffer.hppです。これは、oFのサンプルのexamples/gl/gpuParticleSystemExampleから持ってきました〜
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#pragma once #include "ofMain.h" struct pingPongBuffer { public: void allocate( int _width, int _height, int _internalformat = GL_RGBA){ // Allocate for(int i = 0; i < 2; i++){ FBOs[i].allocate(_width,_height, _internalformat ); FBOs[i].getTexture().setTextureMinMagFilter(GL_NEAREST, GL_NEAREST); } // Clean clear(); } void swap(){ std::swap(src,dst); } void clear(){ for(int i = 0; i < 2; i++){ FBOs[i].begin(); ofClear(0,255); FBOs[i].end(); } } ofFbo& operator[]( int n ){ return FBOs[n];} ofFbo *src = &FBOs[0]; // Source -> Ping ofFbo *dst = &FBOs[1]; // Destination -> Pong private: ofFbo FBOs[2]; // Real addresses of ping/pong FBO´s }; |
次にofAppです。setup()内でセルの初期状態を書き込んでいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#include "ofApp.h" //-------------------------------------------------------------- void ofApp::setup(){ ofSetBackgroundColor(0); ofSetFrameRate(60); ofEnableBlendMode(OF_BLENDMODE_ADD); updateLife.load("shaders/updateLife"); render.load("shaders/render"); cellWidth = ofGetWidth(); cellHeight = ofGetHeight(); vector<float> color(cellWidth * cellHeight * 3); for(int x=0; x<cellWidth; x++){ for(int y=0; y<cellHeight; y++){ int i = cellWidth * y + x; float coin = ofRandom(1.0) < 0.5 ? 0.0 : 1.0; color[i*3+0] = 0.0; color[i*3+1] = coin; color[i*3+2] = 0.0; } } pingPong.allocate(cellWidth, cellHeight, GL_RGB32F); pingPong.src->getTexture().loadData(color.data(), cellWidth, cellHeight, GL_RGB); pingPong.dst->getTexture().loadData(color.data(), cellWidth, cellHeight, GL_RGB); } //-------------------------------------------------------------- void ofApp::update(){ ofSetWindowTitle(ofToString(ofGetFrameRate())); pingPong.dst->begin(); ofClear(0); updateLife.begin(); updateLife.setUniformTexture("backbuffer", pingPong.src->getTexture(), 0); pingPong.src->draw(0, 0); updateLife.end(); pingPong.dst->end(); pingPong.swap(); } //-------------------------------------------------------------- void ofApp::draw(){ cam.begin(); ofPushMatrix(); ofTranslate(-ofGetWidth()/2, -ofGetHeight()/2); render.begin(); render.setUniformTexture("lifeTex", pingPong.dst->getTexture(), 0); ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight()); render.end(); ofPopMatrix(); cam.end(); } |
最後に、セルの状態を計算するupdateLifeシェーダのfragment shaderです。誕生、過疎、生存、過密の4つのルールに従い次の世代のセルの状態を決定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#version 150 precision mediump float; uniform sampler2DRect backbuffer; in vec2 vTexCoord; in vec2 vPos; out vec4 fragColor; void main(){ float life = texture(backbuffer, vPos).g; float count = 0.0; for(int x=-1; x<=1; x++){ for(int y=-1; y<=1; y++){ if(!(x == 0 && y == 0)){ count += texture(backbuffer, vPos+vec2(x,y)).g; } } } float nextlife = 0.0; if(life == 0.0){ if(count == 3.0){ nextlife = 1.0; } else { nextlife = 0.0; } } else { if(count == 0 || count == 1){ nextlife = 0.0; } else if(count == 2 || count == 3){ nextlife = 1.0; } else { nextlife = 0.0; } } fragColor = vec4(0.0, nextlife, 0.0, 1.0); } |
実際に描画する際には、セルの更新を終え結果が書き込まれたテクスチャをrenderシェーダで受け取りテクスチャ座標でサンプリングして描画しています。
全文は冒頭に記載したGitHubのリンクでご確認ください。
今回のコードでは、GPUを使ってセルの更新計算を行っているのでかなり高速に動作します。
是非皆さん他にも色々ルールを実装して遊んでみてください〜〜。ではでは。