我们可以使用D3D为游戏,科学和桌面应用程序创建3-D图形。
当然,我们第一步要开始认识里面的基本api。
#include <d3d11.h> #include <chrono> using namespace std::chrono; #pragma comment(lib,"D3D11.lib") //----------------------------------------------------------------------------- // 全局变量 //----------------------------------------------------------------------------- HRESULT hr; ID3D11Device* g_pd3dDevice = nullptr; IDXGISwapChain* g_pSwapChain = nullptr; ID3D11DeviceContext* g_pImmediateContext = nullptr; ID3D11RenderTargetView* pTarget = nullptr; std::chrono::steady_clock::time_point last; //----------------------------------------------------------------------------- // Desc: 初始化Direct3D //----------------------------------------------------------------------------- HRESULT InitD3D(HWND hWnd) { DXGI_SWAP_CHAIN_DESC sd; ZeroMemory(&sd, sizeof(sd)); sd.BufferCount = 1; // BufferCount: number of buffers 1: 设置一个后缓存,再加上一个自动设置的前缓存,就是双缓存 sd.BufferDesc.Width = 0; //系统自动设置合适的值 sd.BufferDesc.Height = 0; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 0; //不处于全屏模式,所以不用管刷新率 sd.BufferDesc.RefreshRate.Denominator = 0; sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //程序自己决定缩放比率 sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; //对于大多数显示器,不用设置扫描线顺序 sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // Use the surface or resource as an output render target. sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; //不需要抗锯齿,一般默认该值 sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; //窗口化 if (FAILED(hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, NULL, &g_pImmediateContext))) { return hr; } //gain access to texture subresource in swap chain (back buffer) ID3D11Resource* pBackBuffer = nullptr; g_pSwapChain->GetBuffer(0, _uuidof(ID3D11Resource), reinterpret_cast<void**>(&pBackBuffer)); // Get the back buffer 0: 后缓存的索引 g_pd3dDevice->CreateRenderTargetView( pBackBuffer, nullptr, &pTarget // pTarget is used to save RenderTarget view ); pBackBuffer->Release(); } //----------------------------------------------------------------------------- // Desc: 释放创建对象 //----------------------------------------------------------------------------- VOID Cleanup() { //释放Direct3D设备对象 if (g_pd3dDevice != NULL) g_pd3dDevice->Release(); //释放Direct3D对象 if (g_pSwapChain != NULL) g_pSwapChain->Release(); if (g_pImmediateContext != NULL) g_pImmediateContext->Release(); if (pTarget != NULL) pTarget->Release(); } //定时器 float Peek() { return duration<float>(steady_clock::now() - last).count(); } //----------------------------------------------------------------------------- // Desc: 渲染图形 //----------------------------------------------------------------------------- void Render() { // Just clear the backbuffer float r = sin(Peek()) / 2.0f + 0.5f; float ClearColor[4] = { r, r, 1.0f, 1.0f }; //red,green,blue,alpha g_pImmediateContext->ClearRenderTargetView(pTarget, ClearColor); // undertake all the drawing work g_pSwapChain->Present(0u, 0u); //Presents a rendered image to the user. } //----------------------------------------------------------------------------- // Desc: 消息处理 //----------------------------------------------------------------------------- LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_DESTROY: Cleanup(); PostQuitMessage(0); return 0; case WM_PAINT: Render(); ValidateRect(hWnd, NULL); return 0; } return DefWindowProc(hWnd, msg, wParam, lParam); } //----------------------------------------------------------------------------- // Desc: 程序入口 //----------------------------------------------------------------------------- INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, INT) { //注册窗口类 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ClassName", NULL }; RegisterClassEx(&wc); //创建窗口 HWND hWnd = CreateWindow(L"ClassName", L"A Direct3D appp", WS_OVERLAPPEDWINDOW, 200, 100, 600, 500, NULL, NULL, wc.hInstance, NULL); last = steady_clock::now(); //running timer //初始化Direct3D if (SUCCEEDED(InitD3D(hWnd))) { //显示主窗口 ShowWindow(hWnd, SW_SHOWDEFAULT); UpdateWindow(hWnd); //进入消息循环 MSG msg; ZeroMemory(&msg, sizeof(msg)); while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Render(); //渲染图形 } } } UnregisterClass(L"ClassName", wc.hInstance); return 0; }
先上一个简单的demo供参考,里面有很多的参数介绍。
大概流程:
创建一个交换链(D3D11CreateDeviceAndSwapChain),然后创建一个后缓存(GetBuffer),再创建一个RenderTarget作为后台缓冲区的渲染目标(ID3D11RenderTargetView* pTarget = nullptr)。 使用CreateRenderTargetView创建用于访问资源数据的渲染目标视图,它承担所有绘图工作。
ClearRenderTargetView将渲染目标中的所有元素设置为一个值。
上面都是对象的创建, 我们还需要context,即上下文,用于发送渲染命令并配置渲染管道。D3D11CreateDeviceAndSwapChain已经帮我们创建好了
等这些都创建完毕,则可以调用g_pSwapChain->Present(0u, 0u); 来将渲染的图片发给user --------------------Present命令相当于bitblt,用于将后缓存的视图快速复制给前缓存
另附一张图:
简单说来就是,传统情况下,显示器从单个缓冲区扫描像素的时候,它遵循自左上角到右下角的点扫描,就是一个像素一个像素的扫描,那么如果我们在单个缓冲区里绘制东西的时候,还没绘制好就被显示器扫描走了,那就会显示不完整的图形。
所以D3D需要后缓存来避免这样的问题。当后缓存的东西绘制完毕,使用present或者filp来将后缓存快速复制给前缓存,那么显示器在扫描的时候,每次扫描完前缓存的东西时(扫描也需要时间的),后缓存已经准备好了。
言归正传,在代码中我们有了后缓存pBackBuffer,但是我们得需要一个渲染视图在后缓存中绘制我们需要的东西,那么我们就需要创建一个渲染对象pTarget,然后使用CreateRenderTargetView来使pTarget保存渲染的视图(渲染视图相当于后缓冲区)。 好了,有了渲染视图,就可以使用ClearRenderTargetView将RGBA的数值渲染到视图中,这样我们就可以使用Present来显示图像了。
为什么需要渲染呢?
从逻辑上将, 我们会说:“后缓存...!" 并完成。 但是,Direct3D在这一点上并不知道。
在Direct3D中渲染时,必须建立渲染目标。 这是一个简单的com对象,它在视频内存中维护一个位置,供我们渲染。 在大多数情况下, 这是后台缓冲区。
拓展:
纹理资源是旨在存储纹理像素的数据的结构化集合。纹素表示管道可以读取或写入的纹理的最小单位。与缓冲区不同,纹理可以被着色器单元读取,从而可以通过纹理采样器进行过滤。
渲染管线是指:在给定一个3D场景的几何描述及一架已确定位置和方向的虚拟摄像机(virtual camera)时,根据虚拟摄像机的视角生成2D图像的一系列步骤。