D3D实战-在窗口中画一个三角形
Posted 微软专题
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了D3D实战-在窗口中画一个三角形相关的知识,希望对你有一定的参考价值。
开发环境
系统:Windows 10
IDE:Visual Studio 2019
开发步骤
创建窗口
1、新建一个空白C++项目,文件->新建->项目,语言选择C++,平台选择Windows,选择空项目,点击下一步,真写项目名称和位置,点击创建。
2、新建一个C++文件:stdafx.cpp和一个头文件:stdafx.h。
3、解决方案资源管理器中右击项目,选择属性。
常规里边,输出目录改为:bin$(Configuration)\,中间目录改为:obj$(Configuration)\。
调试里边,工作目录改为:$(OutDir)
点击确定
4、右击项目,选择属性。
C/C++->预处理器,预处理器定义,编辑,填入如下内容:
_DEBUG
_WINDOWS
NOMINMAX
C/C++->预编译头,改为使用(/Yu)。
链接器->输入,附加依赖项改为:
d3d12.lib
D3D11.lib
dxgi.lib
d3dcompiler.lib
d2d1.lib
dwrite.lib
dxguid.lib
xaudio2.lib
mfcore.lib
mfplat.lib
mfreadwrite.lib
mfuuid.lib
链接器->系统,子系统改为:窗口 (/SUBSYSTEM:WINDOWS)
点击确定。
5、右击项目,管理NuGet程序包,浏览里边搜索DirectXTK12,在搜索结果中选择directxtk12_desktop_2017,安装。
6、右击stdafx.cpp文件,选择属性,预编译头,改为创建 (/Yc)
6、打开stdafx.h,输入以下内容
#pragma once
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <time.h>
#include <string>
#include <d3d12.h>
#include <d3d11_3.h>
#include <d3d11on12.h>
#include <dxgi1_4.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#include <d2d1_3.h>
#include <dwrite_3.h>
#include <wincodec.h>
#include <CommonStates.h>
#include <DDSTextureLoader.h>
#include <ResourceUploadBatch.h>
#include <DirectXHelpers.h>
#include <Effects.h>
#include <GamePad.h>
#include <GeometricPrimitive.h>
#include <GraphicsMemory.h>
#include <Keyboard.h>
#include <Model.h>
#include <Mouse.h>
#include <PostProcess.h>
#include <PrimitiveBatch.h>
#include <ScreenGrab.h>
#include <SimpleMath.h>
#include <SpriteBatch.h>
#include <SpriteFont.h>
#include <VertexTypes.h>
#include <WICTextureLoader.h>
#include <Audio.h>
#include <string>
#include <wrl.h>
#include <shellapi.h>
7、创建一个C++源文件TriangleMain.cpp,输入以下内容:
#include "stdafx.h"
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szWindowClass[] = L"TriangleWindow";
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此处放置代码。
// 初始化全局字符串
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
MSG msg = {};
// 主消息循环:
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = szWindowClass;
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(szWindowClass, L"Triangle", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN:
break;
case WM_PAINT:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
编译后按F5运行,正常情况下会出现一个空白窗口。这个窗口用作绘图区域。
准备3D环境
首先,我们要在程序里枚举出支持DirectX的设备,并创建一个交换链用于呈现后台缓存中的图像内容。我们将这部分代码封装到一个Device的类中,绘图操作放到Scene类中。
Device类实现
这个类中需要创建两个东西,一个设备接口实例,一个交换链实例,还有一个是CPU与GPU数据同步的围栏,以及提供创建各类资源、呈现图像、缓存管理的方法。别外,创建交互链需要提供窗口的句柄和尺寸。
//Device.h
#pragma once
using namespace DirectX;
using namespace Microsoft::WRL;
class Device
{
private:
HWND m_hwnd; //窗口句柄
int m_width; //窗口宽度
int m_height; //窗口高度
bool m_useWarpDevice; //是否使用模拟设备
UINT m_frameCount; //缓冲数量
int m_rtvDescriptorSize; //RTV缓存大小
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap;
std::vector<ComPtr<ID3D12Resource>> m_renderTargets;
ComPtr<ID3D12Resource> m_depthStencil;
ComPtr<ID3D12DescriptorHeap> m_dsvHeap;
ComPtr<ID3D12Fence> m_fence;
UINT64 m_fenceValue;
HANDLE m_fenceEvent;
int m_frameIndex;
void GetHardwareAdapter(_In_ IDXGIFactory2* pFactory, _Outptr_result_maybenull_ IDXGIAdapter1** ppAdapter);
void CreateDevice();
void CreateRenderTarget();
void CreateDepthStencil();
public:
Device(HWND hwnd, int wWidth, int wHeight, int frameCount, bool useWrap);
HRESULT CreateCommandList(ComPtr<ID3D12CommandAllocator>& commandAllocator, ComPtr<ID3D12GraphicsCommandList>& commandList);
void Present();
void PostCommand(ID3D12GraphicsCommandList* commandList);
void WaitGpu();
ID3D12Device* GetDevice() { return m_device.Get(); }
ID3D12DescriptorHeap* GetDepthStencilHeap() { return m_dsvHeap.Get(); };
ComPtr<ID3D12CommandQueue> GetCommandQueue() { return m_commandQueue; }
ID3D12Resource* GetRenderTargetResource() { return m_renderTargets[m_frameIndex].Get(); }
CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetHandle()
{
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap.Get()->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
return rtvHandle;
}
};
//Device.cpp
#include "stdafx.h"
#include "Device.h"
void Device::GetHardwareAdapter(IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter)
{
ComPtr<IDXGIAdapter1> adapter;
*ppAdapter = nullptr;
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don‘t select the Basic Render Driver adapter.
// If you want a software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don‘t create the
// actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
break;
}
}
*ppAdapter = adapter.Detach();
}
void Device::CreateDevice()
{
UINT dxgiFactoryFlags = 0;
#if defined(_DEBUG)
// Enable the debug layer (requires the Graphics Tools "optional feature").
// NOTE: Enabling the debug layer after device creation will invalidate the active device.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// Enable additional debug layers.
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));
bool useWarpDevice = true;
if (useWarpDevice)
{
ComPtr<IDXGIAdapter> warpAdapter;
factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter));
D3D12CreateDevice(
warpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
);
}
else
{
ComPtr<IDXGIAdapter1> hardwareAdapter;
GetHardwareAdapter(factory.Get(), &hardwareAdapter);
D3D12CreateDevice(
hardwareAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
);
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = m_frameCount;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
ComPtr<IDXGISwapChain1> swapChain;
factory->CreateSwapChainForHwnd(
m_commandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
m_hwnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain
);
// This sample does not support fullscreen transitions.
factory->MakeWindowAssociation(m_hwnd, DXGI_MWA_NO_ALT_ENTER);
swapChain.As(&m_swapChain);
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
m_fenceValue = 1;
// Create an event handle to use for frame synchronization.
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (m_fenceEvent == nullptr)
{
HRESULT_FROM_WIN32(GetLastError());
}
}
void Device::CreateRenderTarget()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = m_frameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap));
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_renderTargets.resize(m_frameCount);
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
// Create a RTV for each frame.
for (UINT n = 0; n < m_frameCount; n++)
{
m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n]));
m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
NAME_D3D12_OBJECT_INDEXED(m_renderTargets, n);
rtvHandle.Offset(1, m_rtvDescriptorSize);
}
}
void Device::CreateDepthStencil()
{
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&m_dsvHeap)));
D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {};
depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;
D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0;
m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, m_width, m_height, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL),
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue,
IID_PPV_ARGS(&m_depthStencil)
);
NAME_D3D12_OBJECT(m_depthStencil);
m_device->CreateDepthStencilView(m_depthStencil.Get(), &depthStencilDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
}
Device::Device(HWND hwnd, int width, int height, int frameCount, bool useWrap):
m_hwnd(hwnd),
m_width(width),
m_height(height),
m_frameCount(frameCount),
m_useWarpDevice(useWrap)
{
CreateDevice();
CreateRenderTarget();
CreateDepthStencil();
}
HRESULT Device::CreateCommandList(ComPtr<ID3D12CommandAllocator>& commandAllocator, ComPtr<ID3D12GraphicsCommandList>& commandList)
{
HRESULT hr = E_INVALIDARG;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
if (hr == S_OK) {
commandAllocator = m_commandAllocator;
NAME_D3D12_OBJECT(m_commandAllocator);
ComPtr<ID3D12GraphicsCommandList> m_commandList;
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), nullptr, IID_PPV_ARGS(&m_commandList));
commandList = m_commandList;
NAME_D3D12_OBJECT(m_commandList);
}
return hr;
}
void Device::Present()
{
m_swapChain->Present(1, 0);
WaitGpu();
}
void Device::PostCommand(ID3D12GraphicsCommandList* commandList)
{
ID3D12CommandList* ppCommandLists[] = { commandList };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
}
void Device::WaitGpu()
{
const UINT64 fence = m_fenceValue;
m_commandQueue->Signal(m_fence.Get(), fence);
m_fenceValue++;
// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
m_fence->SetEventOnCompletion(fence, m_fenceEvent);
WaitForSingleObject(m_fenceEvent, INFINITE);
}
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
Scene的实现
这个类准备一个命令表,通过这个命令表传递GPU绘图命令,创建相关的资源,如顶点,纹理等。最后交给命令队列去执行,将绘图结果放入后台缓冲,最后调用交互链的呈现接口,将生成的图像呈现在屏幕上。
Scene.h
#pragma once
#include "Device.h"
using namespace DirectX;
using namespace DirectX::SimpleMath;
class Scene
{
private:
CD3DX12_VIEWPORT m_viewport;
CD3DX12_RECT m_scissorRect;
Device* m_device;
std::unique_ptr<GraphicsMemory> m_graphicsMemory;
std::unique_ptr<DirectX::CommonStates> m_states;
ComPtr<ID3D12GraphicsCommandList> m_commandList;
ID3D12CommandQueue* m_commandQueue;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
DirectX::SimpleMath::Matrix m_world;
DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;
using VertexType = DirectX::VertexPositionColor;
std::unique_ptr<DirectX::BasicEffect> m_effect;
std::unique_ptr<DirectX::PrimitiveBatch<VertexType>> m_batch;
public:
HWND m_hwnd;
Scene(HWND hwnd, int width, int height);
~Scene() {};
void Render();
};
Scene.cpp
#include "stdafx.h"
#include "Scene.h"
Scene::Scene(HWND hwnd, int width, int height) :
m_hwnd(hwnd),
m_viewport(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)),
m_scissorRect(0, 0, static_cast<LONG>(width), static_cast<LONG>(height))
{
m_device = new Device(hwnd, width, height, 3, true);
m_graphicsMemory = std::make_unique<GraphicsMemory>(m_device->GetDevice());
m_device->CreateCommandList(m_commandAllocator, m_commandList);
m_commandList->Close();
m_states = std::make_unique<CommonStates>(m_device->GetDevice());
m_batch = std::make_unique<PrimitiveBatch<VertexType>>(m_device->GetDevice());
RenderTargetState rtState(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_D32_FLOAT);
EffectPipelineStateDescription pd(
&VertexType::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState);
m_effect = std::make_unique<BasicEffect>(m_device->GetDevice(), EffectFlags::VertexColor, pd);
Matrix proj = Matrix::CreateScale(2.f / float(width),
-2.f / float(height), 1.f)
* Matrix::CreateTranslation(-1.f, 1.f, 0.f);
m_effect->SetProjection(proj);
}
void Scene::Render()
{
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator.Get(),nullptr);
ID3D12DescriptorHeap* heaps[] = { m_states->Heap() };
m_commandList->SetDescriptorHeaps(_countof(heaps), heaps);
m_commandList->RSSetViewports(1, &m_viewport);
m_commandList->RSSetScissorRects(1, &m_scissorRect);
ID3D12Resource* rt = m_device->GetRenderTargetResource();
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(rt, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_device->GetRenderTargetHandle();
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(m_device->GetDepthStencilHeap()->GetCPUDescriptorHandleForHeapStart());
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
const float clearColor[] = { 0.5f, 0.2f, 0.4f, 0.5f };
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
m_effect->Apply(m_commandList.Get());
m_batch->Begin(m_commandList.Get());
VertexPositionColor v1(Vector3(400.f, 150.f, 0.f), Colors::Yellow);
VertexPositionColor v2(Vector3(600.f, 450.f, 0.f), Colors::Yellow);
VertexPositionColor v3(Vector3(200.f, 450.f, 0.f), Colors::Yellow);
m_batch->DrawTriangle(v1, v2, v3);
m_batch->End();
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(rt, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
m_commandList->Close();
m_device->PostCommand(m_commandList.Get());
m_device->Present();
}
以上是关于D3D实战-在窗口中画一个三角形的主要内容,如果未能解决你的问题,请参考以下文章