5-5 効果(EFFECT)の使用

ここでは今までの板ポリ描画の応用で画面に光る効果をつけてみましょう。

この光る効果を使うことで、サンプルゲームの当たり判定時にモヤっと光らせる部分が作れます。
また、この効果はよく格ゲーなどで必殺技を出すときに使われたり、魔法の発動時などの演出を行うのに使われます。

■アルファブレンドと加算合成

まず合成というのはどういうものかと言うと、下地にある画像に上から画像を重ねることを言います。
※ちなみに合成を英語でブレンドと言います

そして重ねるときに色をどのように掛け合わせるかを指定することが出来ますが、
一般的に言うアルファブレンドとは、元の色から重ねたい色をそれぞれ0~100%とし、
アルファ値を倍率として使い最終的にその中間の色を確定します。

計算式にすると以下のようになります。

確定色 = 背景色 + (重ねる色 - 背景色) * (アルファ値 / 255)

これはRGBそれぞれに行う計算式となり、またカラー値は8bitなので範囲は0x00~0xFFが入ります。

またアルファ値は重ねたい画像の方に含まれる成分であり、
値の範囲はRGBと同じ0x00~0xFFですが計算時にはこれを0.0~1.0の倍率として考えます。
※0.0~1.0の倍率にするにはアルファ値を0xFF(255)で割ります(floatなどにキャストしないと計算出来ません)


例えば背景色が0x808080の灰色だった場合、これを成分別にみるとR=0x80、G=0x80、B=0x80となります。

この上に黄色で透明度50%の色を重ねたとします。
黄色の成分的にはR=0xFF、G=0xFF、B=0x00、そしてアルファ成分は0x80となるので、
これを32bit値とすると0x80FFFF00となります。
※以下はアルファ成分のないRGB色(0xFFFF00)です

そしてこれを実際に合成すると結果は0xC0C040の少し暗い黄色となります。

この色は上の計算式から求めることが出来ます。

R = (BYTE)(0xFF + (0xFF - 0x80) * (0x80 / 255.0)) = 0xC0
G = (BYTE)(0xFF + (0xFF - 0x80) * (0x80 / 255.0)) = 0xC0
B = (BYTE)(0x00 + (0x00 - 0x80) * (0x80 / 255.0)) = 0x40



次に加算合成についてですが、加算という名前的に背景色に重ねたい色を単に加算したものとなります。
この時のアルファ値とは、重ねたい色を何%加算するかという計算に使われます。

確定色 = 背景色 + 重ねる色 * (アルファ値 / 255)


計算式を見れば分かりますが、加算合成では場合によっては計算結果が8bitの上限を超えてしまいます。
上限を超えたらその上限に補正する必要がありそのためにif文が必要となるため、加算合成の方が処理的には少し重くなります。

if( 確定色>0xFF ) {
    確定色 = 0xFF;
}


このように加算合成では上限が存在するため、背景色が明るい色だった場合に新たに色を重ねると、
すぐに色が上限に達してしまいそれ以上明るくすることが出来ないといった問題がありますが、
この加算合成を使うことで光る効果を簡単に表現することが出来るので、
ゲームの演出には欠かせない要素となっています。

ちなみにこれらの合成は全てグラフィックカードが行うので特に計算式を覚えておく必要はありませんが、
思ったように色が出ない場合はこの計算式を思い出してみると良いかと思います。


■加算合成のプログラム

ここでは前回のDirect3D9のテストプログラムを改変して実際に加算合成を行ってみます。

加算合成の結果を見るためには背景に画像が必要なので、ここでは元の画像を全画面に表示したものを背景画像とし、
その上に加算合成にてもう1枚同じ画像を表示してみます。

まず背景描画用にもう1つ頂点バッファを用意します。

// 頂点データ
D3DTLVERTEX             v[4];                       // 四角の4頂点分
D3DTLVERTEX             v2[4];                      // 背景用の4頂点


