エンジニア

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

投稿日:2013年7月16日 更新日:

今回のエンジニアブログを担当する安藤です。
前回のブログで次回は実践編と豪語してしまいましたが、
内容が導入編になってしまったので、導入編の続きとさせて頂きます。
今回は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);

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.