游戏编程之DirectX的修炼:三(DirectX的初始化:下)

        哇卡卡卡,哈哈哈,这节咋们就来学习初始化DirectX的具体代码和细节吧。

        还几次上节说的我们的初始化主要有两个步骤,我们再看下图。


       是否心里已经明白我们大概要做什么了那?

      1. 好,首先来解决初始化的第一步,获得IDirect3DDevice9这个接口。 

       IDirect3DDevice9这个接口又叫设备接口,设备,什么设备?当然就是我们的硬件了。回想之前我们创建窗口的时候,我们说要有一个自己的窗口,需要一些最基本的信息来告诉我们窗口长啥样子,同理,我们在创建IDirect3DDevice9这个设备接口时候我们也要知道一些基本的信息,才能让我们知道这个设备接口大概是什么样子(这里的样子指的是它的属性,并不是具体外观),而这些基本信息就是我们的硬件信息了,因为我们说过DirectX是可以直接和硬件打交道的对吧。那么,也就是说。创建一个设备接口,我们需要获取硬件信息,然后把这些消息告知我们的设备接口,我们的设备接口就算完成了。

       总结起来就是两步:

      1.1:获取硬件信息

      1.2:将信息告知我们的设备接口,创建设备接口

没问题把,获得设备接口就这么两步,那我们先走第一步:获得硬件信息

          首先,想要获取,硬件信息,我们需要创建一个LPDIRECT3D9接口,创建这个接口不需要任何条件,我们通过这个接口调用GetDeviceCaps这个函数,就能获得硬件信息,同时我们也是通过LPDIRECT3D9接口调用CreateDevice函数最终创建我们的IDirect3DDevice9设备接口的。稍后便会讲到。我们先看一下用LPDIRECT3D9获得硬件信息的代码。

        LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
	if (NULL == pD3D) { return E_FAIL; }
	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;//软件顶点运算模式

      我们看一下这一段代码,首先第一句话,我们使用了一个Direct3DCreate9函数来创建了一个LPDIRECT3D9接口,Direct3DCreate9这个函数只有一个参数,这个参数的意义是我们当前的DirectX版本,保证我们包含的头文件能正确调用相应的.dll库。如果失败,说明版本协商有问题。

      然后是我们定义了一个D3DCAPS9型的结构体来保存我们的硬件信息。大家看到了我们还定义了一个整形的vp变量,这个变量是用来保存我们硬件是否支持硬件顶点运算模式,如果支持,那么后面我们将告诉我们的设备接口,让它在渲染的时候使用硬件顶点运算模式,如果不行就只能使用软件顶点运算模式了。最后的那句if else语句就是用来判断是否支持硬件顶点运算模式,当然,硬件顶点运算模式是要比软件顶点运算模式快很多的。这也是我们判断的原因。

      最后就是中间的GetDeviceCaps函数了,这个函数就是用来获取硬件信息的。

HRESULT GetDeviceCaps(
  [in]  UINT       Adapter,//显卡序号
  [in]  D3DDEVTYPE DeviceType,//设备类型
  [out] D3DCAPS9   *pCaps//保存硬件信息的结构体
);


第一个参数一般填写默认的:D3DADAPTER_DEFAULT

第二个参数,我们也一般填写硬件设备类型:D3DDEVTYPE_HAL

最后一个参数就是我们需要保存信息的结构体,当我们调用完这个函数以后,设备信息就存到caps里面去了。ok,取得硬件信息就是这么多了。

  1.2:将信息告知我们的设备接口,创建设备接口

我们已经取得了,硬件信息,是不是马上可以开始创建设备接口了哪?是的,但是在创建设备接口之前,我们还需要设置设备接口的另一些详细信息。而幸运的是,这些信息都包含在一个结构体内,还记得我们填窗口填写的WINDCLASSEX吗?这次我们要为设备填写一个叫做D3DPRESENT_PARAMETERS的结构体,这个结构体非常重要!

