GLSLのゲーム「life」のエミュレーター

まず、小さな教育プログラム: onetwothree



おそらく、私の人生で少なくとも一度はゲームの「人生」のエミュレーターを書いたことが多いでしょう。

たぶん、プログラミングを学ぶため、たぶん楽しみのため、実験するために...

いずれにせよ、多くの一般的なプログラミング言語での実装は、この言語を習得するための簡単な練習です。



しかし、ゲーム自体のアルゴリズムは並列コンピューティングを使用して適切に実装されているため、今日はビデオカードを使用してこのようなエミュレータを実装しようとします。

シェーダーの言語であるOpenGL、GLSLをそれぞれ使用します。 メインプログラムはC ++で記述されます。



はじめに



それでは始めましょう。

まず、このゲームで「世代交代」がどのように発生するかを思い出しましょう。

各セルについて、その隣接するセルの数を調べます。 3で、元のセルが空の場合、セルは有効になります。 元のセルが生きていても、隣接セルの数が2または3でない場合、死にます。

明らかに、これらの条件を個別にチェックできるセルごとに、前のステップでセルの状態と8つの隣接セルの状態を知る必要があります。 さらに、次世代への移行は「同時に」行われるため、最も単純な実装では、この目的のために2つの2次元配列が使用されます。



もちろん、1つの配列を持つオプションがあります-生きて死ぬセルにマークを付けてから、配列内のデータを変更します。 この実装では、それぞれテクスチャ(= 2次元配列)を持つ2つのフレームバッファーを使用します。

たとえば、1つの配列を持つバリアントは機能しません。これは、レンダリング時に同じテクスチャを読み書きに割り当てることができないためです。 さらに、この方法では、配列を2回パスする必要があります。

したがって、気にすることはなく、単純なオプションを実装します。 現在、多くのビデオメモリがあります:)



初期化



フレームバッファとテクスチャ作成


最初に、2つのフレームバッファを作成し、それぞれにテクスチャを追加します。

単一バッファー初期化コード:



glGenFramebuffersEXT(1,&FrameBufferID);

glGenTextures(1,?ColorBufferID);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FrameBufferID );

glBindTexture(GL_TEXTURE_2D,ColorBufferID);

glTexImage2D(GL_TEXTURE_2D,0,4,SizeX,SizeY,0,RGBA,GL_UNSIGNED_BYTE,0);

glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D,ColorBufferID,0);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

glBindTexture(GL_TEXTURE_2D,0);

glDisable(GL_DEPTH_TEST);

glEnable(GL_TEXTURE_2D);

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);









ここで、フレームバッファとテクスチャを作成し、glTexImage2Dを使用して、テクスチャのサイズと形式を設定します(もちろん、これらの目的のために1コンポーネントテクスチャを作成し、レンダリング時にシェーダに転送し、データから画面にカラーピクセルを表示できますが、簡単にするために、同じテクスチャを出力するので、すぐにこのフォーマットを設定してください)。

次のglFramebufferTexture2DEXT関数は、テクスチャをフレームバッファにアタッチします。

次に、テクスチャパラメータを調整します-隣接するテクスチャピクセルから読み取るときにシェーダーのエラーを回避するために、最も近いフィルタリングを行います。



限定されたフィールド


ご存知のように、「ライフ」エミュレーターのコンピューター実装では、トロイダルフィールドが非常に頻繁に使用されます。 左端が右に閉じ、上が下になります。

ここで、GL_REPEATはこのようなフィールドの機能を提供します-テクスチャから読み取ろうとすると、エッジの外側の値は値を取得します-つまり まさに必要なもの。



フィールドセルの編集


glTexSubImage2D関数を使用すると、テクスチャ内のデータを簡単に変更できます。 セルが「生きている」ことに同意しましょう。そうすれば、すべてのRGBAコンポーネントは255に等しくなります。そうでなければ、すべて0に等しくなります。

次に、コードは1つのピクセルテクスチャを変更します。



unsigned char buf[4]={val,val,val,val}; // val is 0 or 255

glBindTexture(GL_TEXTURE_2D,ColorBufferID);

glTexSubImage2D(GL_TEXTURE_2D,0,x,y,1,1,GL_RGBA,GL_UNSIGNED_BYTE,buf);

