【Visual C++】游戏开发笔记三十九 浅墨DirectX教程之七 他山之石:几种几何体的快捷绘制法...

本系列文章由zhmxy555(毛星云)编写,转载请注明出处。

文章链接:http://blog.csdn.net/zhmxy555/article/details/8475261

作者:毛星云(浅墨)邮箱:happylifemxy@163.com

本篇文章里,我们对Direct3D之中几种几何体的简洁绘制方法进行了详细的剖析,最后依旧是提供文章配套的详细注释的demo源代码的欣赏,并在文章末尾提供了源代码下载。(这标题有些歧义的,这个几种是修饰几何体的,而绘制的方法只有一种 :D)

首先需要跟大家道声歉,上个周末因为浅墨个人的原因。没把想写的内容的思路整理好,所以就停更了一次。

好了,别的废话咱不说了,下面直奔本周主题~

一、D3D中内置的几何体概述

通过前面的讲解大家应该会发现,使用顶点缓存和索引缓存通过绘制三角形单元来绘制图形还是有一点复杂的,需要一定的想象力和大量顶点和索引的设置,我们一般很少去这样通过逐个设置顶点缓存和索引缓存来进行画面上几何体的绘制的。而比较人性化的是,Direct3D为我们提供了几种特殊的生成简单3D几何体的网格数据的方法。可以说是在我们学习从文件载入网格数据之前,为我们提供了很多便利,丰富了我们的学习时的图形素材。下面我们就来看看这几个快捷的几何体绘制函数,他们分别是立方体(Cube),圆环体(Torus),多边形(Polygon),球面体(Sphere),茶壶(Teapot)和圆柱体(Cylinder)。

我们可以在DirectX SDK(以目前最新的Microsoft DirectX SDK (June 2010)为例)的Windows DirectX Graphics.chm文档中依次展开【Windows DirectX Graphics Documentation】->【Direct3D 9 Graphics】->【Reference for Direct3D 9】->【D3DX Reference】->【D3DX Functions】->【Shape Drawing Functions】找到这些函数的说明文档,这是这些函数最官方最原始的参考地址了。

不过显然这些DirectX SDK Document都是纯英文的,和大众之间的距离还是比较疏远。

先给大家看看这几个函数的全家福,后面我们会全部详细介绍使用方法的:

D3DXCreateBox 用于创建一个盒子,立方体

D3DXCreateSphere 用于创建一个球面体

D3DXCreateCylinder 用于创建一个柱体

D3DXCreateTeapot 用于创建一个茶壶

D3DXCreatePolygon 用于创建一个多边形

D3DXCreateTorus 用于创建一个圆环体

我们可以看到这些函数的名字取的都非常的憨厚老实,一开始是D3DX,然后紧跟其后一个Create,表示是在创建某东西,最后跟上对应的几何体名,比如Teapot茶壶,这样连起来就非常好记忆了,D3DXCreateTeapot。

二、D3D中几种内置的几何体绘制四步曲

首先需要说明的是下面提到的ID3DXMesh接口类型是后面我们在讲解网格相关知识的时候重点讲解的内容,这里大家先对其有一个大概的印象就可以了。

想要通过这几个函数快捷绘制出一个几何体,需要以下几步:

1.定义一个ID3DXMesh接口类型的对象。

2.调用这六个函数的其中一个对我们在第一步里面定义的这个对象进行初始化,也就是把创建好的网格存储在我们定义好的ID3DXMesh类型的对象中

3.在Direct3D渲染五步曲的第三步,也就是在BeginScene之后调用DrawSubset方法进行网格图形的绘制,即是拿着第二步里面初始化好的ID3DXMesh接口类型的对象指一下DrawSubset(0)方法就好了

4.绘制完成之后,调用ID3DMesh接口的Release方法,或者我们自定义的COM接口释放宏进行资源的释放。

总结起来也就简明扼要八个字:

定义,创建,绘制,释放

方便大家理解各个过程,给出一个配套的注释好的代码:

//四步曲之一,定义
ID3DXMesh* meshBox;
//四步曲之二,创建
D3DXCreateBox(  g_pd3dDevice,  2.0f, 2.0f,   2.0f,&meshBox, 0  );
//四步曲之三,绘制
g_pd3dDevice->BeginScene();
meshBox->DrawSubset(0);
//四步曲之四,释放
meshBox->Release();


三、D3D中几种内置几何体的创建详细剖析

