58 章

先ほどの章では一枚描画するだけでもエラく大変でした。

しかし、やってる事は決まりきってる事が多く、「わかりにくいことを沢山書いてる」だけであって、効率化出来そうですよね。

この章では、3D描画を楽に出来るようにしてあげることと、画像のフェードインフェードアウトの機能を付けることを目的とします。

フェードインとは、透明な状態からスーッとはっきりさせる演出のことで、フェードアウトとはその逆です。

遠くにある物体が近づいてくるとき、どこかの地点から急にはっきり見えたりしませんよね。

ですから、徐々に徐々にはっきり見えてくる、フェードインや、その反対のフェードアウトの機能を付けてあげましょう。

まずは、この章で作ったプログラムの実行結果をご覧下さい。

実行結果


ちょっと何をしているのかよくわからないかもしれませんので、59章の実行結果の動画もあわせてご覧下さい。



この左側の壁がありますよね。これを奥にやったり手前にやったりしているのが58章の実行結果の動画です。

普通DXライブラリでアルファブレンドなどを使って画像を透明にする時は、画像全体が一定の透過率になっていました。

しかし、実行結果からわかるように、一枚の画像の中でも、透過率に変化を持たせることが出来ます。

先ほどの章でも言ったとおり、座標一つ一つに透過率が設定出来るからですね。

これを使う事で、「遠くになるほどスーッと透明に」が可能なのです。

ちょっとこの画像をご覧下さい。



今、壁が遠くから自分に向かって近づいてきているとします。

フェードイン、フェードアウトを設定する為に、どこから画像を描画し始めて、どこまでフェードインし、

どこからフェードアウトして、どこまで描画するかを変数にセットしてやります。

画像を描画し始めるZ地点をFromZ

画像をフェードインさせ終わるZ地点をFadeFromZ

画像をフェードアウトし始めさせるZ地点をFadeToZ

画像を描画し終わるZ地点をToZ

としています。画像一枚の大きさはLargeX,LargeYにセットしてやり、中心座標は(x,y,z)とします。

これらをまとめて構造体で用意してやります。

//一つのテクスチャーについての構造体
typedef struct{
    int Type;                // 0:画面に平行、1:画面に垂直(壁)
    int Img;                 //画像
    float x,y,z;             //中心点
    float LargeX,LargeY;     //縦横の大きさ(Typeが1の時はLargeXがLargeZの役割をする)
    float u,v;               //使用する画像のどの部分を使うか
    float FromZ,ToZ;         //どこからどこまで奥行きを設定するか
    float FadeFromZ,FadeToZ; //どこからどこまでフェードを設定するか(消える瞬間フェードアウト、現れる瞬間フェードインする)
    VERTEX_3D Vertex[6] ;    //描画用頂点6個
}Object_t;


構造体に Type という変数を更にもたせてやりました。

3Dで描画させる際、多くの場合、

・画面に平行
・画面に垂直(壁)
・画面に垂直(地面)

のどれかになる場合が多いですよね。もちろん階段とか坂道とか、斜めにも出来ますが、とりあえず0か1どっちかをセットしてやれば、

自動的に画面に平行か垂直かどちらかで計算してくれるようにしてやります。現在の章では画面に垂直か、壁だけ作りました。

また、先ほどの章で、こんなプログラム書きましたよね。


        // 画面の中央に幅・高さ100で描画
        Vertex[0].pos.x = 320.0F - 50.0F ;      Vertex[0].pos.y = 240.0F + 50.0F ;      Vertex[0].pos.z = Z ;
        Vertex[0].u = 0.0F ;
        Vertex[0].v = 0.0F ;

        Vertex[1].pos.x = 320.0F + 50.0F ;      Vertex[1].pos.y = 240.0F + 50.0F ;      Vertex[1].pos.z = Z ;
        Vertex[1].u = 1.0F ;
        Vertex[1].v = 0.0F ;

        Vertex[2].pos.x = 320.0F - 50.0F ;      Vertex[2].pos.y = 240.0F - 50.0F ;      Vertex[2].pos.z = Z ;
        Vertex[2].u = 0.0F ;
        Vertex[2].v = 1.0F ;

        Vertex[3].pos.x = 320.0F + 50.0F ;      Vertex[3].pos.y = 240.0F - 50.0F ;      Vertex[3].pos.z = Z ;
        Vertex[3].u = 1.0F ;
        Vertex[3].v = 1.0F ;

        Vertex[4].pos.x = 320.0F - 50.0F ;      Vertex[4].pos.y = 240.0F - 50.0F ;      Vertex[4].pos.z = Z ;
        Vertex[4].u = 0.0F ;
        Vertex[4].v = 1.0F ;

        Vertex[5].pos.x = 320.0F + 50.0F ;      Vertex[5].pos.y = 240.0F + 50.0F ;      Vertex[5].pos.z = Z ;
        Vertex[5].u = 1.0F ;
        Vertex[5].v = 0.0F ;


