描画順位


全部一人でゲーム作製している場合,
良い所は,プログラムの改変が必要となるようなアイディアを,簡単に追加できる事,
悪い所は,アイディアを追加する度にプログラムが改変されて,大変な状態になる事,
...


自分の場合,判定を行わないオブジェクト(爆発,背景雲,飛沫,文字...)を,
総じて「パーティクル」と呼んでいて,何かの演出をしたいと思うと,
大体,このパーティクルをこき使う事になるんですが,
id:keim_at_Si:20050420のように,ある演出アイディアを実現しようとすると,
次々と,この「パーティクル」の亜種を実装していく事になる.
でも,id:keim_at_Si:20050611で,破片とかを新たな亜種で表現しようとして,
パーティクルの亜種が全6種類になった所で,ようやく「ちょっとマズくね?」
と思い直した(遅.


パーティクルの亜種と言いながら,実際は,
描画順位;背景→□→敵機→□→自機→□→弾→□→Fade→□(□の位置で描画)
描画方法;デプステストOn/Off,ライティングOn/Off
描画演算;ベタ,アルファブレンド,加算描画,乗算描画
描画対象;3Dモデル,ビットマップ
によって区別しているだけで,移動量計算などの実行部分は全く同じだったりする.
もともと,「実行」と「描画」を分離してあるので,
オブジェクトを描画する所にだけ,タスクシステムを採用して,
描画順序を入れ替えられるようにプログラムを変更する事にしようと思い立つ.
(タスクシステムについてはコチラ→id:kenmo:20050522,物凄く丁寧)


その下準備として,描画順序について徹底的に考え直してみる.


まず,描画タスクシステム.
いちいち合成演算関数を切り替えながら描画するのは,ちょっと嫌なので,
描画演算毎に別々のタスクを設けてしまう.以下,思考プログラム

// 描画優先順位
enum _DRAWING_PRIORITY {
  【描画の優先順位の列挙子】,
  _DRAWING_PRIORITY_MAX
}

// 描画演算方法
enum _DRAWING_TYPE {
  DRAW_MODEL=0,     // ベタ描画
  DRAW_ALPHA_BLEND, // アルファ合成描画
  DRAW_ADD_BLEND,   // 加算合成描画
  DRAW_MULT_BLEND,  // 乗算合成描画
  _DRAWING_TYPE_MAX
}

class IActor
{
private:
  _DRAWING_PRIORITY  m_enDrawingPrior;  // 描画優先順位
  _DRAWING_TYPE      m_enDrawingType;   // 描画演算方法

pubilc:
  inline _DRAWING_PRIORITY GetDrawingPrior(){return m_enDrawingPrior;}
  inline _DRAWING_TYPE     GetDrawingType() {return m_enDrawingType;}
};

class CObjectManager
{
private:
  // 演算毎/優先順位毎に,別々の描画タスク
  static CDrawingTaskChain  s_DrawingTask[_DRAWING_TYPE_MAX][_DRAWING_PRIORITY_MAX];
  
  // 指定した優先度の描画タスクに登録
  void AddDrawingChain(IActor* pActor)
  {
    _DRAWING_TYPE      enType  = pActor->GetDrawingType();   // 描画演算方法
    _DRAWING_PRIORITY  enPrior = pActor->GetDrawingPrior();  // 描画優先順位
    s_DrawingTask[enType][enPrior].Add(pActor);
  }

  // 描画タスクから抹消
  void RemoveDrawingChain(IActor* pActor)
  {
    _DRAWING_TYPE      enType  = pActor->GetDrawingType();   // 描画演算方法
    _DRAWING_PRIORITY  enPrior = pActor->GetDrawingPrior();  // 描画優先順位
    s_DrawingTask[enType][enPrior].Remove(pActor);
  }
};


ブレンド描画する順序がばらばらだと描画結果が異なってしまうので,固定する.
光による飽和を優先させたければ加算プレンドを後ろに,
影や黒煙をしっかり描きたければ乗算ブレンドを後ろに,
物体自体をしっかり描きたければアルファプレンドを後ろに,
持って行けば良いので,
ベタ(BlendOff)→アルファプレンド→加算プレンド→乗算ブレンド
とりあえず,この順序にしてみる.


以下,思考プログラム

namespace GL {
inline void SetAlphaBlend(){glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);}
inline void SetAddBlend()  {glBlendFunc(GL_SRC_ALPHA, GL_ONE);}
inline void SetMultBlend() {glBlendFunc(GL_ZERO, GL_SRC_COLOR);}
}

void CObjectManager::DrawObject(_DRAWING_PRIORITY enPrior)
{
  glEnable(GL_LIGHTING);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_BLEND);
  
    // ベタ 描画タスク
    s_DrawingTask[DRAW_MODEL][enPrior].Draw();
    
  glDisable(GL_LIGHTING);
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  
    // Alpha Blend 描画タスク
    GL::SetAlphaBlend();
    s_DrawingTask[DRAW_ALPHA_BLEND][enPrior].Draw();
    
    // Add Blend 描画タスク
    GL::SetAddBlend();
    s_DrawingTask[DRAW_ADD_BLEND][enPrior].Draw();
    
    // Mult Blend 描画タスク
    GL::SetMultBlend();
    s_DrawingTask[DRAW_MULT_BLEND][enPrior].Draw();
}


次に,オブジェクトの描画順序(パーティクル以外)
[背景]→[敵機]→[ショット]→[プレイヤー]→[文字列]→[敵弾]
このうち,「背景」と「プレイヤー」は1個しかないので,
描画タスクシステムには入れないで直接描画する.
それぞれのオブジェクトの描画タイミングと,
その間の描画タイミングを設定できるように優先順位を設ける.
以下,思考プログラム

// 描画優先順位
enum _DRAWING_PRIORITY {
  PRIOR_BACKGROUND = 0,
  PRIOR_BEFORE_ENEMY,
  PRIOR_ENEMY,
  PRIOR_BEFORE_SHOT,
  PRIOR_SHOT,
  PRIOR_BEFORE_PLAYER,
  PRIOR_PLAYER,
  PRIOR_BEFORE_BULLET,
  PRIOR_BULLET,
  PRIOR_TOPMOST,
  _DRAW_PRIORITY_MAX
};


で,描画の実行.以下,思考プログラム

bool CObjectManager::Draw()
{
  int i;

  // カメラ
  glPushMatrix();
  if (GLOBAL::Camera.IsAlive()) GLOBAL::Camera.MultMatrix();

  // 背景
  if (s_BackGround.IsAlive()) {
    glPushMatrix();
      glDisable(GL_LIGHTING);
      glEnable(GL_DEPTH_TEST);
      glDisable(GL_BLEND);
      s_BackGround.MultMatrix();        // 座標変換
      s_BackGround.Draw();              // 描画
      // 背景の座標変換の影響を受けるオブジェクトの描画
      DrawObject(PRIOR_BACKGROUND);
    glPopMatrix();
  }

  // プレイヤーより優先度の低いオブジェクト描画
  for (i=PRIOR_BEFORE_ENEMY; i


うーん,こんな感じになるの...かな?