demo 运行注意事项:
源码为.cpp 跟fx文件夹一起导入。
Effects.h 和两个Effects.lib(环境变量)导入:
https://blog.csdn.net/lishuzhai/article/details/54135799
书籍推荐:《大话设计模式》。
阅读本文时需要对DX_INPUT 、Free—Camera 有一定理解。
强烈建议博客:
https://blog.csdn.net/poem_qianmo/article/details/8547531 dx_input
https://blog.csdn.net/poem_qianmo/article/details/8657656 free_camera
老鸟:小白,你发现没有,我们虽然只是实现一个小小的三角形,代码却依然达到300行左右。
小白:是呀,每次都要写那么多烦死了,幸好我每次都是复制粘贴。
老鸟:嗯,看来你已经学到程序员"最优秀"的操作了,只不过小白,你的C++学的怎么样?
小白:一般般,怎么了,你要考我吗?
老鸟:考谈不上,只是想问你一个最基础的问题,C++除了面对对象、抽象、多态 等等外,
还有一个什么重要属性?
小白:哈哈,这你还来问我,c++最重要的基础不就是类吗!
老鸟:哦,原来你知道呀,我还以为你白学C++了。
小白:哇,你挖苦我,等等,你是不是要我把DX用类封装一下。
老鸟:小白还是很聪明的吗。
小白:可是我在学校写代码时,我知道类这个玩意,但也局限于简单封装过几个小函数,
这个庞然大物我有点应付不来,大神,你帮帮我吧。
老鸟:一看你上课就不认真,作业恐怕也是copy的,将长代码提取属性封装成类是每个
c++程序员必需的技能。
老鸟:就拿上次那个三角形demo来说吧:
//初始化
BOOL InitWin32();
//初始化D3D
BOOL InitD3D();
//应用初始化
BOOL InitApp();
//场景更新
void Update();
//场景绘制
void Render();
//主循环
int Run();;
//回收资源
void Release();
老鸟:其可分为三部分:win32、D3D、app。
老鸟:Win32管理窗口,D3D管理DX11的初始化、渲染、更新,App管理shader文件,
主循环为消息处理机制。(资源回收由谁创建谁回收)
老鸟:故上面很简单的就分成了三个类:WindowClass、D3DClass、ShaderClass。
老鸟:这么简单的分类当然不是我们今天研究的对象,如果你去找过DX11龙书的源码,
一般会有这样的资源视图:
老鸟:今天我们就其中几个现在需要的讲解:
(1) 摄像机class
(2)d3dclass (DX11class)
(3)图形class
(4)着色器class
(5)DX的底层按键输入class
(6)系统class
图形class =摄像机class + DX11class + 着色器class
系统class =图形class + DX的底层按键输入class
老鸟:将代码分为类,一般采用的方法为从下至上,即从底层到高层。这与代码
书写不一样,一个完整的design的实现,往往是从抽象到具体;当然大项目
会有架构师给你分配工作,整合你们代码时就是从细节到整体。
老鸟:代码书写顺序因人而异,但类的整合往往还是这样为佳。把底层的基础逻辑
理清,一部分一部分解决,当然如果你对你的项目十分了解,从高级抽象到底层
实现了然于心,从上至下也未尝不可,我一般推荐这种方法留着去检验你类封装
的质量。
老鸟:就本例而言:我们首当其冲的就是整合Windows窗口创建的代码,把其中大部分固定
的代码提取出来。
老鸟:Init_Window 代码需要的全局变量(全部应封装成类成员变量)
#define Screen_Width 800
#define Screen_Height 600
HWND g_hWnd;
老鸟:下面是 Init_Window具体代码(其中有些临时变量也应封装)
BOOL Init_Window()
{
TCHAR* Title_Name = L"DX11 类简化";
TCHAR* Class_Name = L"Try";
WNDCLASS Wnd = { 0 };
Wnd.cbClsExtra = NULL;
Wnd.cbWndExtra = NULL;
Wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
Wnd.hCursor = NULL;
Wnd.hIcon =(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
Wnd.hInstance = GetModuleHandle(NULL);
Wnd.lpfnWndProc = WndProc;
Wnd.lpszClassName = Class_Name;
Wnd.lpszMenuName = NULL;
Wnd.style = CS_HREDRAW | CS_VREDRAW;
if (!RegisterClass(&Wnd))
{
return FALSE;
}
int Window_Height = Screen_Height;
int Window_Width = Screen_Width;
HWND hWnd = CreateWindow(Class_Name,Title_Name,WS_OVERLAPPEDWINDOW,0,0,
Window_Width,Window_Height,NULL,NULL,Wnd.hInstance,NULL);
g_hWnd = hWnd;
ShowWindow(hWnd, SW_SHOWNORMAL);
SetForegroundWindow(hWnd);
SetFocus(hWnd);
//UpdateWindow(hWnd);
return TRUE;
}
老鸟:处理完Init_Window()后,消息处理泵和消息处理函数也就是顺理成章的提取,
这里有一点要提及,Init_Window()与消息处理机制应属于同层级,此次笔者将
Init_Window()整合为一函数,并位于Init_System()下,但实际上只是单纯函数整合。
实际在龙书中Init_Window()名为:SystemClass::InitializeWindows。
老鸟:故读者应该理解为我将Init_Window和消息处理等整合为System_Class,而不是
将Window窗口单独成类,因为其掌握消息处理,故与其它类耦合度较高,提取出来
并没有很大收益,反倒是就以它作为总类(最高抽象)会比较符合情理。
小白:完蛋了,我们刚刚还说从底层到高层了,这一搞就是最高层了。
老鸟:慌什么,这对我们类的封装影响不大,我们还是能封装它呀,只不过改了个名字
叫System而已,这也告诉我们类的区分不是单纯看其是否能整合代码的,而是看
其是否与整个代码的耦合程度,如果影响全部函数,而且其并不属于重用率高的基类
函数,虽然其看上去属于底层框架,我们还是考虑将其提升抽象等级。当然将其提取出来
也很简单:
//System.cpp
BOOL System::Init_Window(WNDPROC WndProc){}
//_____________________________________________________//
//main.cpp
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
System* pSys=new System;
if (!pSys->Init_Window(WndProc))
{
return -1;
}
MSG msg = { 0 };
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
delete pSys;
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{}
老鸟:这里我只列出需要关注的wndproc,因为其属于系统处理的回调函数,我们无法
取得源码,也没有提供接口,否则我们应该将wndproc也从窗口类提取出来,这样
windowclass就离一致抽象更进一步。
老鸟:因为wndproc的关系,我们无法将WindomClass置于其它高级抽象下,比如GraphicsClass
下,因为Init_Window()需要参数wndproc,这不属于图形抽象级,硬要实现就要把
Init_Graphics()改成 Init_Graphics(WNDPROC WndProc),这是不提倡的,不能因为底层
实现需要某个参数,其高层函数也要传入相应参数;wndproc是属于system抽象级,我们给
windowclass传参已经导致底层实现依赖高层,既然有这么多的不便,那么就把窗口
初始化放在System抽象级下,虽然这会违背我们对system的期望:只控制流程,
具体的实现全部由底层实现。
老鸟:当然,高层传递参数至下一层级是理所当然的,因为高层就是靠参数去改变底层实现细节的,
但是我们要注意的一点是,如果没有高层传参,其底层也应该能实现其基本功能。
小白:那我在windowClass里实现winproc,再通过参数去选择改变就行了。
老鸟:但那样你的windowClass的抽象级就不一致了,再说,那样其与高层的system的功能就有
一部分 重复了,我们封装类时如果父类与子类有重复的函数,我们应该考虑将子类函数
提至基类,并考虑是否将父类函数变为虚函数,本例主要是因为wndproc与窗口建立
抽象级不一致,一个属于系统级别,一个属于窗口级别,因为我们无法分离两者,
故我们将窗口级别提升至系统级别来实现。
小白:啊啊,原来这里面还有这么多道道,我以前就是单纯把函数跟相关的数据整合为一个类,
需要什么我就增加函数参数,现在看来真的是糟糕的做法。
老鸟:现在你知道也不晚,好啦,我们把“开头难”理清了,接下来讲其它类就简单多了。
老鸟:d3dClass就简单多了,其所需的参数完全自给自足。
BOOL Init_D3D(HWND hwnd);
void Render_D3D(float red, float green, float blue, float alpha);
void Update_D3D();
void Clean_D3D();
老鸟:carmer和shader
BOOL Init_Camera();
void Update_Camera();
void Clean_Camera();
BOOL Init_Shader();
void Clean_Shader();
BOOL Init_Graphics();
BOOL Frame_Graphics();
void Clean_Graphics();
BOOL Init_Graphics()
{
if (!Init_D3D(g_hWnd))
{
return FALSE;
}
if (!Init_Shader())
{
return FALSE;
}
if (!Init_Camera())
{
return FALSE;
}
return TRUE;
}
BOOL Frame_Graphics()
{
float color[4];
color[0] = 0.2f;
color[1] = 0.4f;
color[2] = 0.6f;
color[3] = 1.0f;
Render_D3D(color[0], color[1], color[2], color[3]);
Update_D3D();
return TRUE;
}
void Clean_Graphics()
{
Clean_D3D();
Clean_Shader();
Clean_Camera();
}
老鸟:dx_input
BOOL DX_Input_Init();
BOOL DX_Input_Frame();
void DX_Input_Run();
void DX_Input_Clean();
老鸟:system
BOOL Init_System();
bool Frame_System();
void Run_System();
void Clean_System();
BOOL Init_System()
{
if (!Init_Window(WndProc))
{
return FALSE;
}
if (!Init_Graphics())
{
return FALSE;
}
if (!DX_Input_Init())
{
return FALSE;
}
return TRUE;
}
bool Frame_System()
{
if (!Frame_Graphics())
return false;
if (!DX_Input_Frame())
return false;
return true;
}
void Run_System()
{
MSG msg = { 0 };
bool result;
while (msg.message!=WM_QUIT)
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
result = Frame_System();
if (!result)
{
MessageBox(g_hWnd, L"Frame Processing Failed", L"Error", MB_OK);
}
}
}
}
void ShutDown_System()
{
Clean_Graphics();
DX_Input_Clean();
}
老鸟:主函数
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
if (!Init_System())
{
return -1;
}
Run_System();
return 0;
}
老鸟:这是留给你的作业,把它封装成类;