typedef struct _D3DPRESENT_PARAMETERS_
{
    UINT                BackBufferWidth;//交换链后台缓存宽度
    UINT                BackBufferHeight;//交换链后台缓存高度
    D3DFORMAT           BackBufferFormat;//后台缓存格式,他的参数是D3DFORMAT的成员之一
    UINT                BackBufferCount;//后台缓存个数

    D3DMULTISAMPLE_TYPE MultiSampleType;//多重采样格式
    DWORD               MultiSampleQuality;//多重采样类型

    D3DSWAPEFFECT       SwapEffect;//将后台缓存内容复制到前台的方式.
    HWND                hDeviceWindow;//我们的窗口句柄
    BOOL                Windowed;//是否全屏
    BOOL                EnableAutoDepthStencil;//自动深度缓存,如果为真,需要设置下一个参数。
    D3DFORMAT           AutoDepthStencilFormat;//深度缓存的像素格式
    DWORD               Flags;//附加属性

    /* FullScreen_RefreshRateInHz must be zero for Windowed mode */
    UINT                FullScreen_RefreshRateInHz;//全屏模式时候的刷新率
    UINT                PresentationInterval;前台缓存与后台缓存的最大交换速率。在D3DPRESENT中取值
} 
好了这就是这个可以为我们的设备接口做出巨大贡献的结构体,可能大家第一次看到会觉得很生疏,如果大家有看不懂的地方,请不要在意,因为你不懂的,后面都会慢慢讲的。大家只需要记住这是个重要的结构体就行。接下来看看它的赋值


        D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
	ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
	d3dpp.BackBufferWidth = WINDOW_WEIGHT;//缓冲区宽度
	d3dpp.BackBufferHeight = WINDOW_HIGHT;//缓冲区高度
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT//++
	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;//深度缓存像素格式D3DFORMAT中取值
	d3dpp.Flags = 0;//附加属性,通常为0
	d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率可以在D3DPRESENT取值

好了 硬件信息有了,结构体也有啦,那就开始创建我们的设备接口把

pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,hwnd, vp, &d3dpp, &g_pd3dDevice)
pD3D->Release();

好吧 创建是非常简单的,一个CreateDevice函数就搞定了,调用这个函数后我们就可以用设备接口IDirect3DDevice9搞事情了。不过在此之前我们还是介绍下CreateDevice

HRESULT CreateDevice(
  [in]          UINT                  Adapter,//显卡序号,一般填0,D3DADAPTER_DEFAULT=0
  [in]          D3DDEVTYPE            DeviceType,//设备类型,当然是硬件
  [in]          HWND                  hFocusWindow,//窗口句柄
  [in]          DWORD                 BehaviorFlags,//顶点运算模式
  [in, out]     D3DPRESENT_PARAMETERS *pPresentationParameters,//千辛万苦填的接口体
  [out, retval] IDirect3DDevice9      **ppReturnedDeviceInterface//指向我们的设备接口的指针,这个参数就是我们的设备接口.
);


好啦  到此为止我们Direct3D的初始化第一部分创建设备接口IDirect3DDevice9就讲完了,我们把完整代码贴出来

LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
	if (NULL == pD3D) { return E_FAIL; }
	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;//软件顶点运算模式
	
	D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
	ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
	d3dpp.BackBufferWidth = WINDOW_WEIGHT;//后台缓冲区宽度
	d3dpp.BackBufferHeight = WINDOW_HIGHT;//后台缓冲区高度
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT
	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;//深度缓存像素格式D3DFORMAT中取值
	d3dpp.Flags = 0;//附加属性,通常为0
	d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率,窗口时为0在AdapterModes枚举中取值
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率可以在D3DPRESENT取值
															
	if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		hwnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	pD3D->Release();//LPDIRECT3D9接口对象的使命完成释放

	return S_OK;

以上就是初始化第一部分的全部代码了,记得当我们使用完LPDIRECT3D9这个接口时,要把他Release掉。


       2.使用我们的设备接口进行渲染并且在主事件循环中不断渲染

由于现在我们只是在初始化D3D,所以我们的不会在平面上渲染任何东西,所以这部分的代码也非常简单,就是使用我们的IDirect3DDevice9调用几个函数就可以搞定,然后把他们放入我们的主事件循环中不断地渲染就好啦,那我们来看看这几个函数把。

void Render()
{
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 255), 1.0, 0);//清空后台缓存
	g_pd3dDevice->BeginScene();//开始绘制

	g_pd3dDevice->EndScene();//结束绘制
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);//交换与显示
}


好,看上去是不是很简单。我定义了一个函数Render将我们进行渲染地几个函数放在了一起。

首先还是来看看咋们是第一个函数Clear,这个函数的主要作用就是设置我们清空了后台缓冲之后的一些属性。