なんだか解りにくそうなプログラムですが、要は
このabc,defを順番に座標指定しているだけですよね。

良く見ると例えば[0]は

x = 中心 - ○, y = 中心 + ○, u = 0, v = 0 

となってるので、つまり

x = 中心 + ○*a, y = 中心 + ○*b, u = ■+□*c, v = ▲+△*d

とおくと

(a,b,c,d) = (-1,1,0,0)

と置けますよね。これを全てに置き換えてやると


typedef struct{
    float x,y;
    float u,v;
}VtPm_t;
VtPm_t VtPm[6]={{-1,1,0,0},{1,1,1,0},{-1,-1,0,1},{1,-1,1,1},{-1,-1,0,1},{1,1,1,0}};


こうなるので、ループで計算出来るようになります。

・・・え?意味がわからない?

上のプログラムを見てみると、同じ数字を引いたり足したりしているだけですよね。

u,vに関してはどこかが0になったり1になったりしているだけです。

この法則を変数にいれてやってそれを代入してやればループで計算出来るということです。

これは理解したりいじくる必要がないので、イマイチ解らないって人は放置でOKです(投げやり)

では、実際にサンプルを目で追って行きましょう。


Object_t Object;


先ほどのObject_t型の構造体を宣言します。
そして構造体の初期化です。


void ini (){
    int i;
    Object.Img = LoadGraph( "mydat/img/kabe.png" );
    Object.LargeX = 48.0f;//とりあえず描画する大きさを適当に設定。縦・横比は素材の通りにする
    Object.LargeY = 60.0f;
    Object.Type = 1;//タイプを垂直に
    Object.x = 220.0f;//とりあえず描画する中心位置を中心よりちと左に
    Object.y = 240.0f;
    Object.z = 0.0f;
    Object.u = 0.763671875f;//画像のどの部分を使うか
    Object.v = 1.0f;
    Object.FromZ     =  200;//描画開始地点
    Object.FadeFromZ =  100;//描画フェードイン開始地点
    Object.FadeToZ   = -100;//描画フェードアウト開始地点
    Object.ToZ       = -200;//描画終了地点

    for(i=0; i<6; i++){
        Object.Vertex[i].r = 255;
        Object.Vertex[i].b = 255;
        Object.Vertex[i].g = 255;
        Object.Vertex[i].a = 255;
        Object.Vertex[i].u = Object.u * VtPm[i].u;
        Object.Vertex[i].v = Object.v * VtPm[i].v;
    }
}


特に説明の必要は無いと思います。

LargeX,LargeYは描画の大きさなんで、この辺は適当にしておいて、見ながら調整していけばいいでしょう。

ただ、画像の縦横比と同じ比率にしないとおかしなことになるので注意です。

.uに入れている意味不明な数値は、素材を見てもらったら解ると思います。

横512ピクセルのうち、512を1にした時、使用する幅が0.7636...fなのです。

.FromZ〜.ToZについては上に述べた通りですね。描画する範囲とフェードする範囲をセットしています。