这一小节里面讲解的都是上文我们提到的几何体绘制四步曲的第二步里面的具体创建方法,但是配套的调用实例都包含了四步曲里面的前三步,大家需要注意下。

1. 立方体的创建——D3DXCreateBox方法

D3DXCreateBox方法用于创建一个立方体,或者说是一个盒子。

HRESULT  D3DXCreateBox(
  __in   LPDIRECT3DDEVICE9 pDevice,//Direct3D设备对象
  __in   FLOAT Width, //盒子的宽度
  __in   FLOAT Height,//盒子的高度
  __in   FLOAT Depth,//盒子的深度
  __out  LPD3DXMESH *ppMesh,//存储着盒子网格的指针
  __out  LPD3DXBUFFER *ppAdjacency//存储三角形索引的指针
);

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个盒子网格之间关联起来了。

█第二个参数,FLOAT类型的Width,表示我们创建的盒子沿着X轴的宽度。

█第三个参数,FLOAT类型的Height,表示我们创建的盒子沿着Y轴的高度。

█第四个参数 , FLOAT类型的Depth,表示我们创建的盒子沿着Z轴的深度。

█第五个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateBox函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的盒子网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_box->DrawSubset(0);

█第六个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

下面是一个调用实例,创建一个长方体:

//用此函数产生一个长方体
         ID3DXMesh* meshBox;
         D3DXCreateBox(  
                            g_pd3dDevice, //D3D绘制对象
                            2.0f, //宽度
                            2.0f, //高度
                            2.0f, //深度
                            &meshBox,//对应COM对象
                            0 //指向ID3DXBuffer,存放相邻平面的信息,通常设置成0(或NULL)
                            );
         g_pd3dDevice->BeginScene();
         meshBox->DrawSubset(0);


2. 柱体的创建——D3DXCreateCylinder方法

D3DXCreateCylinder方法用于创建柱体。