次にこの頂点バッファを全画面描画用に初期化します。
以下のように各頂点の座標として左上(0,0)、右上(640,0)、左下(0,480)、右下(640,480)と設定します。

    // 背景用頂点の初期化
    ZeroMemory( &v2,sizeof(v2) );

    v2[0].x     = 0.0f;
    v2[0].y     = 0.0f;
    v2[0].z     = 0.0f;
    v2[0].rhw   = 1.0f;
    v2[0].color = 0xFFFFFFFF;
    v2[0].tu    = 0.0f;
    v2[0].tv    = 0.0f;

    v2[1].x     = 640.0f;
    v2[1].y     = 0.0f;
    v2[1].z     = 0.0f;
    v2[1].rhw   = 1.0f;
    v2[1].color = 0xFFFFFFFF;
    v2[1].tu    = 1.0f;
    v2[1].tv    = 0.0f;

    v2[2].x     = 0.0f;
    v2[2].y     = 480.0f;
    v2[2].z     = 0.0f;
    v2[2].rhw   = 1.0f;
    v2[2].color = 0xFFFFFFFF;
    v2[2].tu    = 0.0f;
    v2[2].tv    = 1.0f;

    v2[3].x     = 640.0f;
    v2[3].y     = 480.0f;
    v2[3].z     = 0.0f;
    v2[3].rhw   = 1.0f;
    v2[3].color = 0xFFFFFFFF;
    v2[3].tu    = 1.0f;
    v2[3].tv    = 1.0f;

※テクスチャ座標は前のサンプルと同じく画像の全領域を指定しています


次にDirect3Dの合成機能を有効にするため、以下のようにメインループの前にデバイスを設定します。

    // 半透明処理を有効にする
    lpD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

    // 元の画像の合成でアルファ値を使用する
    lpD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );

    // 1枚目のテクスチャの合成方法を設定する
    lpD3DDevice->SetTextureStageState( 0,D3DTSS_ALPHAOP, D3DTOP_MODULATE );


    // メインループ
    MSG msg;
    while(1) {

Direct3Dデバイスは一度設定した値は変更が無い限り保持された状態となります。
つまり、一度ステートを変更してしまえばあとは変更の必要が無いので、
上記のように今後変更の無い設定はメインループの前で行ってしまいます。



最後に今回追加した背景画像の表示と、元のサンプルで描画していた画像を加算合成として描画してみます。

        // シーンの開始
        lpD3DDevice->BeginScene();

        // テクスチャのセット
        lpD3DDevice->SetTexture( 0,lpTex );

        // 使用する頂点フォーマットのセット
        lpD3DDevice->SetFVF( D3DFVF_TLVERTEX );

        // 背景の描画
        lpD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, v2, sizeof(D3DTLVERTEX) );

        // レンダリングステートの変更
        lpD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );

        // 頂点リストの描画
        lpD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, v, sizeof(D3DTLVERTEX) );
 
        // レンダリングステートを戻す
        lpD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

        // シーンの終了
        lpD3DDevice->EndScene();

        // フリップして実際に画面に反映
        lpD3DDevice->Present( NULL,NULL,NULL,NULL );


まずは背景用の頂点を普通に描画します。
これで全画面に画像が表示されます。


次にこの上に表示する画像を加算合成とするため、描画の前にレンダリングステートを変更しています。
加算合成にするにはD3DRS_DESTBLENDをD3DBLEND_ONEに設定するだけです。

この状態で次に描画した頂点バッファは加算合成されて描画されるようになります。


最後にD3DRS_DESTBLENDをD3DBLEND_INVSRCALPHAに設定することで
レンダリングステートを元に戻しています。

これ以降は再び今まで通り通常のアルファブレンドにて描画されるようになります。


これで加算合成のプログラムは完成ですが、テストとして元の頂点バッファ(v[4]の方)のカラー値を
いろいろ変更してみることで、どのように画像が合成されるのかを確認することが出来ます。
以下は頂点のアルファを0x40にした場合の加算結果です。

※上の100%の合成と比べて飽和しなくなったため、全体的に少しだけ明るく光っているように見えると思います


実際のゲームではこれに拡大縮小や回転、さらに透明度を使い分けることでいろいろな効果を表現出来ます。

例えば下のような画像(適当ですが)を1枚作って何枚か重ねるだけで、
簡単に複雑な炎を表現することが出来ます。
また、この炎テクスチャがアニメーションしていればもやもやと動いている炎も表現出来ます。

炎テクスチャ 4枚を適当に加算合成


■サンプルのソース

ここでは上のサンプルコードをダウンロードすることが出来ます。

VisualStudio2010のプロジェクト
※VC2012、VC2013はこちら
TestEffect_vc2010.zip
VisualStudio2015のプロジェクト TestEffect_vc2015.zip