程序设置
在包含目录中添加下载的DirectX SDK文件中的include
在库目录中添加下载的DirectX SDK文件中的lib\x64
程序解释
包含头文件和全局变量定义
#include <d3d9.h> #include <d3dx9.h> #pragma comment (lib, "d3d9.lib") #pragma comment (lib, "d3dx9.lib") LPDIRECT3D9 d3d; // Direct3D接口指针 LPDIRECT3DDEVICE9 d3ddev; // Direct3D设备类指针
头文件
d3d9.h: 包含Direct3D 9的核心函数和类定义,这是使用DirectX进行3D图形编程的基础。
d3dx9.h: 包含一些辅助功能,比如矩阵运算、着色器编译等,使得3D编程更加简单和高效。
库链接
#pragma comment: 这是一个编译器指令,用于自动链接程序需要的库。这里链接的是Direct3D 9 (d3d9.lib) 和辅助库 (d3dx9.lib)。
C 语言为什么只需要 include<stdio.h> 就能使用里面声明的函数?
而windows.h头文件所链接的库文件为编译器默认链接的,因此不用手动添加。
全局变量
LPDIRECT3D9 d3d: 这是Direct3D 9接口的指针,用于创建和管理Direct3D设备。
LPDIRECT3DDEVICE9 d3ddev: 这是一个Direct3D设备指针,所有的渲染操作都是通过这个设备进行的。
此处仅声明了指针对象,但是还没有实例,是空指针。
初始化DirectX 9
void initD3D(HWND hWnd) { //将安装的d3d接口地址赋值给d3d指针 d3d = Direct3DCreate9(D3D_SDK_VERSION); //此处创建d3dpp的结构体变量,用于设置Direct3D的设备参数 D3DPRESENT_PARAMETERS d3dpp; //清空d3dpp地址的缓存区,确保direct3D设备的所有设置都是从已知状态开始的。 ZeroMemory(&d3dpp, sizeof(d3dpp)); //显示窗口为窗口模式。 d3dpp.Windowed = TRUE; //选择翻转机制为翻转后清除后台缓存区 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; //窗口模式下可以为NULL,全屏模式下是CreateDevice的hFocusWindow d3dpp.hDeviceWindow = hWnd; //通过CreateDevice让d3ddev从接口指针转化为接口对象 //参数分别为默认显卡型号,硬件渲染模式,创建的设备所绑定的窗口, //顶点软处理模式,输入参数,输出参数 d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); }
初始化函数
Direct3DCreate9: 通过指定Direct3D版本号(D3D_SDK_VERSION),创建Direct3D接口的实例。
D3DPRESENT_PARAMETERS: 这是一个结构体,用来设置Direct3D设备的一些参数,比如是否窗口模式运行、交换链的处理方式等。
ZeroMemory: 将结构体的内存清零,确保所有设置都是从已知状态开始的。
CreateDevice: 创建一个Direct3D设备实例,这是所有图形渲染操作的核心。此函数需要多个参数,包括设备类型、处理方式等。
设置图形和渲染
//创建一个名为CUSTOMVERTEX(自定义顶点)的结构体 struct CUSTOMVERTEX { //顶点坐标 FLOAT X, Y, Z; }; //顶点格式,FVF灵活顶点模式, XYZ包含位置信息,NORMAL包含法向量信息 #define CUSTOMFVF (D3DFVF_XYZ) //初始化与图形相关的组件、为后面的绘图做准备 void initGraphics() { //定义了一个名为vertices的CUSTOMVERTEX类型数组,存放了三个顶点 CUSTOMVERTEX vertices[] = { { -1.0f, 1.0f, 0.0f }, { 1.0f, -1.0f, 0.0f }, { -1.0f, -1.0f, 0.0f }, }; //顶点缓存区指针,让显卡里的显存能够存放图形绘制的最基本数据 LPDIRECT3DVERTEXBUFFER9 v_buffer; //创建顶点缓存区,参数分别为三倍结构体大小,0表示缓存区将用于渲染而非动态更新, //顶点格式管理池(意味着DirectX负责管理这块内存),顶点缓冲区的内存类型,输出参数,NULL d3ddev->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0,CUSTOMFVF,D3DPOOL_MANAGED,&v_buffer,NULL); //无数据类型指针pVoid,指向顶点缓存区首地址 VOID* pVoid; //锁定顶点缓存区以便安全写入数据,参数为锁定起始位置,锁定区域大小,缓存区首地址,锁定类型 //其中0,0代表全部锁定,转化成void型 v_buffer->Lock(0, 0, (void**)&pVoid, 0); //内存对拷函数,参数为拷贝目标,数据源,拷贝数据大小 memcpy(pVoid, vertices, sizeof(vertices)); //解锁顶点缓存区,使其可以被GPU访问以进行渲染 v_buffer->Unlock(); }
顶点结构和顶点缓冲区
CUSTOMVERTEX: 定义了一个简单的顶点数据结构,包含三个浮点数代表顶点的X, Y, Z坐标。
CUSTOMFVF: 这是一个”Flexible Vertex Format”标记,说明顶点数据结构中包含的数据类型(这里是X, Y, Z坐标)。
CreateVertexBuffer: 创建一个顶点缓冲区,用来存储顶点数据。这里使用的是管理池(D3DPOOL_MANAGED),这意味着DirectX负责管理这块内存。
Lock 和 Unlock: 这两个函数用来锁定顶点缓冲区以便安全写入数据,然后解锁。
渲染函数
//定义渲染函数 void render_frame(void) { //清除窗口背景函数,参数分别为清除的目标数量,清除的矩形区域, //清除的缓存区类型,清除后渲染目标的颜色清除深度缓存区的值,清除模版缓存区的值 d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); d3ddev->BeginScene();//开始渲染 d3ddev->SetFVF(CUSTOMFVF);//设置当前顶点格式 //设置数据源,参数为数据流索引,数据源,开始读取位置,每个顶点大小 d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX)); //绘制图元,参数为图元类型,初始顶点编号,绘制图形数量。此处为三角面片。 d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); //结束渲染。 d3ddev->EndScene(); //将后台缓存区的内容展示到前台,即显示到用户屏幕上。 d3ddev->Present(NULL, NULL, NULL, NULL); }
渲染操作
Clear: 清除屏幕和深度缓冲区,这里指定了清除颜色。
BeginScene 和 EndScene: 开始和结束一次渲染操作。所有的绘图命令都需要在这两个调用之间执行。
SetFVF: 设置当前顶点格式,告诉Direct3D如何解释顶点数据。
SetStreamSource: 绑定顶点缓冲区到输入装配阶段。
DrawPrimitive: 执行渲染,根据指定的顶点缓冲区绘制图形(这里是三角形列表)。
(D3DPT_TRIANGLELIST,0,1):从第0号顶点开始画1个三角形。
Present: 将后台缓冲区的内容展示到前台,即显示到用户屏幕上。
主函数和消息循环
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // (创建窗口代码略) initD3D(hWnd); initGraphics(); MSG msg; while(TRUE) { //PeekMessage接收到消息的返回值为1,未接收到消息的返回值为0, //意味着它接收到WM_QUIT消息的返回值为1,和GetMessage不同 if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } if(msg.message == WM_QUIT) break; render_frame(); } // (清理资源代码略) return msg.wParam; }
WinMain: 程序入口点,负责初始化Direct3D,设置图形,然后进入一个消息处理循环。
PeekMessage 和 DispatchMessage: 这些函数用于处理Windows消息,如鼠标点击、键盘事件或窗口关闭事件。
PeekMessage不会阻塞程序运行,而GetMessage在没有消息时会阻塞程序运行直到有消息为止。
render_frame: 在消息循环中调用,不断重绘屏幕。
这些解释应该帮助学生更好地理解如何使用DirectX 9来创建基本的3D图形程序。每个函数和结构的用途都被详细说明,确保学生能够理解它们在整个程序中的作用
重点与难点
重点
初始化Direct3D环境
(1)Direct3D接口和设备的创建:使用 Direct3DCreate9 和 CreateDevice 初始化Direct3D是所有DirectX程序的起点。这涉及到设置多个设备参数,如是否窗口化、交换链处理方式等。理解这些参数的意义对于配置Direct3D环境至关重要。
顶点缓冲区的使用
(2)定义顶点格式和顶点缓冲区:使用自定义顶点结构 (CUSTOMVERTEX) 和灵活顶点格式标记 (CUSTOMFVF) 管理顶点数据。这要求对DirectX的顶点处理方式有清晰的理解,以确保GPU正确解释顶点数据。
场景渲染流程与渲染流程管理
(3)从 BeginScene 到 EndScene,包括设置顶点源、应用顶点格式、实际的绘图命令到显示结果的过程,这些步骤共同定义了DirectX中的渲染流程。每个步骤都必须正确执行,以保证场景正确渲染到屏幕上。
难点
Direct3D设备设置
(1)初学者往往难以理解 D3DPRESENT_PARAMETERS 结构中的各种设置项及其对渲染输出的影响,如 SwapEffect、Windowed 等。这些参数的不当设置可能会导致渲染效果不符合预期或程序运行效率低下。
资源管理
(2)DirectX 9要求开发者手动管理大部分图形资源(如顶点缓冲区)。正确地创建、使用和销毁这些资源是保持程序稳定运行的关键。资源泄露是常见问题,特别是在复杂的应用中。
顶点和图形管线设置
(3)对于初学者来说,设置并理解顶点格式(FVF)可能比较复杂。此外,如何将顶点数据传递到GPU(通过顶点缓冲区),以及如何指示GPU如何解析这些数据(通过设置FVF和顶点缓冲区),都是概念上的挑战。
调试和错误处理
DirectX 9 API通常只返回一个成功或失败的状态码,不提供错误的详细信息。这使得调试成为一个挑战,特别是对于初学者来说,很难确定哪里出了问题及其原因。
解决难点的策略
详细的文档阅读:深入阅读和理解Microsoft的DirectX文档,特别是关于API调用和结构的详细说明。
逐步构建和测试:开发DirectX应用时,应逐步构建并频繁测试每个部分,确保每一步都正确无误,容易定位问题。利用Visual Studio的调试器来跟踪和诊断问题。
完整源码
#include <windows.h> // 窗口过程函数原型 LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM); #include <d3d9.h> #include <d3dx9.h> // Link necessary d3d9 libraries #pragma comment (lib, "d3d9.lib") #pragma comment (lib, "d3dx9.lib") LPDIRECT3D9 d3d; // the pointer to our Direct3D interface LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class LPDIRECT3DVERTEXBUFFER9 v_buffer; void initD3D(HWND hWnd) { d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device information ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use d3dpp.Windowed = TRUE; // program windowed, not fullscreen d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D // create a device class using this information and the info from the d3dpp stuct d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); // Set up the view matrix D3DXVECTOR3 vCamera(0.0f, 0.0f, -10.0f); // 摄像机位置 D3DXVECTOR3 vLookat(0.0f, 0.0f, 0.0f); // 观察点位置 D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f); // 上向量 D3DXMATRIX matView; // 视图矩阵 D3DXMatrixLookAtLH(&matView, &vCamera, &vLookat, &vUpVec); d3ddev->SetTransform(D3DTS_VIEW, &matView); // 应用视图矩阵 // Set up the projection matrix D3DXMATRIX matProj; // 投影矩阵 D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4, (FLOAT)800 / (FLOAT)600, 1.0f, 100.0f); d3ddev->SetTransform(D3DTS_PROJECTION, &matProj); // 应用投影矩阵 } struct CUSTOMVERTEX { FLOAT X, Y, Z; D3DVECTOR NORMAL; }; #define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL) void initGraphics() { // create the vertices using the CUSTOMVERTEX struct CUSTOMVERTEX vertices[] = { { -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, }, { 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, }, { -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, -1.0f, }, }; //LPDIRECT3DVERTEXBUFFER9 v_buffer; // create a vertex buffer interface called v_buffer d3ddev->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0, CUSTOMFVF, D3DPOOL_MANAGED, &v_buffer, NULL); VOID* pVoid; // a void pointer // lock v_buffer and load the vertices into it v_buffer->Lock(0, 0, (void**)&pVoid, 0); memcpy(pVoid, vertices, sizeof(vertices)); v_buffer->Unlock(); // set the vertex buffer d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX)); // set the FVF d3ddev->SetFVF(CUSTOMFVF); } void render_frame(void) { // clear the window to a deep blue d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); d3ddev->BeginScene(); // begins the 3D scene // select which vertex format we are using d3ddev->SetFVF(CUSTOMFVF); // select the vertex buffer to display d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(CUSTOMVERTEX)); // copy the vertex buffer to the back buffer d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1); d3ddev->EndScene(); // ends the 3D scene d3ddev->Present(NULL, NULL, NULL, NULL); // displays the created frame on the screen } // WinMain:应用程序入口点 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow) { WNDCLASSW wc = {0}; // 创建窗口类结构体实例,并初始化为0 wc.hbrBackground = (HBRUSH)COLOR_WINDOW; // 窗口背景颜色 wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 窗口光标 wc.hInstance = hInst; // 应用程序实例句柄 wc.lpszClassName = L"MyWindowClass"; // 窗口类名 wc.lpfnWndProc = WindowProcedure; // 窗口过程函数 // 注册窗口类 if (!RegisterClassW(&wc)) return -1; // 创建窗口 HWND hWnd = CreateWindowW(L"MyWindowClass", L"我的窗口", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 500, 500, NULL, NULL, NULL, NULL); initD3D(hWnd); // sets up and initializes Direct3D initGraphics(); // creates the shape to render // 消息循环 MSG msg = {0}; while (TRUE) { //while (GetMessage(&msg, NULL, 0, 0)) while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) return 0; } render_frame(); } v_buffer->Release(); // close and release the vertex buffer d3ddev->Release(); // close and release the 3D device d3d->Release(); // close and release Direct3D return 0; } // 窗口过程函数:处理窗口消息 LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch(msg) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProcW(hwnd, msg, wp, lp); } return 0; }