源程序:
此次例子的大部分代码均拷贝自上一章 InitD3D 例子程序。
d3dUtility.h
/*************************************************************************
> File Name: d3dUtility.h
> Author: YLD10
> Mail: yl1315348050@yahoo.com
> Created Time: 2018.6.1 21:36
> Describe: Win32 Application InitD3D.
《基于 DirectX11 的 3D 图形程序设计案例教程》
5.2 节程序例子 绘制一个三角形
************************************************************************/
#pragma once
#include <Windows.h>
//////////////////////////////////////////////////////////////////////////
// XNA 数学库相关头文件
//////////////////////////////////////////////////////////////////////////
#include <d3dCompiler.h>
#include <xnamath.h>
//////////////////////////////////////////////////////////////////////////
// DirectX11 相关头文件
//////////////////////////////////////////////////////////////////////////
#include <d3d11.h>
#include <d3dx11.h>
//////////////////////////////////////////////////////////////////////////
// DirectX11 相关库
//////////////////////////////////////////////////////////////////////////
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dCompiler.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")
namespace d3d // 定义一个 d3d 命名空间
{
/*
* 初始化 D3D
*/
bool InitD3D(HINSTANCE hInstance,
int width, int height,
ID3D11RenderTargetView** renderTargerView, // 目标渲染视图接口
ID3D11DeviceContext** immediateContext, // 执行上下文接口
IDXGISwapChain** swapChain, // 交换链接口
ID3D11Device** device); // 设备用接口, 每个 D3D 程序至少有一个设备
/*
* 消息循环
*/
int EnterMsgLoop(bool (*ptr_display)(float timeDelta));
/*
* 回调函数
*/
LRESULT CALLBACK WndProc(HWND,
UINT msg,
WPARAM,
LPARAM lParam);
}
d3dUtility.cpp
/*************************************************************************
> File Name: d3dUtility.cpp
> Author: YLD10
> Mail: yl1315348050@yahoo.com
> Created Time: 2018.6.1 21:36
> Describe: Win32 Application InitD3D.
《基于 DirectX11 的 3D 图形程序设计案例教程》
5.2 节程序例子 绘制一个三角形
************************************************************************/
#include "d3dUtility.h"
/*
* D3D 初始化
* 这个函数中包括两个部分: 第一部分: 创建一个窗口; 第二部分: 初始化 D3D
* 函数参数包括:
* 1.HINSTANCE 当前应用程序实例的句柄
* 2.int width 窗口宽
* 3.int height 窗口高
* 4.ID3D11RenderTargetView** renderTargetView 目标渲染视图指针
* 5.ID3D11DeviceContext** immediateContext 立即执行上下文指针
* 6.IDXGISwapChain** swapChain 交换链指针
* 7.ID3D11Device** device 设备用指针, 每个 D3D 程序至少有一个设备
*/
bool d3d::InitD3D(HINSTANCE hInstance,
int width, int height,
ID3D11RenderTargetView** renderTargerView,
ID3D11DeviceContext** immediateContext,
IDXGISwapChain** swapChain,
ID3D11Device** device)
{
//************* 第一部分: 创建一个窗口开始 ***************
// 这部分的代码和 2.2 节中创建窗口代码基本一致
// 具体参数的注释可以参考 2.2 节程序例子
// 创建窗口的 4 个步骤: 1 设计一个窗口类; 2 注册窗口类
// 3 创建窗口; 4 窗口显示和更新
// 1 设计一个窗口类
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L"Direct3D11App";
// 2 注册窗口类
if (!RegisterClass(&wc))
{
::MessageBox(0, L"RegisterClass() - FAILED", 0, 0);
return false;
}
// 3 创建窗口
HWND hwnd;
hwnd = ::CreateWindow(L"Direct3D11App",
L"D3D11",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
width,
height,
0,
0,
hInstance,
0);
if (!hwnd)
{
::MessageBox(0, L"CreateWindow() - FAILED", 0, 0);
return false;
}
// 4 窗口显示和更新
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
//************* 第一部分: 创建一个窗口结束 ***************
//************* 第二部分: 初始化 D3D 开始 ***************
// 初始化 D3D 设备主要为以下步骤
// 1. 描述交换链, 即填充 DXGI_SWAP_CHAIN_DESC 结构
// 2. 使用 D3D11CreateDeviceAndSwapChain 创建 D3D 设备 (ID3D11Device)
// 执行上下文接口 (ID3D11DeviceContext), 交换链接口 (IDXGISwapChain)
// 3. 创建目标渲染视图 (ID3D11RenderTargetView)
// 4. 设置视口 (View Port)
// 第一步, 描述交换链, 即填充 DXGI_SWAP_CHAIN_DESC 结构
DXGI_SWAP_CHAIN_DESC sd; // 首先声明一个 DXGI_SWAP_CHAIN_DESC 的对象 sd
ZeroMemory(&sd, sizeof(sd)); // 用 ZeroMemory 对 sd 进行初始化
sd.BufferCount = 1; // 交换链中后台缓存数量, 通常为 1
sd.BufferDesc.Width = width; // 缓存区中的窗口宽
sd.BufferDesc.Height = height; // 缓存区中的窗口高
sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // 表示红绿蓝 Alpha 各 8 位
sd.BufferDesc.RefreshRate.Numerator = 60; // 刷新频率的分子为 60
sd.BufferDesc.RefreshRate.Denominator = 1; // 刷新频率的分母为 1, 即
// 刷新频率为每秒 60 次
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 用来描述后台缓存用法
// 控制 CPU 对后台缓存的访问
sd.OutputWindow = hwnd; // 指向渲染目标窗口的句柄
sd.SampleDesc.Count = 1; // 多重采样, 本例中不采用多重采样
sd.SampleDesc.Quality = 0; // 所以 Count = 1, Quality = 0
sd.Windowed = TRUE; // TRUE 为窗口模式, FALSE 为全屏模式
// 第二步, 创建设备, 交换链以及立即执行上下文
// 创建一个数组确定尝试创建 Featurelevel 的顺序
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_0, // D3D11 所支持的特征, 包括 shader model 5
D3D_FEATURE_LEVEL_10_1, // D3D10 所支持的特征, 包括 shader model 4
D3D_FEATURE_LEVEL_10_0
};
// 获取 D3D_FEATURE_LEVEL 数组的元素个数
UINT numFeatureLevels = ARRAYSIZE(featureLevels);
// 调用 D3D11CreateDeviceAndSwaoChain 创建交换链、设备和执行上下文
// 分别存入 swapChain, device, immediateContext
if (FAILED(D3D11CreateDeviceAndSwapChain(
NULL, // 确定显示适配器, NULL 表示默认显示适配器
D3D_DRIVER_TYPE_HARDWARE, // 驱动类型, 这里表示使用三维硬件加速
NULL, // 只有上一个参数设置为
// D3D_DRIVER_TYPE_SOFTWARE 时才使用该参数
0, // 也可以设置为 D3D11_CREATE_DEVICE_DEBUG 开启调试模式
featureLevels, // 前面定义的 D3D_FEATURE_LEVEL 数组
numFeatureLevels, // D3D_FEATURE_LEVEL 的元素个数
D3D11_SDK_VERSION, // SDK 的版本, 这里为 D3D11
&sd, // 前面定义的 DXGI_SWAP_CHAIN_DESC 对象
swapChain, // 返回创建好的交换链指针, InitD3D 函数传递的实参
device, // 返回创建好的设备用指针, InitD3D 函数传递到实参
NULL, // 返回当前设备支持的 featureLevels 数组中的第一个对象,
// 一般设置为 NULL
immediateContext))) // 返回创建好的执行上下文指针,
// InitD3D 函数传递的实参
{
::MessageBox(0, L"CreateDevice - FAILED", 0, 0);
return false;
}
// 第三步, 创建并设置渲染目标视图
HRESULT hr = 0; // COM 要求所有的方法都会返回一个 HRESULT 类型的错误号
ID3D11Texture2D* pBackBuffer = NULL; // ID3D11Texture2D 类型的后台缓存指针
// 调用 GetBuffer() 函数得到后台缓存对象, 并存入 &pBackBuffer 中
hr = (*swapChain)->GetBuffer(0, // 缓存索引, 一般设置为 0
__uuidof(ID3D11Texture2D), // 缓存类型
(LPVOID*)&pBackBuffer); // 缓存指针
// 判断 GetBuffer 是否调用成功
if (FAILED(hr))
{
::MessageBox(0, L"GetBuffer - FAILED", 0, 0);
return false;
}
// 调用 CreateRenderTargetView 创建好渲染目标视图
// 创建后存入 renderTargetView 中
hr = (*device)->CreateRenderTargetView(pBackBuffer, // 上面创建好的后台缓存
NULL, // 设置为 NULL 得到默认的渲染目标视图
renderTargerView); // 返回创建好的渲染目标视图
// InitD3D 函数传递的实参
pBackBuffer->Release(); // 释放后台缓存
// 判断 CreateRenderTargetView 是否调用成功
if (FAILED(hr))
{
::MessageBox(0, L"CreateRender - FAILED", 0, 0);
return false;
}
// 将渲染目标视图绑定到渲染管线
(*immediateContext)->OMSetRenderTargets(1, // 绑定的目标视图的个数
renderTargerView, // 渲染目标视图
NULL); // 不绑定深度模版
// 第四步, 设置视口大小, D3D11 默认不会设置视口, 此步骤必须手动设置
D3D11_VIEWPORT vp; // 创建一个视口的对象
vp.Width = width; // 视口的宽
vp.Height = height; // 视口的高
vp.MinDepth = 0.0f; // 深度值的下限, 由于深度值是 [0, 1] 所以下限值是 0
vp.MaxDepth = 1.0f; // 深度值的上限, 上限值是 1
vp.TopLeftX = 0; // 视口左上角的横坐标
vp.TopLeftY = 0; // 视口左上角的纵坐标
// 设置视口
(*immediateContext)->RSSetViewports(1, // 视口的个数
&vp); // 上面创建的视口对象
return true;
//************* 第二部分: 初始化 D3D 结束 ***************
}
/*
* 消息循环函数, 和之前 "Hello World" 程序中 Run() 起到同样的功能
* bool(*ptr_display)(float timeDelta) 表示传递一个函数指针作为参数
* 这个函数有一个 float 类型的参数, 有一个 bool 类型的返回
*/
int d3d::EnterMsgLoop(bool(*ptr_display)(float timeDelta))
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG)); // 初始化内存
static float lastTime = (float)timeGetTime(); // 第一次获取当前时间
while (WM_QUIT != msg.message)
{
if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
// 第二次获取当前时间
float currTime = (float)timeGetTime();
// 获取两次时间之间的时间差
float timeDelta = (currTime - lastTime) * 0.001f;
// 调用显示函数, 这在后面时间图形的变化 (如旋转) 时会用到
ptr_display(timeDelta);
lastTime = currTime;
}
}
return msg.wParam;
}
d3dTriangle.cpp
/*************************************************************************
> File Name: d3dTriangle.cpp
> Author: YLD10
> Mail: yl1315348050@yahoo.com
> Created Time: 2018.6.1 21:35
> Describe: Win32 Application InitD3D.
《基于 DirectX11 的 3D 图形程序设计案例教程》
5.2 节程序例子 绘制一个三角形
************************************************************************/
#include "d3dUtility.h"
ID3D11Device* device = NULL; // D3D11 设备指针
IDXGISwapChain* swapChain = NULL; // 交换链指针
ID3D11DeviceContext* immediateContext = NULL; // 执行上下文指针
ID3D11RenderTargetView* renderTargetView = NULL; // 渲染目标视图指针
// 新增的两个全局变量
ID3D11VertexShader* vertexShader; // 顶点着色器
ID3D11PixelShader* pixelShader; // 像素着色器
// 定义一个顶点结构, 这个顶点只需要包含坐标信息
struct Vertex
{
XMFLOAT3 Pos;
};
//************* 以下为框架函数 ***************
/*
* 第一步, 创建顶点着色器
* 第二步, 创建像素着色器
* 第三步, 创建并设置输入布局
* 第四步, 创建顶点缓存
*/
bool Setup()
{
// 第一步, 创建顶点着色器
// 定义一个着色器编译标识符
// D3DCOMPILE_ENABLE_STRICTINESS 表示强制严格编译
// 这样一些已经废弃的语法将不能编译
DWORD dwShaderFlag = D3DCOMPILE_ENABLE_STRICTNESS;
// ID3DBlob 接口用于表示任意长度的数据
// 本例使用该接口声明一个对象来存放编译后的着色器
ID3DBlob* pVSBlob = NULL;
// 从指定文件编译着色器
if (FAILED(D3DX11CompileFromFile(
L"Triangle.hlsl", // 这是我们创建的 hlsl 文件
NULL, // 可选项, 指向一个宏定义数组的指针, 一般为 NULL
NULL, // 用于处理着色器中的 include 文件, 没有 include 文件则为 NULL
"VSMain", // 指定顶点着色器的入口函数
"vs_5_0", // 指定顶点着色器版本, 这里是用 5.0 版本
dwShaderFlag, // 上面定义的着色器编译标识符
0, // Effect 编译标识符, 如果不是编译 Effect 文件则设置为 0
NULL, // 指向 ID3DX11ThreadPump 的指针,
// 设置为 NULL 表示这个函数在完成之前不会返回
&pVSBlob, // 指向编译好的顶点着色器的所在内存的指针
NULL, // 指向存放在编译中产生错误和警告的指针
NULL))) // 一个指向返回值的指针, 如果第八个参数为 NULL,
// 这里也设置为 NULL
{
// 如果编译失败, 弹出一个消息框
MessageBox(NULL, L"Fail to compile vertex shader", L"ERROR", MB_OK);
return S_FALSE;
}
// 用 CreateVertexShader 创建顶点着色器
device->CreateVertexShader(
pVSBlob->GetBufferPointer(), // 指向 pVSBlob 所在内存块的起始地址
pVSBlob->GetBufferSize(), // 取得 pVSBlob 的大小
NULL, // 指向 ID3D11ClassLinkage 的指针, 一般为空
&vertexShader); // 将创建好的顶点着色器存放到 vertexShader 中
// 第二步, 创建像素着色器
// 声明一个 ID3DBlob 对象来存放编译后的像素着色器
ID3DBlob* pPSBlob = NULL;
if (FAILED(D3DX11CompileFromFile(
L"Triangle.hlsl", // 这是我们创建的 hlsl 文件
NULL, // 可选项, 指向一个宏定义数组的指针, 一般为 NULL
NULL, // 用于处理着色器中的 include 文件, 没有 include 文件则为 NULL
"PSMain", // 指定像素着色器的入口函数
"ps_5_0", // 指定像素着色器版本, 这里是用 5.0 版本
dwShaderFlag, // 上面定义的着色器编译标识符
0, // Effect 编译标识符, 如果不是编译 Effect 文件则设置为 0
NULL, // 指向 ID3DX11ThreadPump 的指针,
// 设置为 NULL 表示这个函数在完成之前不会返回
&pPSBlob, // 指向编译好的像素着色器的所在内存的指针
NULL, // 指向存放在编译中产生错误和警告的指针
NULL))) // 一个指向返回值的指针, 如果第八个参数为 NULL,
// 这里也设置为 NULL
{
MessageBox(NULL, L"Fail to compile pixel shader", L"ERROR", MB_OK);
return S_FALSE;
}
// 用 CreatePixelShader 创建像素着色器
device->CreatePixelShader(
pPSBlob->GetBufferPointer(), // 指向 pPSBlob 所在内存块的起始地址
pPSBlob->GetBufferSize(), // 取得 pPSBlob 的大小
NULL, // 指向 ID3D11ClassLinkage 的指针, 一般为空
&pixelShader); // 将创建好的像素着色器存放到 pixelShader 中
// 第三步, 创建并设置输入布局
// 所谓输入布局, 就是制定顶点结构所包含信息的实际意义
// D3D11_INPUT_ELEMENT_DESC 用于描述顶点结构的意义
// 以便让显卡识别所定义的顶点结构
// 这里定义一个 D3D11_INPUT_ELEMENT_DESC 数组,
// 由于定义的顶点结构只有位置坐标, 所有这个数组里也只有一个元素
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{
"POSITION", // 语义标识符, 必须和 hlsl 文件中 VSMain 所用的标识符一致
0, // 表示第 1 个元素, 注意和数组一样都是从 0 开始计数,
DXGI_FORMAT_R32G32B32_FLOAT, // 96 位浮点像素, 红绿蓝各 32 位
0, // 可以取值 0 - 15, 0 表示绑定顶点缓存到第一个输入槽
0, // 可选项, 定义了缓存的对齐方式,
D3D11_INPUT_PER_VERTEX_DATA, // 定义输入数据类型为顶点数据
0 // 本例没有使用实例技术, 所以这里为 0
}
};
// 获取输入布局中元素个数
UINT numElements = ARRAYSIZE(layout);
// 声明一个输入布局对象 pVertexLayout 用于存放创建好的布局
ID3D11InputLayout* pVertexLayout;
// 调用 CreateInputLayout 创建输入布局
device->CreateInputLayout(
layout, // 上面定义的 D3D11_INPUT_ELEMENT_DESC 数组
numElements, // D3D11_INPUT_ElEMENT_DESC 数组的元素个数
pVSBlob->GetBufferPointer(), // 指向顶点着色器起始位置的指针
pVSBlob->GetBufferSize(), // 指向顶点着色器的所在内存大小
&pVertexLayout); // 返回生成的输入布局对象
// 设置生成的输入布局
immediateContext->IASetInputLayout(pVertexLayout);
// 第四步, 创建顶点缓存
// 用我们自己定义的 Vertex 结构创建三角形的三个顶点坐标
Vertex vertices[] =
{
XMFLOAT3(0.0f, 0.5f, 0.0f),
XMFLOAT3(0.5f, 0.0f, 0.0f),
XMFLOAT3(-0.5f, 0.0f, 0.0f)
};
// 填充 D3D11_BUFFER_DESC 结构, 这个结构是用来描述顶点缓存的属性
// 首先声明一个 D3D11_BUFFER_DESC 的对象 bd
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd)); // 进行清零操作
bd.Usage = D3D11_USAGE_DEFAULT; // 设置缓存的读写方式, 一般用默认方式
bd.ByteWidth = sizeof(Vertex) * 3; // 设置缓存区域的大小,
// 由于有三个顶点所以要乘以 3
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 将这个缓存区域绑定到顶点缓存
bd.CPUAccessFlags = 0; // CPU 访问标识符, 0 表示没有 CPU 访问
bd.MiscFlags = 0; // 其他项标识符, 0 表示不使用该项
// 声明一个数据用于初始化子资源
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData, sizeof(initData)); // 进行清零操作
initData.pSysMem = vertices; // 设置需要初始化的数据, 即顶点数组
// 声明一个 ID3D11Buffer 对象作为顶点缓存
ID3D11Buffer* vertexBuffer;
// 调用 CreateBuffer 创建顶点
device->CreateBuffer(&bd, &initData, &vertexBuffer);
UINT stride = sizeof(Vertex); // 获取 Vertex 的大小作为跨度
UINT offset = 0; // 设置偏移量为 0
// 设置顶点缓存
immediateContext->IASetVertexBuffers(
0, // 绑定到第一个输入槽
1, // 顶点缓存的个数, 这里为一个
&vertexBuffer, // 创建好的顶点缓存
&stride, // 跨度, 即顶点结构的大小
&offset); // 缓存第一个元素到所用元素的偏移量
// 指定图元类型, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST 表示图元为三角形
immediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return true;
}
void Cleanup()
{
// 释放指针
if (renderTargetView)
{
renderTargetView->Release();
}
if (immediateContext)
{
immediateContext->Release();
}
if (swapChain)
{
swapChain->Release();
}
if (device)
{
device->Release();
}
if (vertexShader)
{
vertexShader->Release();
}
if (pixelShader)
{
pixelShader->Release();
}
}
bool Display(float timeDelta)
{
if (device)
{
// 声明一个数组存放颜色信息, 4 个元素分别表示红, 绿, 蓝以及 alpha
float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };
// 清除渲染目标视图
immediateContext->ClearRenderTargetView(renderTargetView,
ClearColor);
// 把创建好的顶点着色器和像素着色器绑定到 immediateContext
immediateContext->VSSetShader(vertexShader, NULL, 0);
immediateContext->PSSetShader(pixelShader, NULL, 0);
// 绘制三角形, 第一个参数表示绘制 3 个点
// 第二个参数表示从第 0 个点开始绘制
immediateContext->Draw(3, 0);
swapChain->Present(0, 0);
}
return true;
}
//************* 框架函数编写结束 ***************
/*
* 回调函数
*/
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_KEYDOWN:
if (VK_ESCAPE == wParam)
{
::DestroyWindow(hwnd);
}
break;
}
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
/*
* 主函数 WinMain
*/
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPreInstance,
PSTR pCmdLine,
int nShowCmd)
{
// 初始化
// 上面声明的 4 个指针, 在这里作为参数传给 InitD3D 函数
if (!d3d::InitD3D(hInstance,
800,
600,
&renderTargetView,
&immediateContext,
&swapChain,
&device))
{
::MessageBox(0, L"InitD3D() - FAILED", 0, 0);
return 0;
}
if (!Setup())
{
::MessageBox(0, L"Setup() - FAILED", 0, 0);
return 0;
}
// 执行消息循环, 将函数 Display 的指针作为参数传递
d3d::EnterMsgLoop(Display);
Cleanup();
return 0;
}
运行结果:
在此特别感谢评论区 1 楼的 zzy717565 同学帮我找出了程序中存在的错误。错误地方在于 d3dTriangle.cpp 第 115 行代码的 DXGI_FORMAT_R32G32B32_FLOAT 被我写成了 DXGI_FORMAT_R32G32B32A32_FLOAT 。
项目工程:https://github.com/YLD10/VisualStudio_Projects/tree/master/DirectX11_3D/D3DTriangle