HRESULT  D3DXCreateCylinder(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   FLOAT Radius1,
  __in   FLOAT Radius2,
  __in   FLOAT Length,
  __in   UINT Slices,
  __in   UINT Stacks,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个柱体网格之间关联起来了。

█ 第二个参数,FLOAT类型的Radius1,表示我们创建的柱体沿Z轴负方向的半径大小,这个参数显然必须大于等于0.0f。

█ 第三个参数,FLOAT Radius2, 表示我们创建的柱体沿Z轴正方向的半径大小,这个参数显然必须大于等于0.0f。

█ 第四个参数,FLOAT类型的Length,表示我们创建的柱体沿Z轴的长度。

█ 第五个参数,UINT类型的Slices,表示柱体的外围有几个面,举个例子,如果设为8的话,我们创建的就是八角柱了。

█ 第六个参数,UINT类型的Stacks,表示柱体的两端间共有几段,这个后面光照效果的时候需要要用到的。

█ 第七个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateCylinder函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的柱体网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_Cylinder->DrawSubset(0);

█ 第八个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

3. 2D 多边形的创建——D3DXCreatePolygon方法

D3DXCreatePolygon方法用于快速创建一个2D多边形。

HRESULT  D3DXCreatePolygon(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   FLOAT Length,
  __in   UINT Sides,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个2D 多边形网格之间关联起来了。

█ 第二个参数,UINT类型的Sides,表示我们创建的多边形每条边的长度。

█ 第三个参数,FLOAT Radius2, 表示我们创建的多边形的包含几个三角形。

█ 第四个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateCylinder函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的2D多边形网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_Polygon->DrawSubset(0);

█ 第五个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

下面是一个调用实例

//用此函数产生一个2D 多边体
         ID3DXMesh* meshPolygon;
         D3DXCreatePolygon( 
                            g_pd3dDevice, //D3D绘制对象
                            2.0f,  //每条边的长度
                            6, //包含几个三角形
                            &meshPolygon,
                            0 //通常设置成0(或NULL)
                            );
         g_pd3dDevice->BeginScene();
         meshPolygon->DrawSubset(0);


4. 球面体创建——D3DXCreateSphere方法

D3DXCreateSphere方法用于创建一个球面体。

HRESULT  D3DXCreateSphere(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   FLOAT Radius,
  __in   UINT Slices,
  __in   UINT Stacks,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个2D 多边形网格之间关联起来了。(浅墨在这里是复制粘贴上面的介绍,因为这个参数在本篇文章里介绍的几个创建几何体函数中都是一样的)

█ 第二个参数,UINT类型的Sides,表示我们创建的多边形每条边的长度。

█ 第三个参数,UINT类型的Slices, 表示我们创建的球面体绕主轴线切片数,也就是用几条经线来进行绘制。

█ 第四个参数,UINT类型的Stacks,表示我们创建的球面体绕主轴线的纬线数,也就是用几条纬线来进行绘制。

█ 第五个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateCylinder函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的球面体网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_Polygon->DrawSubset(0);

█ 第六个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

依然是一个调用实例:

//用此函数产生一个球面体
         ID3DXMesh* meshSphere; 
         D3DXCreateSphere(  
                        g_pd3dDevice, //D3D绘制对象
                            1.0f, //球面体半径
                            10, //用几条经线绘制
                            10, //用几条维线绘制
                            &meshSphere,
                         0 //通常设置成0(或NULL)
                            );
         g_pd3dDevice->BeginScene();
         meshSphere->DrawSubset(0);

看到这里如果大家觉得累了,来看几幅游戏美图吧,今天浅墨给大家准备的是2012年8月份刚上市的游戏大作《暗黑血统2》的桌面壁纸,这款游戏浅墨最近在玩,感觉还不错:

是不是很酷呢?上面的第二张图片浅墨可是做了很长时间的壁纸的呢。

好了,下面我们继续来讲解。


五 茶壶的创建——D3DXCreateTeapot方法

D3DXCreateTeapot方法用于创建一个茶壶。

HRESULT  D3DXCreateTeapot(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);

这个函数相对来说参数很少,他并没有给我们提供茶壶具体参数的设置。

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个2D 多边形网格之间关联起来了。(浅墨在这里是复制粘贴上面的介绍,因为这个参数在本篇文章里介绍的几个创建几何体函数中都是一样的)

█ 第二个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateCylinder函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的茶壶网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_Polygon->DrawSubset(0);

█ 第三个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

依然是一个调用实例:

 //用此函数产生一个茶壶
         ID3DXMesh* meshTeapot;
         D3DXCreateTeapot(
                            g_pd3dDevice, //D3D绘制对象
                            &meshTeapot,
                            0 //通常设置成0(或NULL)
                            );
         g_pd3dDevice->BeginScene();
         meshTeapot->DrawSubset(0);



六、圆环的创建——D3DXCreateTorus方法

D3DXCreateTorus方法用于创建一个圆环体。

HRESULT  D3DXCreateTorus(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   FLOAT InnerRadius,
  __in   FLOAT OuterRadius,
  __in   UINT Sides,
  __in   UINT Rings,
  __out  LPD3DXMESH *ppMesh,
  __out  LPD3DXBUFFER *ppAdjacency
);

█ 第一个参数,LPDIRECT3DDEVICE9类型的pDevice,指向IDirect3DDevice9接口的指针,这样一般就填我们在初始化四步曲里面创建的有效的Direct3D绘制”金钥匙”——Direct3D设备对象g_pd3dDevice。表示Direct3D设备对象和我们创建的这个2D 多边形网格之间关联起来了。(浅墨在这里是复制粘贴上面的介绍,因为这个参数在本篇文章里介绍的几个创建几何体函数中都是一样的)

█ 第二个参数,FLOAT类型的InnerRadius,表示我们创建的圆环的內圈半径。

█ 第三个参数,FLOAT类型的OuterRadius, 表示我们创建的圆环的外圈半径。

█ 第四个参数,UINT类型的Sides,表示我们创建的圆环的外圈有几个面,也就是大圆的轮廓是几边形,这个值显然要大于等于3.

█ 第五个参数,UINT类型的Rings,表示我们创建的圆环的内圈与外圈之间有几个面。

█ 第六个参数,LPD3DXMESH类型的*ppMesh,一个存储着我们创建的形状的指针的地址,也就是绘制用D3DXCreateCylinder函数创建的网格的金钥匙,后面需要绘制我们用这个函数创建的圆环体网格的话,拿这个指针指一下DrawSubset方法,也就是这样写g_Polygon->DrawSubset(0);

█ 第七个参数,LPD3DXBUFFER类型的*ppAdjacency,它存储着我们在这里绘制的网格的三角形索引的指针,我们不用他的话,设为0就可以了。

依然是一个调用实例:

//用此函数产生一个圆环体
         ID3DXMesh* meshTours; 
         D3DXCreateTorus(  
                         g_pd3dDevice, //D3D绘制对象
                            1.0f, //圆环的內圈半径
                            3.0f, //圆环的外圈半径
                            10, //外圈有几个面
                            10, //內圈和外圈间有几个面(同心圆)
                         &meshTours,
                         0 //通常设置成0(或NULL)
                          );
         g_pd3dDevice->BeginScene();
         meshTours->DrawSubset(0);


讲解最后做下总结,首先,我们可以发现这些方法的第一个参数和最后两个参数都是相同的。然后,这些函数的使用方法不用去强行记忆,用到的时候按D3DX+Create+几何体名字的命名方法去查一下SDK文档或者稍微看一下这篇博文就可以了。

四,详细注释的源代码讲解及欣赏

这里依旧是通过一个小程序,来把本篇文章所学的知识融会贯通。本篇文章里面我们介绍了Direct3D中立方体(Cube),圆环体(Torus),多边形(Polygon),球面体(Sphere),茶壶(Teapot)和圆柱体(Cylinder)这几种几何体的简洁绘制法,而下面就是一个通过本篇文章里介绍的方法绘制出四个几何体的程序的源代码。在贴出详细注释的源代码之前,我们先讲解一下写这个演示程序中的几个要点。

首先,我们定义了几个全局的ID3DXMesh对象,以及几个D3DXMATRIX矩阵类型。这里定义ID3DXMesh对象就是我们本节介绍的几何体绘制四步曲的第一步,而矩阵类型是为了后面给绘制的每个几何体不同的世界坐标位置以及做出旋转的镜头效果。相关代码如下:

LPD3DXMESH g_teapot = NULL;         //茶壶对象
LPD3DXMESH g_cube = NULL;            //立方体(盒子)对象
LPD3DXMESH g_sphere = NULL;         //球面体对象
LPD3DXMESH g_torus = NULL;           //圆环对象
D3DXMATRIX g_WorldMatrix[4],R;        //定义一些全局的世界矩阵


然后,我们在Objects_Init()方法中进行了几何体绘制四步曲的第二步,创建几何体的过程(我们准备绘制立方体,茶壶,球面体,茶壶这四个几何体,就在这一步当中进行了创建),以及调用了一些SetRenderState关闭了光照,开启了背面消隐,以及将填充模式默认设置为线框填充。因为Objects_Init()方法我们整个程序运行过程中只会运行一次,所以默认的渲染模式的设置都可以在这里进行。相关代码如下:

HRESULT Objects_Init()
{
	//创建字体
	if(FAILED(D3DXCreateFont(g_pd3dDevice, 30, 0, 0, 1, FALSE, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("宋体"), &g_pFont)))
		return E_FAIL;


	// 物体的创建
	if(FAILED(D3DXCreateBox(g_pd3dDevice, 2, 2, 2, &g_cube, NULL)))	//立方体的创建
		return false;
	if(FAILED(D3DXCreateTeapot(g_pd3dDevice, &g_teapot, NULL)))		//茶壶的创建
		return false;	
	if(FAILED(D3DXCreateSphere(g_pd3dDevice, 1.5, 25, 25,					//球面体的创建
		&g_sphere, NULL))) return false;
	if(FAILED(D3DXCreateTorus(g_pd3dDevice, 0.5f, 1.2f, 25, 25,				//圆环体的创建
		&g_torus, NULL))) return false;
	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);   //关闭光照
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);   //开启背面消隐
	g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);  //设置线框填充模式

	return S_OK;
}