int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
             LPSTR lpCmdLine, int nCmdShow )
{
    int i;
    float z=0;
    ChangeWindowMode(TRUE);//ウィンドウモード
    if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化

    ini();

    // 何かキーが押されるまでループ
    while(ProcessMessage()==0 && ClearDrawScreen()==0 && CheckHitKey(KEY_INPUT_ESCAPE)==0){

        Object.z=z;

        if(CheckHitKey(KEY_INPUT_Z)>0){
            z+=1.4f;
        }
        if(CheckHitKey(KEY_INPUT_Y)>0){
            z-=1.4f;
        }


描画するもののタイプで計算を変えます。

画面に平行な場合はそのまま大きさを代入すればいいですが、画面に垂直(.Type==1)の場合、

Xの大きさを奥行きに使っています。


        switch(Object.Type){
            case 0:
                for(i=0;i<6;i++){
                    Object.Vertex[i].pos.x = Object.x + Object.LargeX * VtPm[i].x ;    
                    Object.Vertex[i].pos.y = Object.y + Object.LargeY * VtPm[i].y ;
                    Object.Vertex[i].pos.z = Object.z ;
                }
                break;
            case 1:
                for(i=0;i<6;i++){
                    Object.Vertex[i].pos.x = Object.x;    
                    Object.Vertex[i].pos.y = Object.y + Object.LargeY * VtPm[i].y ;
                    Object.Vertex[i].pos.z = Object.z + Object.LargeX * VtPm[i].x ;
                }
                break;
        }


ここでフェードの計算をします。

.FromZ〜.FadeFromZの間はフェードイン

.FadeToZ〜.ToZの間はフェードアウトします。


/*
z
Object.FromZ        200
z
Object.FadeFromZ    100
z
Object.FadeToZ     -100
z
Object.ToZ         -200
z
*/
        if( Object.FromZ - Object.FadeFromZ <= 0 ){
            printfDx(".Fromの設定がおかしい\n");
        }
        else if( Object.FadeToZ - Object.ToZ <= 0 ){
            printfDx(".Toの設定がおかしい\n");
        }
        else{
            for(i=0; i<6; i++){
                float z = Object.Vertex[i].pos.z;
                //位置が描画する範囲より遠かったら透過0
                if     (z < Object.ToZ){
                    Object.Vertex[i].a = 0;
                }
                //(近づいている場合)フェードインする位置だったら
                else if(Object.ToZ < z && z <=Object.FadeToZ){
                    Object.Vertex[i].a = (unsigned char)(255.0f / (Object.FadeToZ - Object.ToZ) * (z - Object.ToZ)) ;
                }
                //通常描画する位置なら
                else if(Object.FadeToZ <= z && z <= Object.FadeFromZ){
                    Object.Vertex[i].a = 255;
                }
                //(近づいてる場合)フェードアウトする位置だったら
                else if(Object.FadeFromZ <= z && z < Object.FromZ){
                    Object.Vertex[i].a = (unsigned char)(255.0f / (Object.FromZ - Object.FadeFromZ) * (Object.FromZ - z)) ; 
                }
                //描画する範囲より近かったら透過0
                else if(Object.FromZ < z){
                    Object.Vertex[i].a = 0;
                }
            }
        }


ポリゴンを描画して終わります。


        // ポリゴンを透過色無しで2枚描画
        DrawPolygon3D( Object.Vertex, 2, Object.Img, TRUE ) ;

        DrawFormatString(0,0,GetColor(255,255,255),"%f",z);
        // 裏画面の内容を表画面に反映
        ScreenFlip() ;
    }

    // DXライブラリ使用の終了処理
    DxLib_End() ;

    // ソフトの終了
    return 0 ;
}


では全体を見渡してみましょう。

実行結果では「Yキー」「Zキー」で物体を遠のかせたり近づかせたり出来るのでやってみて下さい。

--- main.cpp --- 

#include "../../../include/DxLib.h"

//三角形のポリゴン2つで四角形を描画する為の値。数値固定なので、覚える必要なし
typedef struct{
    float x,y;
    float u,v;
}VtPm_t;
VtPm_t VtPm[6]={{-1,1,0,0},{1,1,1,0},{-1,-1,0,1},{1,-1,1,1},{-1,-1,0,1},{1,1,1,0}};

//一つのテクスチャーについての構造体
typedef struct{
    int Type;    // 0:画面に平行、1:画面に垂直
    int Img;    //画像
    float x,y,z;//中心点
    float LargeX,LargeY;//縦横の大きさ(Typeが1の時はLargeXがLargeZの役割をする)
    float u,v;            //使用する画像のどの部分を使うか
    float FromZ,ToZ;    //どこからどこまで奥行きを設定するか
    float FadeFromZ,FadeToZ;    //どこからどこまでフェードを設定するか(消える瞬間フェードアウト、現れる瞬間フェードインする)
    VERTEX_3D Vertex[6] ;        //描画用頂点6個
}Object_t;

Object_t Object;

