【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计:公告板(Billboard)技术
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/13278851
作者:毛星云(浅墨) 微博:@浅墨_毛星云 邮箱: happylifemxy@163.com
现在大家看到的,就是【Visual C++】游戏开发系列文章第三季中作为正式回归的第一篇文章了。
这里的3D人物,可就是一张图片哦,而不是之前我们用的3D人物模型。
不过,浅墨知道这样的努力到最终肯定都是值得的,就像之前有一次每日一语里面的那个句子一样:
“面对生活,我们没有选择,但是请始终相信,现在所经历的一切,都有它存在的意义。”
一、公告板(Billboard)技术的概念和定位
需要再次强调的是,公告板技术通常都是和纹理的Alpha混合一起使用的,这样便能够以极低的系统资源实现丰富的绘制效果。
关于地位,公告板技术在3D游戏中有着非常广泛的应用,因为它可以极大地节省资源、提高游戏的流畅度,降低对3D游戏素材的依赖,以及降低对配置要求的门槛。
二、对公告板技术的情境理解
公告板的原理是使用两个三角形组成的矩形显示一张平面位图,并且在显示的过程中,该平面矩形的角度随虚拟摄像机的观察角度和位置的变化而变化。
在Direct3D应用中,公告板的实现方式通常是两种,一种是让某个平面始终对着虚拟摄像机,也就是平面模型的屏幕与观察者的视线垂直(图1)。
另一种是让平面模型全部朝向投影空间的前屏幕,也就是平面模型与投影平面相平行(图2)。
公告板最常见的例子是用作模拟3D树木。想要模拟3D树木往往不用真正的3D模型,而是用看上去很像3D树木的2D图像映射到一个矩形平面上。
如果我们向某个方向观察一个映射到多边形的树木时,由于多边形是朝某个固定方向的,此时能够正确的观察该树木。但是当我们与树木朝向之间有一个观察角度的话,图形在观察者的视野中就会逐渐变窄。
但是如果我们用了告板技术的话,那么树木始终都是朝向用户的,这个时候,无论我们如何在水平方向转移观察方向都可以正确地观察到没有很多异样的树木。
为了印象深刻,再来一个对比吧,如果不使用公告板技术,在水平方向上环绕着移动视角,便显示出了扭曲的图形:
三、公告板技术的实现原理理解
1. 在合适的位置和朝向放置矩形平面
2. 让矩形平面始终和视线垂直
3. 将位图贴到矩形平面上
公告板技术的原理其实很简单,就是使用两个三角形组成的矩形来显示一个位图,在显示过程中这个矩形面板根据摄像机的观察角度和位置而变化。这里看起来又和前面章节中讲到的在3D中显示2D图形有些像,但它们是不同的。当采用正交投影时,显示出来的图形将不具有纵深感,不会随着距离的远近而产生缩 放。而在3D环境中一般的面板又不能很好地显示出效果。
一般来说,公告板技术可以分成三类,分别应用于不同的游戏类型需求:
1.观察者可以在游戏虚拟空间中任意移动,而我们的工作是让某一模型始终对着摄像机。例如,一个太阳。这一类在当前技术中使用较少,因为目前硬件的能力突飞猛进,直接放一个3D模型的太阳就行了。
2.在角色扮演游戏中,摄像机可以做的旋转只限于水平和竖直。程序员的目的是:无论角色如何走动,某一个平面模型始终朝向观察点。简单来说,就是平面模型的平面和视线垂直。
a.垂直于视线图示
(3)第三类和第二类相似,差异在于平面模型不是全部朝向观察点,而是全部朝向投影空间的前屏幕。简单来说,就是平面模型始终与屏幕平行。
b.平行于屏幕图示
四、公告板技术使用五步曲
还是老规矩,深入浅出,没力气没精力理解前面的那些理论的话(其实前面的理论也不难的- -),落实到一个用上,不妨直接啃这一节总结好的精粹知识,瞬间就学会了如何快速上手使用公告板技术。
关于这五步曲的大体思路,其实就是纹理映射几步曲那一套弄完之后,渲染的时候加关于公告板矩阵的一些额外操作就是了。而最关键的两个步骤:一是在合适的位置和朝向放置矩形平面,二是将位图贴到矩形平面上。
Ⅰ.五步曲之一:顶点的定义
想实现公告板的话,首先当然是需要定义公告板矩形的灵活顶点格式FVF。因为在公告板矩形中只显示一张位图,所以在灵活顶点格式中仅需要包含位置和一对纹理坐标。
这一步当然是定义描述广告牌矩形的灵活顶点格式。由于在广告牌矩形上只用显示一张位图就行,所以在灵活顶点格式中只要包含位置和一对纹理坐标,但是其实我们之前在贴地面的纹理的时候就用到了相同格式的FVF顶点格式,所以如果在之前的demo上修改代码的话,这步根本不用去新加什么代码,知道我们之前用烂了的那个FVF顶点格式结构体CUSTOMVERTEX这次继续服役就行了~
//定义FVF顶点结构 structCUSTOMVERTEX {FLOAT _x, _y, _z;FLOAT _u, _v ;CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z,FLOAT u, FLOAT v): _x(x), _y(y), _z(z), _u(u),_v(v) {} }; #defineD3DFVF_CUSTOMVERTEX (D3DFVF_XYZ |D3DFVF_TEX1)
Ⅱ、五步曲之二:顶点的访问
填充顶点和之前的纹理映射步骤里的代码除了顶点的坐标设置需要额外讲究以外,其他的方面几乎一模一样。而需要讲究的是放置公告板所在的矩形位置,也就是四个顶点的位置。最好是离是摄像机所在的位置远一点,因为距离产生美嘛,毕竟纸是包不住火的,距离太近的话,无论是什么诡计,都会被识破的~
那么,关于公告板矩形顶点的设置,代码如下:
//-----------------------------【创建NPC的顶点缓存】--------------------------------g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0,D3DFVF_CUSTOMVERTEX,D3DPOOL_MANAGED, &g_pNPCVBuffer , NULL );pVertices= NULL;g_pNPCVBuffer->Lock(0, 0, (void**)&pVertices, 0);pVertices[0]= CUSTOMVERTEX(-100.0f, 0.0f, 0.0f, 0.0f, 1.0f);pVertices[1]= CUSTOMVERTEX(-100.0f, 320.0f, 0.0f, 0.0f, 0.0f);pVertices[2]= CUSTOMVERTEX( 100.0f, 0.0f, 0.0f, 1.0f, 1.0f);pVertices[3]= CUSTOMVERTEX( 100.0f, 320.0f, 0.0f, 1.0f, 0.0f);g_pNPCVBuffer->Unlock();
即公告板矩形的四个点是(-100,0),(-100,320),(100,0),(100,320)。
Ⅲ、五步曲之三:纹理的创建
这步里面最好是找到一张看起来像3D的戴透明通道的图片,这样做出来的效果才会很真实。我们选取了一张游戏人物中的3D模型的渲染图,看起来具有3D的效果,放置在工程目录GameMedia\\girl.png之下。
// -----------------------------【创建NPC纹理】---------------------------------------D3DXCreateTextureFromFile(g_pd3dDevice, L"GameMedia\\girl.png", &g_pNPCTexture );
Ⅳ、五步曲之四:构造并使用公告板矩阵
因为公告板技术是通过使用世界矩阵以及观察点来排序公告板平面的,并且在程序中我们已经知道了观察的角度或者可以获得取景变换矩阵(我们后面的示例程序就是获取了取景变换矩阵),而不用去改变公告板矩形的坐标位置。
这一步里面需要注意的是g_pCamera->CalculateViewMatrix(&matView); 这句里面计算当前的取景变换并存到matView中,用到了我们之前文章里讲过的那个摄像机类CameraClass。如果你的程序里没有使用浅墨写的这个类的话,那么就根据自己的实际情况机智地去获取好了。
//-----------------------------【利用公告板技术准备绘制NPC】--------------------------//取得当前的取景变换矩阵D3DXMATRIXmatView; g_pCamera->CalculateViewMatrix(&matView);
同样需要注意的是,由于公告板始终是与摄像机的观察方向垂直的,因此当摄像机的观察方向改变时,同样需要将公告板绕Y轴进行旋转,因此,我们可以把取景变换矩阵的11、13和31、33向量分别设置到公告板的变换矩阵中,从而得到公告板在观察坐标系中的变换矩阵。
一般的代码可以是这样的:
//根据取景变换矩阵来计算并构造公告板矩阵D3DXMATRIX matBillboard;D3DXMatrixIdentity(&matBillboard);matBillboard._11 = matView._11;matBillboard._13 = matView._13;matBillboard._31 = matView._31;matBillboard._33 = matView._33;
而为了得到公告板在世界坐标系中的世界变换矩阵的话,还需要将公告板的变换矩阵做逆运算。
D3DXMatrixInverse(&matBillboard, NULL, &matBillboard);
最终,就是创建一个矩阵,右乘之前的公告板矩阵,并设置成世界矩阵
//创建一个矩阵,右乘之前的公告板矩阵,并设置成世界矩阵D3DXMATRIX matNPC;D3DXMatrixIdentity(&matNPC);matNPC = matBillboard * matNPC; g_pd3dDevice->SetTransform(D3DTS_WORLD, &matNPC );
其实,创建公告板矩阵的另一种方法是通过当前的观察方向完成。此时,将观察点减去视点,得到观察方向的向量,然后通过该向量让公告板矩阵绕Y轴旋转一定的角度。比如说这样:
// 通过当前观察方向来构造公告板矩阵D3DXMATRIX matBillboard;D3DXVECTOR3 vDir = vAt - vEye;if( vDir.x > 0.0f )D3DXMatrixRotationY( &matBillboard, -atanf(vDir.z/vDir.x)+D3DX_PI/2);elseD3DXMatrixRotationY( &matBillboard, -atanf(vDir.z/vDir.x)-D3DX_PI/2);
这步这样讲有些杂乱了,我们用代码把他们串起来:
//-----------------------------【利用公告板技术准备绘制NPC】--------------------------//取得当前的取景变换矩阵D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); //根据取景变换矩阵来计算并构造公告板矩阵D3DXMATRIX matBillboard;D3DXMatrixIdentity(&matBillboard);matBillboard._11 = matView._11;matBillboard._13 = matView._13;matBillboard._31 = matView._31;matBillboard._33 = matView._33;D3DXMatrixInverse(&matBillboard, NULL, &matBillboard);//创建一个矩阵,右乘之前的公告板矩阵,并设置成世界矩阵D3DXMATRIX matNPC;D3DXMatrixIdentity(&matNPC);matNPC = matBillboard * matNPC;g_pd3dDevice->SetTransform( D3DTS_WORLD, &matNPC );
Ⅴ、五步曲之五:正式绘制
正式绘制的代码应该就不用多做介绍了,之前那么多次已经耳濡目染了吧://-----------------------------【正式绘制NPC人物】-----------------------------g_pd3dDevice->SetTexture(0, g_pNPCTexture );g_pd3dDevice->SetStreamSource(0, g_pNPCVBuffer , 0, sizeof(CUSTOMVERTEX) );g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX );g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2 );
其实在正式绘制之前,往往还要做纹理映射状态或者Alpha混合状态的一些设置,疯狂调用SetTextureStageState和SetSamplerState即可:
//设置纹理状态g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );//Alpha混合设置, 设置混合系数g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true );g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
嗯,概念似乎就是这么多了,下面依旧是老规矩,用一个示例程序来融会贯通这篇文章里面的主角“公告板技术”。
五、详细注释的示例程序源代码
这次的示例程序依旧是在之前搭的那个框架之中,新增和修改了一些代码,演化而来的。
好吧,上代码,核心部分main.cpp的详细注释代码:
//-----------------------------------【程序说明】---------------------------------------------- // 【Visual C++】游戏开发系列配套源码五十四 浅墨DirectX教程二十一 视角上的诡计:公告板(Billboard)技术 // VS2010版 // 2013年10月 Create by 浅墨 // 背景音乐素材出处:火影忍者疾风传 //------------------------------------------------------------------------------------------------//-----------------------------------【宏定义部分】-------------------------------------------- // 描述:定义一些辅助宏 //------------------------------------------------------------------------------------------------ #define WINDOW_WIDTH 932 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 #define WINDOW_HEIGHT 700 //为窗口高度定义的宏,以方便在此处修改窗口高度 #define WINDOW_TITLE _T("【致我们永不熄灭的游戏开发梦想】浅墨DirectX教程二十一 视角上的诡计:公告板(Billboard)技术 博文配套示例程序 by浅墨") //为窗口标题定义的宏//-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //------------------------------------------------------------------------------------------------ #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h> #include "DirectInputClass.h" #include "CameraClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass.h"//-----------------------------------【库文件包含部分】--------------------------------------- // 描述:包含程序所依赖的库文件 //------------------------------------------------------------------------------------------------ #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的库文件,注意这里有8 #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib") // 定义FVF顶点结构 struct CUSTOMVERTEX {FLOAT _x, _y, _z;FLOAT _u, _v ;CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v): _x(x), _y(y), _z(z), _u(u), _v(v) {} }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)//-----------------------------------【全局变量声明部分】------------------------------------- // 描述:全局变量的声明 //------------------------------------------------------------------------------------------------ LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象 LPD3DXFONT g_pTextFPS =NULL; //字体COM接口 LPD3DXFONT g_pTextAdaperName = NULL; // 显卡信息的2D文本 LPD3DXFONT g_pTextHelper = NULL; // 帮助信息的2D文本 LPD3DXFONT g_pTextInfor= NULL; // 绘制信息的2D文本 float g_FPS= 0.0f; //一个浮点型的变量,代表帧速率 wchar_t g_strFPS[50] ={0}; //包含帧速率的字符数组 wchar_t g_strAdapterName[60] ={0}; //包含显卡名称的字符数组 D3DXMATRIX g_matWorld; //世界矩阵 DInputClass* g_pDInput = NULL; //DInputClass类的指针实例 CameraClass* g_pCamera = NULL; //摄像机类的指针实例 SkyBoxClass* g_pSkyBox=NULL; //天空盒类的指针实例 SnowParticleClass* g_pSnowParticles = NULL; //雪花粒子系统的指针实例LPDIRECT3DVERTEXBUFFER9 g_pFloorVBuffer = NULL; //地板顶点缓存对象 LPDIRECT3DTEXTURE9 g_pFloorTexture = NULL; //地板纹理对象 LPDIRECT3DVERTEXBUFFER9 g_pNPCVBuffer = NULL; // NPC顶点缓存对象 LPDIRECT3DTEXTURE9 g_pNPCTexture = NULL; // NPC纹理对象 D3DXMATRIX g_matView; //-----------------------------------【全局函数声明部分】------------------------------------- // 描述:全局函数声明,防止“未声明的标识”系列错误 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULT Objects_Init(); void Direct3D_Render( HWND hwnd,FLOAT fTimeDelta); void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta); void Direct3D_CleanUp( ); float Get_FPS(); void HelpText_Render(HWND hwnd);//-----------------------------------【WinMain( )函数】-------------------------------------- // 描述: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("GameMedia\\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, //喜闻乐见的创建窗口函数CreateWindowWS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );//Direct3D资源的初始化,调用失败用messagebox予以显示if (!(S_OK==Direct3D_Init (hwnd,hInstance))){MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 }PlaySound(L"GameMedia\\青鸟-《火影忍者》.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循环播放背景音乐MoveWindow(hwnd,200,10,WINDOW_WIDTH,WINDOW_HEIGHT,true); //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,10)处ShowWindow( hwnd, nShowCmd ); //调用Win32函数ShowWindow来显示窗口UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样//进行DirectInput类的初始化g_pDInput = new DInputClass();g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);//消息循环过程MSG msg = { 0 }; //初始化msgwhile( msg.message != WM_QUIT ) //使用while循环{static FLOAT fLastTime = (float)::timeGetTime();static FLOAT fCurrTime = (float)::timeGetTime();static FLOAT fTimeDelta = 0.0f;fCurrTime = (float)::timeGetTime();fTimeDelta = (fCurrTime - fLastTime) / 1000.0f;fLastTime = fCurrTime;if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。{TranslateMessage( &msg ); //将虚拟键消息转换为字符消息DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。}else{Direct3D_Update(hwnd,fTimeDelta); //调用更新函数,进行画面的更新Direct3D_Render(hwnd,fTimeDelta); //调用渲染函数,进行画面的渲染 }}UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);return 0; }//-----------------------------------【WndProc( )函数】-------------------------------------- // 描述:窗口过程函数WndProc,对窗口消息进行处理 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口过程函数WndProc {switch( message ) //switch语句开始{case WM_PAINT: // 客户区重绘消息Direct3D_Render(hwnd,0.0f); //调用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; //正常退出 }//-----------------------------------【Direct3D_Init( )函数】---------------------------------- // 描述:Direct3D初始化函数,进行Direct3D的初始化 //------------------------------------------------------------------------------------------------ HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) {//--------------------------------------------------------------------------------------// 【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; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的elsevp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算//--------------------------------------------------------------------------------------// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体//--------------------------------------------------------------------------------------D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp));d3dpp.BackBufferWidth = WINDOW_WIDTH;d3dpp.BackBufferHeight = WINDOW_HEIGHT;d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;d3dpp.BackBufferCount = 2;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;//获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串wchar_t TempName[60]=L"当前显卡型号:"; //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中D3DADAPTER_IDENTIFIER9 Adapter; //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息pD3D->GetAdapterIdentifier(0,0,&Adapter);//调用GetAdapterIdentifier,获取显卡信息int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了wcscat_s(TempName,g_strAdapterName);//把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中wcscpy_s(g_strAdapterName,TempName);//把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~if(!(S_OK==Objects_Init())) return E_FAIL;SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉return S_OK; }//-----------------------------------【Object_Init( )函数】-------------------------------------- // 描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化 //-------------------------------------------------------------------------------------------------- HRESULT Objects_Init() {//-----------------------------【创建字体】---------------------------------------D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS);D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName); D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper); D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor); // -----------------------------【创建地面顶点缓存】--------------------------------g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pFloorVBuffer, NULL);CUSTOMVERTEX *pVertices = NULL;g_pFloorVBuffer->Lock(0, 0, (void**)&pVertices, 0);pVertices[0] = CUSTOMVERTEX(-5000.0f, 0.0f, -5000.0f, 0.0f, 10.0f);pVertices[1] = CUSTOMVERTEX(-5000.0f, 0.0f, 5000.0f, 0.0f, 0.0f);pVertices[2] = CUSTOMVERTEX( 5000.0f, 0.0f, -5000.0f, 10.0f, 10.0f); pVertices[3] = CUSTOMVERTEX( 5000.0f, 0.0f, 5000.0f, 10.0f, 0.0f);g_pFloorVBuffer->Unlock();// -----------------------------【创建NPC的顶点缓存】--------------------------------g_pd3dDevice->CreateVertexBuffer( 4 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pNPCVBuffer , NULL );pVertices = NULL;g_pNPCVBuffer ->Lock(0, 0, (void**)&pVertices, 0);pVertices[0] = CUSTOMVERTEX(-100.0f, 0.0f, 0.0f, 0.0f, 1.0f);pVertices[1] = CUSTOMVERTEX(-100.0f, 320.0f, 0.0f, 0.0f, 0.0f); pVertices[2] = CUSTOMVERTEX( 100.0f, 0.0f, 0.0f, 1.0f, 1.0f); pVertices[3] = CUSTOMVERTEX( 100.0f, 320.0f, 0.0f, 1.0f, 0.0f);g_pNPCVBuffer ->Unlock();// -----------------------------【创建地面纹理】---------------------------------------D3DXCreateTextureFromFile(g_pd3dDevice, L"GameMedia\\floor.jpg", &g_pFloorTexture);// -----------------------------【创建NPC纹理】---------------------------------------D3DXCreateTextureFromFile( g_pd3dDevice, L"GameMedia\\girl.png", &g_pNPCTexture );// -----------------------------【创建并初始化虚拟摄像机】---------------------------------------g_pCamera = new CameraClass(g_pd3dDevice);g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 500.0f, -800.0f)); //设置摄像机所在的位置g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 800.0f, 0.0f)); //设置目标观察点所在的位置g_pCamera->SetViewMatrix(); //设置取景变换矩阵D3DXMATRIX matProj;D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, 1.0f, 1.0f, 200000.0f);g_pCamera->SetProjMatrix(&matProj);//-----------------------------【创建并初始化天空对象】---------------------------------------g_pSkyBox = new SkyBoxClass( g_pd3dDevice );g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\TropicalSunnyDayFront2048.png",L"GameMedia\\TropicalSunnyDayBack2048.png",L"GameMedia\\TropicalSunnyDayRight2048.png",L"GameMedia\\TropicalSunnyDayLeft2048.png", L"GameMedia\\TropicalSunnyDayUp2048.png");//从文件加载前、后、左、右、顶面5个面的纹理图g_pSkyBox->InitSkyBox(50000); //设置天空盒的边长//-----------------------------【创建并初始化雪花粒子系统】--------------------------------------- g_pSnowParticles = new SnowParticleClass(g_pd3dDevice); g_pSnowParticles->InitSnowParticle(); // 关闭光照// g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false );return S_OK; }//-----------------------------------【Direct3D_Update( )函数】-------------------------------- // 描述:不是即时渲染代码但是需要即时调用的,如按键后的坐标的更改,都放在这里 //-------------------------------------------------------------------------------------------------- void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta) {//使用DirectInput类读取数据 g_pDInput->GetInput(); // 沿摄像机各分量移动视角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_R)) g_pCamera->MoveAlongUpVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_F)) g_pCamera->MoveAlongUpVec(-1.0f); //沿摄像机各分量旋转视角 if (g_pDInput->IsKeyDown(DIK_LEFT)) g_pCamera->RotationUpVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_RIGHT)) g_pCamera->RotationUpVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_UP)) g_pCamera->RotationRightVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_DOWN)) g_pCamera->RotationRightVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_Q)) g_pCamera->RotationLookVec(0.001f); if (g_pDInput->IsKeyDown(DIK_E)) g_pCamera->RotationLookVec( -0.001f); //鼠标控制右向量和上向量的旋转 g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.0003f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.0003f); //鼠标滚轮控制观察点收缩操作 static FLOAT fPosZ=0.0f; fPosZ += g_pDInput->MouseDZ()*0.03f; //计算并设置取景变换矩阵 D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //把正确的世界变换矩阵存到g_matWorld中 D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ); //以下这段代码用于限制鼠标光标移动区域POINT lt,rb;RECT rect;GetClientRect(hwnd,&rect); //取得窗口内部矩形//将矩形左上点坐标存入lt中lt.x = rect.left;lt.y = rect.top;//将矩形右下坐标存入rb中rb.x = rect.right;rb.y = rect.bottom;//将lt和rb的窗口坐标转换为屏幕坐标ClientToScreen(hwnd,<);ClientToScreen(hwnd,&rb);//以屏幕坐标重新设定矩形区域rect.left = lt.x;rect.top = lt.y;rect.right = rb.x;rect.bottom = rb.y;//限制鼠标光标移动区域ClipCursor(&rect);ShowCursor(false); //隐藏鼠标光标}//-----------------------------------【Direct3D_Render( )函数】------------------------------- // 描述:使用Direct3D进行渲染 //---------------------------------------------------------------------------------------------- void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta) {//--------------------------------------------------------------------------------------// 【Direct3D渲染五步曲之一】:清屏操作//--------------------------------------------------------------------------------------g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 155, 255), 1.0f, 0);//--------------------------------------------------------------------------------------// 【Direct3D渲染五步曲之二】:开始绘制//--------------------------------------------------------------------------------------g_pd3dDevice->BeginScene(); // 开始绘制//--------------------------------------------------------------------------------------// 【Direct3D渲染五步曲之三】:正式绘制//--------------------------------------------------------------------------------------//设置纹理状态g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );g_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );//Alpha混合设置, 设置混合系数g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true );g_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );g_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );//-----------------------------【绘制地板】-----------------------------D3DXMATRIX matFloor;D3DXMatrixTranslation(&matFloor, 0.0f, 0.0f, 0.0f);g_pd3dDevice->SetTransform(D3DTS_WORLD, &matFloor);g_pd3dDevice->SetStreamSource(0, g_pFloorVBuffer, 0, sizeof(CUSTOMVERTEX));g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);g_pd3dDevice->SetTexture(0, g_pFloorTexture);g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);//-----------------------------【利用公告板技术准备绘制NPC】--------------------------//取得当前的取景变换矩阵D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); //根据取景变换矩阵来计算并构造公告板矩阵D3DXMATRIX matBillboard;D3DXMatrixIdentity(&matBillboard);matBillboard._11 = matView._11;matBillboard._13 = matView._13;matBillboard._31 = matView._31;matBillboard._33 = matView._33;D3DXMatrixInverse(&matBillboard, NULL, &matBillboard);//创建一个矩阵,右乘之前的公告板矩阵,并设置成世界矩阵D3DXMATRIX matNPC;D3DXMatrixIdentity(&matNPC);matNPC = matBillboard * matNPC;g_pd3dDevice->SetTransform( D3DTS_WORLD, &matNPC );//-----------------------------【正式绘制NPC人物】-----------------------------g_pd3dDevice->SetTexture( 0, g_pNPCTexture );g_pd3dDevice->SetStreamSource( 0, g_pNPCVBuffer , 0, sizeof(CUSTOMVERTEX) );g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );//-----------------------------【绘制天空】-----------------------------D3DXMATRIX matSky,matTransSky,matRotSky;D3DXMatrixTranslation(&matTransSky,0.0f,-15000.0f,0.0f);D3DXMatrixRotationY(&matRotSky, -0.00002f*timeGetTime()); //旋转天空网格, 简单模拟云彩运动效果matSky=matTransSky*matRotSky;g_pSkyBox->RenderSkyBox(&matSky, false);//-----------------------------【绘制雪花粒子系统】------------------------g_pSnowParticles->UpdateSnowParticle(fTimeDelta);g_pSnowParticles->RenderSnowParticle();//-----------------------------【绘制文字信息】-----------------------------HelpText_Render(hwnd);//--------------------------------------------------------------------------------------// 【Direct3D渲染五步曲之四】:结束绘制//--------------------------------------------------------------------------------------g_pd3dDevice->EndScene(); // 结束绘制//--------------------------------------------------------------------------------------// 【Direct3D渲染五步曲之五】:显示翻转//--------------------------------------------------------------------------------------g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示}//-----------------------------------【HelpText_Render( )函数】------------------------------- // 描述:封装了帮助信息的函数 //-------------------------------------------------------------------------------------------------- void HelpText_Render(HWND hwnd) {//定义一个矩形,用于获取主窗口矩形RECT formatRect;GetClientRect(hwnd, &formatRect);//在窗口右上角处,显示每秒帧数formatRect.top = 5;int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255));//显示显卡类型名g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f));// 输出帮助信息formatRect.left = 0,formatRect.top = 380;g_pTextInfor->DrawText(NULL, L"控制说明:", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255));formatRect.top += 35;g_pTextHelper->DrawText(NULL, L" W:向前飞翔 S:向后飞翔 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" A:向左飞翔 D:向右飞翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" R:垂直向上飞翔 F:垂直向下飞翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" Q:向左倾斜 E:向右倾斜", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" 上、下、左、右方向键、鼠标移动:视角变化 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" 鼠标滚轮:人物模型Y轴方向移动", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));formatRect.top += 25;g_pTextHelper->DrawText(NULL, L" ESC键 : 退出程序", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); }//-----------------------------------【Get_FPS( )函数】------------------------------------------ // 描述:用于计算每秒帧速率的一个函数 //-------------------------------------------------------------------------------------------------- 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()函数,帧数自增1currentTime = 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; }//-----------------------------------【Direct3D_CleanUp( )函数】-------------------------------- // 描述:对Direct3D的资源进行清理,释放COM接口对象 //--------------------------------------------------------------------------------------------------- void Direct3D_CleanUp() {//释放COM接口对象SAFE_DELETE(g_pDInput);SAFE_RELEASE(g_pd3dDevice);SAFE_RELEASE(g_pTextAdaperName)SAFE_RELEASE(g_pTextHelper)SAFE_RELEASE(g_pTextInfor)SAFE_RELEASE(g_pTextFPS)SAFE_RELEASE(g_pd3dDevice) }
然后是几张程序截图:
我们可以发现,无论以水平方向上哪个角度去观察这张图片,都俨然显现出3D模型的真实立体效果,这就是公告板技术的功劳,在这么真的3D效果面前,可不要又一次被一点雕虫小技蒙蔽了双眼哦。
文章最后,依旧是放出本篇文章配套源代码的下载:
本篇文章配套源代码请点击这里下载:
【浅墨DirectX提高班】配套源代码之二十一下载 (CSDN下载频道)
【浅墨DirectX提高班】配套源代码之二十一下载 (新浪微盘)
以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。
文章最后,依然是【每文一语】栏目,今天的句子是:
心是一个人的翅膀,心有多大,世界就有多大。很多时候限制我们的,不是周遭的环境,也不是他人的言行,而是我们自己。看不开、忘不了、放不下,把自己囚禁在灰暗的记忆里;不敢想、不自信、不行动,把自己局限在固定的空间里……如果不能打破心的禁锢,即使给你整个天空,你也找不到自由的感觉。加油:)
下周一,让我们离游戏开发的梦想更近一步。
下周一,游戏开发笔记,我们,不见不散。
【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计:公告板(Billboard)技术相关推荐
- 【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计 公告板 Billboard 技术
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...
- 【Visual C++】游戏开发五十四 浅墨DirectX教程二十一 视觉的诡计:公告板(Billboard)技术...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 作者:毛星云(浅墨) 微博:@浅墨_毛星云邮箱: happylifemxy@163.com I'm back~,这段时间大家久等了~ ...
- 【Visual C++】游戏开发五十六 浅墨DirectX教程二十三 打造游戏GUI界面(一)
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/16384009 作者:毛星 ...
- 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨 ...
- 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨) ...
- 【Visual C++】游戏开发五十七 浅墨DirectX教程二十四 打造游戏GUI界面(二)
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/16922703 作者:毛星 ...
- 动画骨骼【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)...
间时紧张,先记一笔,后续优化与完善. 本系列文章由zhmxy555(毛星云)编写,载转请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/detail ...
- 【Visual C++】游戏开发笔记三十四 浅墨DirectX提高班之三 起承转合的艺术:Direct3D渲染五步曲...
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8223965 作者:毛星云(浅墨 ...
- 【Visual C++】游戏开发笔记三十四 浅墨DirectX提高班之三 起承转合的艺术:Direct3D渲染五步曲
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8223965 作者:毛星云(浅墨 ...
- 【Visual C++】游戏开发四十八 浅墨DirectX教程十六 三维地形系统的实现
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 本系列文 ...
最新文章
- java activiti5_工作流Activiti5.13学习笔记(一)
- OVS datapath结构图(四十六)
- Solr安装(单机版)
- Android ListView侧滑item,仿QQ删除效果
- mysql多个on_在多个查询中插入多行的MySQL ON DUPLICATE KEY UPDATE
- vs2015 去除 git 源代码 绑定,改成向tfs添加源码管理
- sketch钢笔工具_设计工具(Sketch,Adobe XD,Figma和InVision Studio)中奇怪的一项功能
- MySQL-InnoDB索引实现
- 完成计算机组装工艺卡组装准备,计算机组装与维护(刘猛)教程方案.doc
- 【训练题】分队 P1672
- mybatis传参总结
- 三枚Flash 0day漏洞曝光,Flash再次紧急更新
- 上面两点下面一个三角形_K线图中走出三角形收敛形态必有大波动?一招判断后期走势方向...
- 短视频课堂丨传统工厂企业短视频打造之“三好人设”
- java.io和java.nio性能简单对比
- 系统分析师(一)软考简介
- 【经典控制理论】| 自动控制原理知识点概要(上)
- webview和android做壳,Android Studio 制作一个 WebView 外壳
- 俄罗斯“扎克伯格”:创建区块链版“微信” 27岁身价已达2.5亿美元
- 计算机备课组学期工作计划,信息技术备课组工作计划