第五十八章 试着制作3D背景吧(2)

>>点此回到教程目录

在上一章中,我们仅仅是绘制一张图像都相当麻烦呢。

然而,有很多事是明摆着要去做的,正因为“写了很多难以理解的东西”,似乎才能够使程序效率化。

在这一章,我们的目的是努力使3D绘制轻松地进行,以及图像的淡入淡出功能(fade-in, fade-out)的附加。

所谓淡入,就是从透明状态渐渐地变得清晰起来的效果,而淡出与之相反。

远处的物体靠近的时候,从某一个地方看过去,一下子是不会看得很清楚呢。

因此,我们添加上可以慢慢地清楚看见的淡入,以及与之相反功能的淡出吧。

首先,我们来看一下在这一章中完成的程序的运行结果。


运行结果


由于可能您还有点不明白我们现在正在做什么,因此也请同时看看59章的运行结果动画。



在画面的左侧有一个墙壁呢。让这个墙壁一会儿跑到里面一会儿跑到外面的效果就是58章的运行结果动画。

在普通DX Library下使用α混合等来使图像透明的时候,整个图像都会有一定的透明度。

然而,就像从运行结果中看到的那样,即便是在一张图像中,我们也能够让透明度产生变化。

正如同在前面的章节中也说过的那样,这是因为我们能够对每一个坐标都设定透明度。

通过这样子,“越远的地方越透明”这种事情就是可以办到的了。

请稍微看一下这个图像。

现在,我们假定墙壁从远处朝着自己靠近。

为了设定淡入淡出,我们把这些数据设定到变量中:从哪里开始开始绘制图像、淡入到哪里为止、从哪里开始淡出、绘制到哪里为止。

我们把开始绘制图像的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;          //设定z深度移动为从哪里开始到哪里为止
    float FadeFromZ,FadeToZ; //是否设定从哪儿到哪儿进行淡入淡出(消失的瞬间淡出,显示的瞬间淡入)
    VERTEX_3D Vertex[6] ;    //绘制用的6个顶点
}Object_t;

结构体中我们还另外加入了Type这个变量。

在3D的情况下绘制ID时候,许多时候会有这样一些情况:

・平行于画面
・垂直于画面(墙壁)
・垂直于画面(地面)

当然,台阶、坡道等也能倾斜着绘制来但我们暂时不管;总之,一旦我们设置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;// type设置为垂直
    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)来变换计算。

如果是与画面平行的情况,就这样子代入大小就可以了,而如果与画面垂直(.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 Library使用终止处理
    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;         //设定z深度移动为从那里开始到哪里为止
    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;//type设置为垂直
    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 Library使用终止处理
    DxLib_End() ;

    // 程序结束
    return 0 ;
}

>>点此回到教程目录

results matching ""

    No results matching ""