接着,就是在Direct3D_Render()函数中进行绘制了,需要注意的是,四大矩阵变换我们原先都是封装在Matrix_Set()方法中进行的,而这里我们为了绘制的方便,我们把世界变换从Matrix_Set()方法中拿出来,放到了Direct3D_Render()之中。另外需要注意的一点是,制作出镜头旋转的方法,我们先用D3DXMatrixRotationY方法制作一个根据系统时间绕Y轴旋转的矩阵R出来,就是这样写:

D3DXMatrixRotationY(&R, ::timeGetTime() / 720.0f);


然后在设置好物体的世界矩阵坐标后,右乘一下我们制作好的矩阵R,然后把结果矩阵设置为当前的世界矩阵,再进行绘制就可以了。

// 进行立方体的绘制
         D3DXMatrixTranslation(&g_WorldMatrix[0], 3.0f, -3.0f, 0.0f);
         g_WorldMatrix[0]  =  g_WorldMatrix[0]*R; 
         g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix[0]);
         g_cube->DrawSubset(0);

关于这一步的完整代码由于比较长,就在讲解中不贴出来了,因为下面全部代码之中有的,其实就是Direct3D_Render()的函数的实现方法。

最后,绘制完成之后,在程序准备退出的时候就是几何体绘制四步曲的第四步——释放了。其实就是Direct3D_CleanUp()函数的实现代码:

