今回のエンジニアブログを担当する安藤です。
前回のブログで次回は実践編と豪語してしまいましたが、
内容が導入編になってしまったので、導入編の続きとさせて頂きます。
今回はcocos2d-x内で自前のシェーダーを走らせてみたいと思います。
何かと便利なUtilityがそろっているcocos2d-xはシェーダーの学習用としても
優れているのではないかと思います。
今回使用するシェーダー言語はGLSL(OpenGL Shading Language) です。
OpenGL2.0になるに当たって正式サポートされたシェーダー言語になります。
では、環境から揃えていきます。前回同様新規プロジェクトを作成してください。
まずプロジェクトフォルダ内にシェーダー用のフォルダを用意し、
テキストエディターで空の.vsh/.fshのファイルを用意します。
文字コードはxcodeに対応しているutf-8で作ります。
用意したファイルをフォルダにドラッグ&ドロップして追加します。
ここで注意する点としてはデフォルトでは.vsh/.fshがxcodeのビルド対象になってしまうことです。
対処法としてはTARGETSのCompile Sourcesから.vsh/.fshを削除し、
Build PhasesのCopy Bundle Resourcesに.vsh/.fshを移します。
続いてGLSLについての要点だけ説明します。
詳しくはGLSLの入門書を参考にしてください。
● .vsh:頂点シェーダー
● .fsh:フラグメントシェーダー(HLSLでのピクセルシェーダー)
● attribute:頂点情報のやりとり
● uniform:行列情報など各頂点に適用するデータのやりとり
● varying:.vshと.fsh間でのデータやりとり
では、実際にGLSLのコーディングします。
GLSLはC言語に似た言語となっており、そのエントリーポイントも
main()関数からはじまります。
頂点シェーダーのコーディングです
attributeには位置とUV値が入ります。
uniformには行列情報と色情報が入ります。
varyingは算出した色情報とUV値をフラグメントシェーダーに送信します。
shader.vsh
attribute vec4 pos;
attribute vec2 uv;
varying lowp vec4 varying_color;
varying mediump vec2 varying_uv;
uniform mat4 WVP;
uniform vec4 diffuse;
void main()
{
varying_color = diffuse;
varying_uv = uv;
gl_Position = WVP * pos;
}
フラグメントシェーダーのコーディングです。
UV情報からテクスチャカラーを算出しvaryingで受け取った色を掛け合わせています。
shader.fsh
varying lowp vec4 varying_color;
varying mediump vec2 varying_uv;
uniform sampler2D sampler;
void main()
{
gl_FragColor = texture2D(sampler,varying_uv) * varying_color;
}
前回同様テクスチャを描画インタフェースを宣言します。
またuniformのロケーションを保管しておくstd::mapも宣言します。
HelloWorldScene.h
class HelloWorld : public cocos2d::CCLayer { public: ・・・ cocos2d::CCTexture2D* m_pTexture; std::map<int, int> m_uniform_map; virtual void draw(); };
描画に使うプリミティブを定義します。
同時にattributeのindexとuniformのindexを定義します。
HelloWorldScene.cpp
// プリミティブデータ const int VERTEX_CNT=6*6; const int POS_SIZE=VERTEX_CNT*3; const int UV_SIZE=VERTEX_CNT*2; const GLfloat V_POS[POS_SIZE]={ 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, -0.5f, -0.5f, -0.5f, 0.5f, -0.5f, }; const GLfloat V_UV[UV_SIZE]={ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; // attributeのindex enum { ATTRIBUTE_POS, ATTRIBUTE_UV, }; // uniformのindex enum { WVP, DIFFUSE, SAMPLER, };
コーディングしたGLSLをコンパイルして使用します。
注意点としてはupdateUnifoms()の前にattributeをバインドしておくことです。
また今回はcocos2d-xのアニメーションUtilityを使用して3Dポリゴンに
カラーアニメーションを実装してみたいと思います。
HelloWorldScene.cpp
bool HelloWorld::init() { ・・・ // シェーダーの読み込み CCGLProgram* pProgram = new CCGLProgram(); pProgram->autorelease(); pProgram->initWithVertexShaderFilename("shader.vsh","shader.fsh"); this->setShaderProgram(pProgram); // 頂点情報のバインド glBindAttribLocation(pProgram->getProgram(), ATTRIBUTE_POS, "pos"); glBindAttribLocation(pProgram->getProgram(), ATTRIBUTE_UV, "uv"); pProgram->link(); pProgram->updateUniforms(); // 頂点に付加する情報のロケーションを取得 m_uniform_map[WVP] = glGetUniformLocation(pProgram->getProgram(),"WVP"); m_uniform_map[DIFFUSE] = glGetUniformLocation(pProgram->getProgram(),"diffuse"); m_uniform_map[SAMPLER] = glGetUniformLocation(pProgram->getProgram(),"sampler"); // テクスチャ生成 m_pTexture = CCTextureCache::sharedTextureCache()->addImage("HelloWorld.png"); // カラーの変更 背景のカラー変更を3Dポリゴンに利用する float time=1; pSprite->runAction(CCRepeat::create(CCSequence::create( CCSpawn::create(CCFadeTo::create(time,64),CCTintTo::create(time,255,255,255),NULL) ,CCSpawn::create(CCFadeTo::create(time,255),CCTintTo::create(time,255,0,0),NULL) ,NULL),999999)); pSprite->setVisible(false); // 背景は消す return true; }
3Dポリゴンを描画します。
前回と違い自前でindexやunifromロケーションを用意し使用しています。
HelloWorldScene.cpp
void HelloWorld::draw(){ // 深度テスト有効 CCDirector::sharedDirector()->setDepthTest(true); // 頂点に座標とテクスチャUVのindex指定 glEnableVertexAttribArray(ATTRIBUTE_POS); glEnableVertexAttribArray(ATTRIBUTE_UV); // 設定したシェーダーを使用する this->getShaderProgram()->use(); // 行列の作成 static float yaw = 0; static float pitch = 0; yaw += 0.01f; pitch += 0.01f; kmMat4 matProjection; kmMat4 matView; kmMat4 matWVP; kmMat4 matTrans,matScale,matRota,matWorld; kmGLGetMatrix(KM_GL_PROJECTION, &matProjection ); // 射影行列を取得 kmGLGetMatrix(KM_GL_MODELVIEW, &matView ); // ビュー行列の取得 kmMat4RotationPitchYawRoll(&matRota, pitch, yaw, 0); kmMat4Translation(&matTrans, 250, 150, 100); kmMat4Scaling(&matScale, 50,50,50); kmMat4Multiply(&matWVP, &matProjection, &matView); kmMat4Multiply(&matWorld, &matTrans, &matRota); kmMat4Multiply(&matWorld, &matWorld, &matScale); kmMat4Multiply(&matWVP, &matWVP, &matWorld); // 背景のカラーを取得する ccColor4F color = ccc4FFromccc3B(((CCSprite*)this->getChildByTag(0))->getColor()); // 作成した行列をシェーダーに送る glUniformMatrix4fv(m_uniform_map[WVP],1, 0, (float *)&matWVP.mat); // 作成したカラーをシェーダーに送る glUniform4fv(m_uniform_map[DIFFUSE], 1, (float *)&color); // テクスチャサンプラ情報をシェーダーに送る glUniform1i(m_uniform_map[SAMPLER], 0); // テクスチャのバインド ccGLBindTexture2D( m_pTexture->getName() ); // 頂点をセット glVertexAttribPointer(ATTRIBUTE_POS, 3, GL_FLOAT, GL_FALSE, 0, V_POS); glVertexAttribPointer(ATTRIBUTE_UV, 2, GL_FLOAT, GL_FALSE, 0, V_UV); // 描画 glDrawArrays(GL_TRIANGLES, 0, VERTEX_CNT); CC_INCREMENT_GL_DRAWS(1); }
実行結果
しっかり描画できています。
今回はボックスのポリゴンで描画してみました。
cocos2d-xのアニメーションUtilityを使い色情報を書き換えています。
自前でシェーダーが書ける事により、
影を別のオブジェクトに有効にしたり、輝度を調節してHDRを実装したりと表現の自由度が向上します。
ただし、モバイル端末ごとにサポートしているバッファーフォーマットが
違うので実装したら各端末で確認する作業が必要になります。
2013/07/22 追記
HelloWorldScene.cppのglEnableVertexAttribArrayの引数に誤りがあっため修正
修正前
glEnableVertexAttribArray((GLuint)V_POS); glEnableVertexAttribArray((GLuint)V_UV);
修正後
glEnableVertexAttribArray(ATTRIBUTE_POS); glEnableVertexAttribArray(ATTRIBUTE_UV);