void ini (){
    int i;
    Object.Img = LoadGraph( "mydat/img/kabe.png" );
    Object.LargeX = 48.0f;//とりあえず描画する大きさを適当に設定。縦・横比は素材の通りにする
    Object.LargeY = 60.0f;
    Object.Type = 1;//タイプを垂直に
    Object.x = 220.0f;//とりあえず描画する中心位置を中心よりちと左に
    Object.y = 240.0f;
    Object.z = 0.0f;
    Object.u = 0.763671875f;//画像のどの部分を使うか
    Object.v = 1.0f;
    Object.FromZ     =  200;//描画開始地点
    Object.FadeFromZ =  100;//描画フェードイン開始地点
    Object.FadeToZ   = -100;//描画フェードアウト開始地点
    Object.ToZ       = -200;//描画終了地点

    for(i=0; i<6; i++){
        Object.Vertex[i].r = 255;
        Object.Vertex[i].b = 255;
        Object.Vertex[i].g = 255;
        Object.Vertex[i].a = 255;
        Object.Vertex[i].u = Object.u * VtPm[i].u;
        Object.Vertex[i].v = Object.v * VtPm[i].v;
    }
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
             LPSTR lpCmdLine, int nCmdShow )
{
    int i;
    float z=0;
    ChangeWindowMode(TRUE);//ウィンドウモード
    if(DxLib_Init() == -1 || SetDrawScreen( DX_SCREEN_BACK )!=0) return -1;//初期化と裏画面化

    ini();

    // 何かキーが押されるまでループ
    while(ProcessMessage()==0 && ClearDrawScreen()==0 && CheckHitKey(KEY_INPUT_ESCAPE)==0){

        Object.z=z;

        if(CheckHitKey(KEY_INPUT_Z)>0){
            z+=1.4f;
        }
        if(CheckHitKey(KEY_INPUT_Y)>0){
            z-=1.4f;
        }

        switch(Object.Type){
            case 0:
                for(i=0;i<6;i++){
                    Object.Vertex[i].pos.x = Object.x + Object.LargeX * VtPm[i].x ;    
                    Object.Vertex[i].pos.y = Object.y + Object.LargeY * VtPm[i].y ;
                    Object.Vertex[i].pos.z = Object.z ;
                }
                break;
            case 1:
                for(i=0;i<6;i++){
                    Object.Vertex[i].pos.x = Object.x;    
                    Object.Vertex[i].pos.y = Object.y + Object.LargeY * VtPm[i].y ;
                    Object.Vertex[i].pos.z = Object.z + Object.LargeX * VtPm[i].x ;
                }
                break;
        }
/*
z
Object.FromZ        200
z
Object.FadeFromZ    100
z
Object.FadeToZ        -100
z
Object.ToZ            -200
z
*/
        if( Object.FromZ - Object.FadeFromZ <= 0 ){
            printfDx(".Fromの設定がおかしい\n");
        }
        else if( Object.FadeToZ - Object.ToZ <= 0 ){
            printfDx(".Toの設定がおかしい\n");
        }
        else{
            for(i=0; i<6; i++){
                float z = Object.Vertex[i].pos.z;
                //位置が描画する範囲より遠かったら透過0
                if     (z < Object.ToZ){
                    Object.Vertex[i].a = 0;
                }
                //(近づいている場合)フェードインする位置だったら
                else if(Object.ToZ < z && z <=Object.FadeToZ){
                    Object.Vertex[i].a = (unsigned char)(255.0f / (Object.FadeToZ - Object.ToZ) * (z - Object.ToZ)) ;
                }
                //通常描画する位置なら
                else if(Object.FadeToZ <= z && z <= Object.FadeFromZ){
                    Object.Vertex[i].a = 255;
                }
                //(近づいてる場合)フェードアウトする位置だったら
                else if(Object.FadeFromZ <= z && z < Object.FromZ){
                    Object.Vertex[i].a = (unsigned char)(255.0f / (Object.FromZ - Object.FadeFromZ) * (Object.FromZ - z)) ; 
                }
                //描画する範囲より近かったら透過0
                else if(Object.FromZ < z){
                    Object.Vertex[i].a = 0;
                }
            }
        }

        // ポリゴンを透過色無しで2枚描画
        DrawPolygon3D( Object.Vertex, 2, Object.Img, TRUE ) ;

        DrawFormatString(0,0,GetColor(255,255,255),"%f",z);
        // 裏画面の内容を表画面に反映
        ScreenFlip() ;
    }

    // DXライブラリ使用の終了処理
    DxLib_End() ;

    // ソフトの終了
    return 0 ;
}



- Remical Soft -