>>点此回到教程目录
在上一章中,我们仅仅是绘制一张图像都相当麻烦呢。
然而,有很多事是明摆着要去做的,正因为“写了很多难以理解的东西”,似乎才能够使程序效率化。
在这一章,我们的目的是努力使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 ; }