//*****************************************************************************************
// Name: Direct3D_CleanUp()
// Desc: 对Direct3D的资源进行清理,释放COM接口对象
//*****************************************************************************************
void Direct3D_CleanUp()
{
	//释放COM接口对象	
	SAFE_RELEASE(g_torus)
	SAFE_RELEASE(g_sphere)
	SAFE_RELEASE(g_cube)
	SAFE_RELEASE(g_teapot)
	SAFE_RELEASE(g_pFont)
	SAFE_RELEASE(g_pd3dDevice)	
}


好了,讲解完成,最后我们贴出详细注释的源代码:

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码 三十九 浅墨DirectX教程之七 他山之石: 几种几何体的简洁绘制法
//		 VS2010版
// 2013年 1月6日  Create by 浅墨 
//图标素材取自: 游戏大作 《波斯王子:时之沙》
//此刻心情:如果你看到了前面的黑暗,不要担心,那是因为你的背后有阳光。


//程序运行说明: 按键盘上数字键“1”键和“2”键可以在线框填充模式和实体填充模式之间切换。默认情况下是线框填充
//***************************************************************************************** 




//*****************************************************************************************
// Desc: 头文件定义部分  
//*****************************************************************************************                                                                                       
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h>
#include   <time.h> 




//*****************************************************************************************
// Desc: 库文件定义部分  
//***************************************************************************************** 
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib, "winmm.lib ")


//*****************************************************************************************
// Desc: 宏定义部分   
//*****************************************************************************************
#define SCREEN_WIDTH	800						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define SCREEN_HEIGHT	600							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	_T("【Visual C++游戏开发笔记】博文配套demo之三十九 浅墨DirectX教程之七 他山之石: 几种几何体的简洁绘制法") //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }      //自定义一个SAFE_RELEASE()宏,便于资源的释放



//*****************************************************************************************
// Desc: 全局变量声明部分  
//*****************************************************************************************
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; //Direct3D设备对象
ID3DXFont*				g_pFont=NULL;    //字体COM接口
float					g_FPS = 0.0f;       //一个浮点型的变量,代表帧速率
wchar_t					g_strFPS[50];    //包含帧速率的字符数组
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL;    //顶点缓存对象
LPDIRECT3DINDEXBUFFER9  g_pIndexBuffer  = NULL;    // 索引缓存对象
LPD3DXMESH g_teapot = NULL;		//茶壶对象
LPD3DXMESH g_cube = NULL;		//立方体(盒子)对象
LPD3DXMESH g_sphere = NULL;		//球面体对象
LPD3DXMESH g_torus = NULL;		//圆环对象
D3DXMATRIX g_WorldMatrix[4],R;        //定义一些全局的世界矩阵

//*****************************************************************************************
// Desc: 全局函数声明部分 
//***************************************************************************************** 
LRESULT CALLBACK	WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
HRESULT				Direct3D_Init(HWND hwnd);
HRESULT				Objects_Init();
void				Direct3D_Render( HWND hwnd);
void				Direct3D_CleanUp( );
float				Get_FPS();
VOID				Matrix_Set();


//*****************************************************************************************
// Name: WinMain( )
// Desc: Windows应用程序入口函数
//*****************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{

	//开始设计一个完整的窗口类
	WNDCLASSEX wndClass = { 0 };				//用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化    
	wndClass.cbSize = sizeof( WNDCLASSEX ) ;	//设置结构体的字节数大小
	wndClass.style = CS_HREDRAW | CS_VREDRAW;	//设置窗口的样式
	wndClass.lpfnWndProc = WndProc;				//设置指向窗口过程函数的指针
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance = hInstance;				//指定包含窗口过程的程序的实例句柄。
	wndClass.hIcon=(HICON)::LoadImage(NULL,_T("icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标
	wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口类的光标句柄。
	wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //为hbrBackground成员指定一个灰色画刷句柄
	wndClass.lpszMenuName = NULL;						//用一个以空终止的字符串,指定菜单资源的名字。
	wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");		//用一个以空终止的字符串,指定窗口类的名字。

	if( !RegisterClassEx( &wndClass ) )				//设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
		return -1;		

	HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,			//喜闻乐见的创建窗口函数CreateWindow
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,
		SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );


	//Direct3D资源的初始化,调用失败用messagebox予以显示
	if (!(S_OK==Direct3D_Init (hwnd)))
	{
		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 
	}



	MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处
	ShowWindow( hwnd, nShowCmd );    //调用Win32函数ShowWindow来显示窗口
	UpdateWindow(hwnd);  //对窗口进行更新,就像我们买了新房子要装修一样

	

	

	//消息循环过程
	MSG msg = { 0 };  //初始化msg
	while( msg.message != WM_QUIT )			//使用while循环
	{
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			Direct3D_Render(hwnd);			//调用渲染函数,进行画面的渲染
		}
	}

	UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);
	return 0;  
}