HRESULT Clear(
  [in]       DWORD    Count,//指向一个矩形数组地数量,与下一个函数有关,如果下一个函数为NULL,则这里为零,反之,下一个参数不是NULL,这里就不能写零
  [in] const D3DRECT  *pRects,//指向一个矩形结构数组地指针
  [in]       DWORD    Flags,//需要清空地缓冲区,它可以是模板缓冲,颜色缓冲,和深度缓冲三个值地任意组合,用|连接。
  [in]       D3DCOLOR Color,//清空缓冲区后每个像素地颜色值
  [in]       float    Z,//清空深度缓冲后地每个像素地深度值
  [in]       DWORD    Stencil//清空模板缓冲后地每个像素地模板值
);


好后面的三个函数都非常简单

     BeginScene代表着开始绘制

     EndScene代表着结束绘制

     而我们以后需要在屏幕上绘制东西时的代码函数都在BeginScene和EndScene之间

     当所有的绘制完成之后我们就调用Present将绘制的内容显示出来,这个函数也非常简单四个参数都填NULL就ok了!


既然渲染的函数都写好了,那就赶紧将我们的渲染函数放到主循环函数中开始渲染把。是的没错,但是在那之前我们还有最后一个问题需要处理,什么问题?就是关于我们主事件循环的问题,我们先贴一下主事件循环的代码。

	MSG msg = {0};//定义meg
	while(GetMessage(&msg, NULL, 0, 0)) //使用getmessage获得消息
	{
		TranslateMessage(&msg);//转换消息
		DispatchMessage(&msg);//发消息给程序,然后交给os调用窗口处理函数
	}
          之前我们说过,当我们生成窗口之后,OS就会给我们的程序生成一个消息队列,而我们的GetMessage函数则通过While循环不断获取消息,但是大家有没有想过,一旦消息队列为空咋办?会咋办哪?其实,一旦消息队列为空,则GetMessage 函数会一直停在那里等待消息,而不会继续执行下面的代码,试想一下,假如你在玩儿一个游戏,一旦你停止按键盘或者鼠标,整个游戏画面就不再更新,这将是多么可怕的一件事情,所以。我们希望不论消息队列中有没有消息,我们的主循环函数都不会阻塞,并且不断地渲染我们的画面。所以这时候我们需要请出PeekMessage这个函数来代替GetMessage函数。
        那么PeekMessage有什么优点哪?
它唯一的优点就是,当我们的消息队列中没有消息的时候,它会返回一个值继续往下执行,不会一直等待消息队列中有消息才返回。下面我们看看将主事件循环改为PeekMessage之后时什么样

         

MSG msg = { 0 };//定义并初始化 meg
	while (msg.message != WM_QUIT)//消息循环
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //使用peekmessage获得消息
		{
			TranslateMessage(&msg);//转换消息
			DispatchMessage(&msg);//分发消息给程序,然后交给os调用窗口处理函数
		}
		else
		{
			Render();
		}

	}
好了,我们可以看到由于PeekMessage不会一直等待消息队列的消息,所以当消息队列没有消息的时候,我们就让它执行我们的渲染函数,这样我们的画面就会一直更新,还有一点就是PeekMessage的前四个参数和Get Message一样,只有最后多一个参数,这个参数是告诉PeekMessage在查看消息队列中的消息时,是否要将查看的消息移出消息队列。好吧,最后我们把一个完整的Direct3D初始化程序贴出来吧!

//======================================================================================================//
//————————————————————————程序说明———————————————————————//
//程序名称:DirectInitDemo
//2017.9.5  淡一抹夕霞
//======================================================================================================//


//==========================================//
//——————头文件部分——————————//
#include"windows.h"
#include<d3d9.h>
#include<d3dx9.h>
//==========================================//


//==========================================//
//——————库文件部分——————————//
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"d3d9.lib")  
//==========================================//

//==========================================//
//———--———全局变量——————————//
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;//定义一个direct3D设备对象
int Color=0;//用于更改清空后台缓存时的颜色
//==========================================//



//==========================================//
//——————全局函数声明———————--—//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);//窗口处理函数
HRESULT D3Dinit(HWND hWnd, HINSTANCE hInstance);//d3d初始化函数
void    Render();//d3d渲染函数
//==========================================//