glBindTexture(GL_TEXTURE_2D,0);









シェーダーの読み込み


シェーダーのロードコードはここでは提供しません。 この場合、それらは最も一般的な方法でロードされます。

ここでシェーダーとフレームバッファーの使用について読むことができます。



本体



実際、すべてが書かれたもののために。

ここでは、簡単にするために、テクスチャへの単一のレンダリングパスが指定されています。

したがって、2つのバッファーを交互に切り替える必要があります。一方から読み取り、他方に書き込みます。



glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, FrameBufferID );

glUseProgram ( ProgramObject );

float szx=sizex;

float szy=sizey;

glUniform1f( glGetUniformLocation (ProgramObject, "SizeX" ) , szx);

glUniform1f( glGetUniformLocation (ProgramObject, "SizeY" ) , szy);



glBindTexture(GL_TEXTURE_2D,ColorTextureID_2); //

glUniform1i( glGetUniformLocation (ProgramObject, "SourceTexture" ) , 0);



glViewport(0, 0, szx,szy);



glMatrixMode ( GL_PROJECTION );

glLoadIdentity ();

glOrtho ( 0, width, 0, height, -1, 1 );

glMatrixMode ( GL_MODELVIEW );

glLoadIdentity ();

DrawQuad(0,0,szx,szy,-1.0);



glBindTexture(GL_TEXTURE_2D,0);



glUseProgram ( 0 );

glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0 );









DrawQuad関数(単純にするためだけにFFPを使用しますが、もちろん、頂点バッファーなどを作成する必要があります)



void DrawQuad (float x,float y, float w, float h ,float z)

{

glBegin ( GL_QUADS );

glTexCoord2f ( 0, 0 );

glVertex3f ( x, y ,z);



glTexCoord2f ( 1, 0 );

glVertex3f ( x+w, y ,z);



glTexCoord2f ( 1, 1 );

glVertex3f ( x+w, y+h ,z);



glTexCoord2f ( 0, 1 );

glVertex3f ( x, y+h ,z);

glEnd ();

}









このコードでは、最初にフレームバッファーとシェーダーを現在のものとして割り当て、フィールドサイズとテクスチャを別のフレームバッファーからソースとしてのシェーダーに転送します。 次に、フルスクリーンで四角形を描画します。これにより、テストのすべてのポイントでシェーダーが開始されます。 その結果、テクスチャの現在のフレームバッファには、次世代の「ライフ」のフィールドの状態が含まれます。



そしてもちろん、シェーダーコードです!





頂点シェーダーでは特別なことは行いません。頂点変換は必要ありません。

void main(void)

{

gl_Position=ftransform(); //

gl_TexCoord[0] = gl_MultiTexCoord0; //

}









フラグメントシェーダー:

uniform sampler2D SourceTexture;



uniform float SizeX;

uniform float SizeY;



void main(void)

{

float deltax = 1.0/SizeX;

float deltay = 1.0/SizeY;

float Sum = texture2D(SourceTexture,gl_TexCoord[0].st+vec2( deltax, deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2(-deltax, deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2(-deltax,-deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2( deltax,-deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2( 0, deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2( 0,-deltay)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2(-deltax, 0)).r +

texture2D(SourceTexture,gl_TexCoord[0].st+vec2( deltax, 0)).r ;

float center =texture2D(SourceTexture,gl_TexCoord[0].st).r;

float koef=0.0;



if(center>0.5) // - , ,

{

if(Sum>3.5 || Sum<1.5)koef=0.0;else koef=1.0; // ,

}

else

{

if(Sum>2.5 && Sum<3.5)koef=1.0; // Sum==3,

}

gl_FragColor=vec4(1.0,1.0,1.0,1.0)*koef;

}









ソースコード



このプログラムは、Linux(Ubuntu 10.10)でのみ作成および起動されました。

SDLライブラリは、OpenGLコンテキストを初期化するために使用されます。

したがって、おそらくWindowsでコンパイルできます。

誰かがそれを集めてどこかに出すなら、どうもありがとう。

設定ファイルでは、画面の解像度、フィールドサイズ、およびフィールドの初期状態を変更できます。



narod.ru/disk/2930622001/LifeSim.zip.html



All Articles