//*****************************************************************************************
// Name: WndProc()
// Desc: 对窗口消息进行处理
//*****************************************************************************************
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc
{
	switch( message )				//switch语句开始
	{
	case WM_PAINT:					 // 客户区重绘消息
		Direct3D_Render(hwnd);          //调用Direct3D_Render函数,进行画面的绘制
		ValidateRect(hwnd, NULL);   // 更新客户区的显示
		break;									//跳出该switch语句

	case WM_KEYDOWN:                // 键盘按下消息
		if (wParam == VK_ESCAPE)    // ESC键
			DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息
		break;
	case WM_DESTROY:				//窗口销毁消息
		Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象
		PostQuitMessage( 0 );		//向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
		break;						//跳出该switch语句

	default:						//若上述case条件都不符合,则执行该default语句
		return DefWindowProc( hwnd, message, wParam, lParam );		//调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。
	}

	return 0;					//正常退出
}


//*****************************************************************************************
// Name: Direct3D_Init( )
// Desc: 初始化Direct3D
// Point:【Direct3D初始化四步曲】
//		1.初始化四步曲之一,创建Direct3D接口对象
//		2.初始化四步曲之二,获取硬件设备信息
//		3.初始化四步曲之三,填充结构体
//		4.初始化四步曲之四,创建Direct3D设备接口
//*****************************************************************************************

HRESULT Direct3D_Init(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
	//--------------------------------------------------------------------------------------
	LPDIRECT3D9  pD3D = NULL; //Direct3D接口对象的创建
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
 			return E_FAIL;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
	//--------------------------------------------------------------------------------------
	D3DCAPS9 caps; int vp = 0;
	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
		{
			return E_FAIL;
		}
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
	//--------------------------------------------------------------------------------------
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.BackBufferWidth            = SCREEN_WIDTH;
	d3dpp.BackBufferHeight           = SCREEN_HEIGHT;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 1;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = true;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = 0;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
	//--------------------------------------------------------------------------------------
	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
		hwnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	if(!(S_OK==Objects_Init())) return E_FAIL;



	SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉

	return S_OK;
}


HRESULT Objects_Init()
{
	//创建字体
	if(FAILED(D3DXCreateFont(g_pd3dDevice, 30, 0, 0, 1, FALSE, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("宋体"), &g_pFont)))
		return E_FAIL;


	// 物体的创建
	if(FAILED(D3DXCreateBox(g_pd3dDevice, 2, 2, 2, &g_cube, NULL)))	//立方体的创建
		return false;
	if(FAILED(D3DXCreateTeapot(g_pd3dDevice, &g_teapot, NULL)))		//茶壶的创建
		return false;	
	if(FAILED(D3DXCreateSphere(g_pd3dDevice, 1.5, 25, 25,					//球面体的创建
		&g_sphere, NULL))) return false;
	if(FAILED(D3DXCreateTorus(g_pd3dDevice, 0.5f, 1.2f, 25, 25,				//圆环体的创建
		&g_torus, NULL))) return false;
	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);   //关闭光照
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);   //开启背面消隐
	g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);  //设置线框填充模式

	return S_OK;
}


