読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

cocos2d-xで3Dプログラミング〜導入編2〜

今回のエンジニアブログを担当する安藤です。
前回のブログで次回は実践編と豪語してしまいましたが、
内容が導入編になってしまったので、導入編の続きとさせて頂きます。
今回はcocos2d-x内で自前のシェーダーを走らせてみたいと思います。
何かと便利なUtilityがそろっているcocos2d-xはシェーダーの学習用としても
優れているのではないかと思います。
今回使用するシェーダー言語はGLSL(OpenGL Shading Language) です。
OpenGL2.0になるに当たって正式サポートされたシェーダー言語になります。

では、環境から揃えていきます。前回同様新規プロジェクトを作成してください。
まずプロジェクトフォルダ内にシェーダー用のフォルダを用意し、
テキストエディターで空の.vsh/.fshのファイルを用意します。
文字コードはxcodeに対応しているutf-8で作ります。
1
2

用意したファイルをフォルダにドラッグ&ドロップして追加します。
3

ここで注意する点としてはデフォルトでは.vsh/.fshがxcodeのビルド対象になってしまうことです。
対処法としてはTARGETSのCompile Sourcesから.vsh/.fshを削除し、
4
Build PhasesのCopy Bundle Resourcesに.vsh/.fshを移します。
5
続いて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);  
}  

実行結果
6 7
しっかり描画できています。
今回はボックスのポリゴンで描画してみました。
cocos2d-xのアニメーションUtilityを使い色情報を書き換えています。
自前でシェーダーが書ける事により、
影を別のオブジェクトに有効にしたり、輝度を調節してHDRを実装したりと表現の自由度が向上します。
ただし、モバイル端末ごとにサポートしているバッファーフォーマットが
違うので実装したら各端末で確認する作業が必要になります。

2013/07/22 追記
HelloWorldScene.cppのglEnableVertexAttribArrayの引数に誤りがあっため修正

修正前

glEnableVertexAttribArray((GLuint)V_POS);  
glEnableVertexAttribArray((GLuint)V_UV);  

修正後

glEnableVertexAttribArray(ATTRIBUTE_POS);  
glEnableVertexAttribArray(ATTRIBUTE_UV);