介绍
本文主要介绍如何使用 direct3D 11 绘制三角形。
pipeline 介绍
从 msdn 的文章,可以获得 direct3D 的 pipeline 如下图所示,对于本文只需要设置红框内的部分。
输入装配阶段
在这里需要将顶点坐标、顶点数据是使用什么格式保存的(float 还是 int;是几位的等)、想要顶点组成什么图像(比如传入3个点,你可能就是想画3个点或者一条线和1一个点或者一个三角形等)
顶点着色器阶段
着色器就是 gpu 执行的代码,gpu 的特点是计算单元的算力有线,但是每个 gpu 上有多个执行单元,就可以并行处理数据了。
所以着色器的代码一般不会很复杂,我理解着色器语法是 c/c++ 的简化版。
对于着色器代码,会对每个传入的点都运行一遍,且相互之间独立,这个就可以充分发挥 gpu 的高并发的特点。
顶点着色器阶段主要就是对点的位置进行一些几何的变化,如平移、旋转和缩放等
像素着色器阶段
像素着色器阶段就是通过传入的顶点位置,计算该位置应该显示什么颜色。
输出阶段
该阶段是整个 pipeline 的输出,你需要设置 pipeline 最后将处理的数据保存在哪个位置。
代码
重要函数解释与理解
上一篇文章也讲到
device 主要用于显示是否支持某些特性和分配资源
context 主要用于设置渲染状态、将资源绑定到 pipeline 和 发出渲染命令
本文通过 CreateBuffer 向 device 申请的资源,用于存储顶点数据。(即 cpu 中申请一块内存 new malloc)
通过 context 绑定的资源如下所述:
IASetVertexBuffers
向 input-Assembler 阶段设置 顶点缓存数据,即通过该接口将顶点数据保存到 gpu 显存中。此时,gpu只是有了一堆数据,但是不知道这堆数据是什么意思,应该怎么来使用。
IASetInputLayout
告诉 input-Assembler 阶段,每种缓存的数据应该如何来读取,如 DXGI_FORMAT_R32G32B32_FLOAT,表示每组数据有 3 个数,每个数为 4 字节,用 float 格式来解析该数据。
也会写入该数据供哪个变量来读取。
如 int add(int a, int b) ,需要告诉 gpu 你这个数据是传给 a 还是 b的。
shader 中函数写成这样 VSOut MyVs(float3 pos : POSITION),这里的 POSITION 就是 cpu 和 gpu 之间数据传输的标识。 pos 标识是 gpu 内部使用的
IASetPrimitiveTopology
告诉 input-Assembler ,这些点之间的拓扑结构是怎么样,如点、直线和三角形。
VSSetShader
设置 顶点着色器
着色器的编译方式,和 cpu 的代码也不一样,具体的编译方式可以参考这篇文章
PSSetShader
设置 像素着色器
OMSetRenderTargets
设置输出的渲染目标,即把处理后的数据保存在哪里。
RSSetViewports
这个函数是用来设置输出数据,显示在目标纹理的哪个位置,起点和宽度。
Draw
设置上述的配置后,通过该函数控制 pipeline 开始执行。
具体代码
void Graphics::DrawTriangle()
{
/************************************* 输入装配阶段 **************************************/
//创建顶点缓存
struct SimpleVertex
{
DirectX::XMFLOAT3 pos;
};
SimpleVertex vertices[] =
{
DirectX::XMFLOAT3(0.0f, 0.5f, 0.5f),
DirectX::XMFLOAT3(0.5f,-0.5f, 0.5f),
DirectX::XMFLOAT3(-0.5f,-0.5, 0.5f)
};
D3D11_BUFFER_DESC verticsDesc = {};
verticsDesc.ByteWidth = sizeof(vertices) * 3; // 字节数
// 将 usage 设为 D3D11_USAGE_IMMUTABLE D3D11_USAGE_DEFAULT 可行
verticsDesc.Usage = D3D11_USAGE_DEFAULT; // 资源的使用,gpu和cpu 的读写权限
verticsDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 标识如何将资源绑定到 pipeline
verticsDesc.CPUAccessFlags = 0; // CPU 的读写权限
verticsDesc.MiscFlags = 0;
verticsDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA resourceData = {};
resourceData.pSysMem = vertices;
resourceData.SysMemPitch = 0;
resourceData.SysMemSlicePitch = 0;
ID3D11Buffer* verticesBuffer;
m_pDevice->CreateBuffer(&verticsDesc, &resourceData, &verticesBuffer);
UINT strider = sizeof(SimpleVertex);
UINT offset = 0;
m_pContext->IASetVertexBuffers(
0, // start slot
1, // buffer 数量 (start slot ~ start slot + buffer number
&verticesBuffer,// 顶点缓存
&strider, // 每组数据的字节数
&offset); // 偏移量
// 输入数据解释
D3D11_INPUT_ELEMENT_DESC layout[] = {{
"POSITION", // shader 中的变量名
0,
DXGI_FORMAT_R32G32B32_FLOAT, // 顶点数据格式
0, // 代表顶点缓存数据通过哪个 slot 传给 GPU, input slot 数字,范围从0到15
0, // 偏移量,告诉 GPU 从哪个位置开始拿数据
D3D11_INPUT_PER_VERTEX_DATA , // 输入槽的数据类型
0
}};
// 图元拓扑结构
m_pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
/************************************* 顶点着色器阶段 **************************************/
ID3DBlob* pBlob = NULL;
D3DReadFileToBlob(L"HLSL/vs.cso", &pBlob);
ID3D11VertexShader* pVertexShader = NULL;
m_pDevice->CreateVertexShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pVertexShader);
m_pContext->VSSetShader(pVertexShader, nullptr, 0);
ID3D11InputLayout* inputLayout = NULL;
m_pDevice->CreateInputLayout(layout,
1,
pBlob->GetBufferPointer(), // 该 shader 有 layout 中定义的 SemanticName
pBlob->GetBufferSize(),
&inputLayout);
m_pContext->IASetInputLayout(inputLayout);
/************************************* 像素着色器阶段 **************************************/
D3DReadFileToBlob(L"HLSL/ps.cso", &pBlob);
ID3D11PixelShader* pPixelShader = NULL;
m_pDevice->CreatePixelShader(pBlob->GetBufferPointer(), pBlob->GetBufferSize(), nullptr, &pPixelShader);
m_pContext->PSSetShader(pPixelShader, nullptr, 0);
/************************************* 输出阶段 **************************************/
// 设置渲染目标
m_pContext->OMSetRenderTargets(1, &m_pRenderTargetView, NULL);
// 设置视口
D3D11_VIEWPORT viewPort = {};
viewPort.TopLeftX = 0;
viewPort.TopLeftY = 0;
viewPort.Width = 300;
viewPort.Height = 200;
viewPort.MinDepth = 0.0f;
viewPort.MaxDepth = 1.0f;
m_pContext->RSSetViewports(1, &viewPort);
// 开始绘制
m_pContext->Draw(3, 0);
}
vs.hlsl
struct VSOut
{
float4 pos : SV_Position;
};
VSOut MyVs(float3 pos : POSITION)
{
VSOut vsOut;
vsOut.pos = float4(pos.x, pos.y, pos.z, 1.0);
return vsOut;
}
ps.hlsl
float4 MyPs() : SV_Target
{
return float4(0.0f, 1.0f, 0.0f, 1.0f);
}