//*****************************************************************************************
// Name:Matrix_Set()
// Desc: 设置世界矩阵
// Point:【Direct3D四大变换】
//		1.【四大变换之一】:世界变换矩阵的设置
//		2.【四大变换之二】:取景变换矩阵的设置
//		3.【四大变换之三】:投影变换矩阵的设置
//		4.【四大变换之四】:视口变换的设置
//*****************************************************************************************
VOID Matrix_Set()
{
	//--------------------------------------------------------------------------------------
	//【四大变换之一】:世界变换矩阵的设置
	//--------------------------------------------------------------------------------------


	//--------------------------------------------------------------------------------------
	//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matView; //定义一个矩阵
	D3DXVECTOR3 vEye(0.0f, 0.0f, -15.0f);  //摄像机的位置
	D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //观察点的位置
	D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);//向上的向量
	D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //计算出取景变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //应用取景变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之三】:投影变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matProj; //定义一个矩阵
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, 1.0f, 1.0f, 1000.0f); //计算投影变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);  //设置投影变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之四】:视口变换的设置
	//--------------------------------------------------------------------------------------
	D3DVIEWPORT9 vp; //实例化一个D3DVIEWPORT9结构体,然后做填空题给各个参数赋值就可以了
	vp.X      = 0;		//表示视口相对于窗口的X坐标
	vp.Y      = 0;		//视口相对对窗口的Y坐标
	vp.Width  = SCREEN_WIDTH;	//视口的宽度
	vp.Height = SCREEN_HEIGHT; //视口的高度
	vp.MinZ   = 0.0f; //视口在深度缓存中的最小深度值
	vp.MaxZ   = 1.0f;	//视口在深度缓存中的最大深度值
	g_pd3dDevice->SetViewport(&vp); //视口的设置

}



//*****************************************************************************************
// Name: Direct3D_Render()
// Desc: 进行图形的渲染操作
// Point:【Direct3D渲染五步曲】
//		1.渲染五步曲之一,清屏操作
//		2.渲染五步曲之二,开始绘制
//		3.渲染五步曲之三,正式绘制
//		4.渲染五步曲之四,结束绘制
//		5.渲染五步曲之五,翻转显示
//*****************************************************************************************

void Direct3D_Render(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之一】:清屏操作
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制

	Matrix_Set();

	// 获取键盘消息并给予设置相应的填充模式
	if (::GetAsyncKeyState(0x31) & 0x8000f)         // 若数字键1被按下,进行线框填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
	if (::GetAsyncKeyState(0x32) & 0x8000f)         // 若数字键2被按下,进行实体填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
	//--------------------------------------------------------------------------------------

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之四】:绘制图形
	//--------------------------------------------------------------------------------------


	  D3DXMatrixRotationY(&R, ::timeGetTime() / 720.0f);


	// 进行立方体的绘制
	D3DXMatrixTranslation(&g_WorldMatrix[0], 3.0f, -3.0f, 0.0f);
	g_WorldMatrix[0]  =  g_WorldMatrix[0]*R; 
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix[0]);
	g_cube->DrawSubset(0);

	//进行茶壶的绘制
	D3DXMatrixTranslation(&g_WorldMatrix[1], -3.0f, -3.0f, 0.0f);
	g_WorldMatrix[1]  =  g_WorldMatrix[1]*R; 
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix[1]);	
	g_teapot->DrawSubset(0);

	// 进行圆环的绘制
	D3DXMatrixTranslation(&g_WorldMatrix[2], 3.0f, 3.0f, 0.0f);
	g_WorldMatrix[2]  =  g_WorldMatrix[2]*R; 
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix[2]);	
	g_torus->DrawSubset(0);

	// 进行球面体的绘制
	D3DXMatrixTranslation(&g_WorldMatrix[3], -3.0f, 3.0f, 0.0f);
	g_WorldMatrix[3]  =  g_WorldMatrix[3]*R; 
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_WorldMatrix[3]);
	g_sphere->DrawSubset(0);

	//在窗口右上角处,显示每秒帧数
	int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );
	g_pFont->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_XRGB(255,239,136));

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之五】:显示翻转
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}


//*****************************************************************************************
// Name:Get_FPS()函数
// Desc: 用于计算帧速率
//*****************************************************************************************
float Get_FPS()
{

	//定义四个静态变量
	static float  fps = 0; //我们需要计算的FPS值
	static int    frameCount = 0;//帧数
	static float  currentTime =0.0f;//当前时间
	static float  lastTime = 0.0f;//持续时间

	frameCount++;//每调用一次Get_FPS()函数,帧数自增1
	currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间

	//如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零
	if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟
	{
		fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值
		lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间
		frameCount    = 0;//将本次帧数frameCount值清零
	}

	return fps;
}


//*****************************************************************************************
// Name: Direct3D_CleanUp()
// Desc: 对Direct3D的资源进行清理,释放COM接口对象
//*****************************************************************************************
void Direct3D_CleanUp()
{
	//释放COM接口对象	
	SAFE_RELEASE(g_torus)
	SAFE_RELEASE(g_sphere)
	SAFE_RELEASE(g_cube)
	SAFE_RELEASE(g_teapot)
	SAFE_RELEASE(g_pFont)
	SAFE_RELEASE(g_pd3dDevice)	
}