//===========================================================================================//
//—————--------------------—程序的主函数WinMain———-----------------------------——-//
//===========================================================================================//
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	                   ///
	                   //                                   //
	                   //-----------设计窗口部分------------//
	                   //                                   //
	                   ///
	WNDCLASSEX wndclassex = { 0 };//创建一个窗口类,并且记得初始化
	wndclassex.cbSize = sizeof(wndclassex);//节数大小
	wndclassex.style = CS_VREDRAW | CS_HREDRAW;//样式标记
	wndclassex.lpfnWndProc = WndProc;//指向窗口事件处理函数的指针
	wndclassex.hInstance = hInstance;//应用程序实例句柄
	wndclassex.cbClsExtra = 0;//额外的类信息
	wndclassex.cbWndExtra = 0;//额外的窗口信息
	wndclassex.hIcon = LoadIcon(NULL, IDI_APPLICATION);//加载ico图标
	wndclassex.hCursor = ::LoadCursor(NULL, IDC_ARROW);//指定窗口类的光标句柄
	wndclassex.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);//为成员指定一个灰色画刷
	wndclassex.lpszMenuName = NULL;//要加入窗口的菜单名
	wndclassex.lpszClassName = L"WINDOW_CLASS";//窗口类名

					///
					//                                   //
					//-----------注册窗口部分------------//
					//                                   //
					///
	RegisterClassEx(&wndclassex);//向我们的"政府申请注册"

					///
					//                                   //
					//-----------创建窗口部分------------//
				        //                                   //
				        ///
	HWND hWnd = CreateWindowEx(NULL, L"WINDOW_CLASS",L"MyDirectDemo",//直接调用创建函数就好了
		WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
		800, 600, NULL, NULL, hInstance, NULL);

	

	
	ShowWindow(hWnd, nCmdShow);//显示窗口
	UpdateWindow(hWnd);//更新窗口
    ///

	/
	//在此初始化Direct3D
	D3Dinit(hWnd, hInstance);
	

				          ///
				          //                                   //
				          //------------主循环部分-------------//
				          //                                   //
				          ///
	MSG msg = { 0 };//定义并初始化 meg
	while (msg.message != WM_QUIT)//消息循环
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) //使用peekmessage或者getmessage获得消息
		{
			TranslateMessage(&msg);//转换消息
			DispatchMessage(&msg);//分发消息给程序,然后交给os调用窗口处理函数
		}
		else
		{
			Render();
		}

	}


	return msg.wParam;
}

/
//                                             //
//------------窗口消息处理函数部分-------------//
//                                             //
/

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT paintStruct;//定义一个paintstruct记录一些绘制信息
	HDC hdc;//设备环境句柄
	switch (message)//开始处理
	{
	case WM_PAINT://重绘消息  更新客户区

		hdc = BeginPaint(hWnd, &paintStruct);//指定窗口进行绘图准备,并在ps结构中保存相关信息

		EndPaint(hWnd, &paintStruct);//窗口绘图过程结束
		break;
	case WM_KEYDOWN://键盘按下消息

		if (wParam == VK_ESCAPE)//如果是esc
			DestroyWindow(hWnd);//销毁窗口,发送WM_DESTROY消息
		break;
	case WM_DESTROY://销毁消息

		PostQuitMessage(0);//向os申请终止请求。
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);//默认的窗口过程处理函数
	}

	return 0;
}


HRESULT D3Dinit(HWND hWnd, HINSTANCE hInstance)
{
	LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);//创建接口对象获取硬件信息并为后面的设备对象做准备
	if (NULL == pD3D) { return E_FAIL; }
	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;//软件顶点运算模式

	D3DPRESENT_PARAMETERS d3dpp;//一个非常重要的结构体
	ZeroMemory(&d3dpp, sizeof(d3dpp));//用零填充d3dpp这块内存
	d3dpp.BackBufferWidth = 800;//缓冲区宽度
	d3dpp.BackBufferHeight =600;//缓冲区高度
	d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;//缓冲区保存像素格式D3DFORMAT//+
	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;//附加属性,通常为0
	d3dpp.FullScreen_RefreshRateInHz = 0;//全屏模式时候的屏幕刷新率,
	d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;//前后台交换的最大频率

	if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		hWnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	pD3D->Release();//LPDIRECT3D9接口对象的使命完成  释放
	
	return S_OK;
}

void Render()
{
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(Color, 0, 255), 1.0, 0);
	g_pd3dDevice->BeginScene();

	g_pd3dDevice->EndScene();
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
	Color += 1;
	if (Color >= 255)
		Color = 0;
}


好的,大家可以复制代码运行一下看看,是不是觉得很闪?因为我设置了个变量每次清空后台缓存时都将颜色的R值加1;


好吧,这一节就到这里,如果有不明白的,相信大家都明白,大家心中对渲染或许还有疑问,但是没关系,后面会详细介绍。当然有什么建议可以留言哦。感谢!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值