编译并运行上面的源代码,我们就可以得到如下四个排列规则的几何体,且这些几何体都在不停地绕着Y轴不停地旋转着,按按键盘上数字键“1”键和“2”键可以在线框填充模式和实体填充模式之间切换:

按下数字键“2”,我们可以看到这些几何体的实体填充模式:

因为我们关闭了光照,而且材质也没给我们的几何体设置,所以在实体填充模式下这些几何体白得有些惨不忍睹,立体感全无,没关系,敬请期待浅墨下篇讲解光照和材质的文章。

文章最后,依旧是放出本篇文章配套源代码的下载:

本节笔记配套源代码请点击这里下载:

【浅墨DirectX提高班】配套源代码之七下载

其中图标素材取自的游戏大作 波斯王子:时之沙

以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。

浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。

文章最后,依然是【每文一语】栏目,今天是来自张爱玲的句子:

在这个光怪陆离的人间,没有谁可以将日子过得行云流水。但我始终相信,走过平湖烟雨,岁月山河,那些历尽劫数、尝遍百味的人,会更加生动而干净。时间永远是旁观者,所有的过程和结果,都需要我们自己承担。

下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
不错的dx11入门教程 Tutorial 1: Setting up DirectX 11 with Visual Studio Tutorial 2: Creating a Framework and Window Tutorial 3: Initializing DirectX 11 Tutorial 4: Buffers, Shaders, and HLSL Tutorial 5: Texturing Tutorial 6: Diffuse Lighting Tutorial 7: 3D Model Rendering Tutorial 8: Loading Maya 2011 Models Tutorial 9: Ambient Lighting Tutorial 10: Specular Lighting Tutorial 11: 2D Rendering Tutorial 12: Font Engine Tutorial 13: Direct Input Tutorial 14: Direct Sound Tutorial 15: FPS, CPU Usage, and Timers Tutorial 16: Frustum Culling Tutorial 17: Multitexturing and Texture Arrays Tutorial 18: Light Maps Tutorial 19: Alpha Mapping Tutorial 20: Bump Mapping Tutorial 21: Specular Mapping Tutorial 22: Render to Texture Tutorial 23: Fog Tutorial 24: Clipping Planes Tutorial 25: Texture Translation Tutorial 26: Transparency Tutorial 27: Reflection Tutorial 28: Screen Fades Tutorial 29: Water Tutorial 30: Multiple Point Lights Tutorial 31: 3D Sound Tutorial 32: Glass and Ice Tutorial 33: Fire Tutorial 34: Billboarding Tutorial 35: Depth Buffer Tutorial 36: Blur Tutorial 37: Coming Soon... DirectX 10 Tutorials: Tutorial 1: Setting up DirectX 10 with Visual Studio Tutorial 2: Creating a Framework and Window Tutorial 3: Initializing DirectX 10 Tutorial 4: Buffers, Shaders, and HLSL Tutorial 5: Texturing Tutorial 6: Diffuse Lighting Tutorial 7: 3D Model Rendering Tutorial 8: Loading Maya 2011 Models Tutorial 9: Ambient Lighting Tutorial 10: Specular Lighting Tutorial 11: 2D Rendering Tutorial 12: Font Engine Tutorial 13: Direct Input Tutorial 14: Direct Sound Tutorial 15: FPS, CPU Usage, and Timers Tutorial 16: Frustum Culling Tutorial 17: Multitexturing and Texture Arrays Tutorial 18: Light Maps Tutorial 19: Alpha Mapping Tutorial 20: Bump Mapping Tutorial 21: Specular Mapping Tutorial 22: Render to Texture Tutorial 23: Fog Tutorial 24: Clipping Planes Tutorial 25: Texture Translation Tutorial 26: Transparency Tutorial 27: Reflection Tutorial 28: Screen Fades Tutorial 29: Water Tutorial 30: Multiple Point Lights Tutorial 31: 3D Sound Tutorial 32: Glass and Ice Tutorial 33: Fire Tutorial 34: Billboarding Tutorial 35: Depth Buffer Tutorial 36: Blur Tutorial 37: Coming Soon... DirectX 10 Terrain Tutorials: Tutorial 1: Grid and Camera Movement Tutorial 2: Height Maps Tutorial 3: Terrain Lighting Tutorial 4: Terrain Texturing Tutorial 5: Color Mapped Terrain Tutorial 6: Quad Trees Tutorial 7: Coming Soon... 。。。。。。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值