05/29/2020
06/02/2020 后续补充-练习题
绘制一个三角形
//引用库文件来编译hlsl文件
pragma comment(lib,"D3DCompiler.lib") //着色器编译器
编译着色器文件(.hlsl的文件)
运行期间编译着色器,生成字节码
HRESULT D3DCompileFromFile(
LPCWSTR pFileName,
CONST D3D_SHADER_MACRO* pDefines,
ID3DInclude* pInclude, //是否允许hlsl文件添加include
LPCSTR pEntrypoint, //入口函数名,比如"VS","PS","CS",表示
LPCSTR pTarget, //使用着色器模型,比如"vs_5_)"
UINT Flags1,
UINT Flags2,
ID3DBlob** ppCode, //获得着色器二进制块
ID3DBlob** ppErrorMsgs //错误信息的二级制块
);
- 入口函数名与hlsl文件中的着色器名字必须保持一致
//Triangle.hlsl
VertexOut VS(VertexIn vIn){...} // 函数名与入口名字保持一致
- 着色器模型:如果显卡支持特性等级是11.0,使用Shader Model 5.0
- ppCode 表示返回的二级制文件
//运行期编译.hlsl文件
ID3DBlob** ppBlobOut; //返回的二进制文件
ID3DBlob* errorBlob;//错误的Blob
hr = D3DCompileFromFile("Triangle_VS.hlsl",nullptr,D3D_COMPILE_STANDARD_FILE_INCLUDE,
"VS",dwShaderFlags,0,ppBlobOut,&errorBlob); //获得一个编译好的着色器文件(.cso)
编译器产生对象,并在运行期加载(.cso文件)
visual studio 2017 自带hlsl的编译器,可以在编译的时候生成cso文件,然后再运行期加载
//运行期加载函数
HRESULT = S_OK;
//传递一个编译好的hlsl文件,文件名以(.cso)结尾
ID3DBlob** ppBlobOut; //返回的二进制文件
D3DReadFileBlob(cosFileNameInOut,ppBlobOut) == S_OK;
Device设备
创建着色器
HRESULT ID3D11Device::CreateVertexShader(
const void *pShaderBytecode, //着色器字节码
SIZE_T BytecodeLength, //字节码长度
ID3D11ClassLinkage* pClassLinkage, //
ID3D11VertexShader **ppVertexShader //获取顶点着色器
);
ComPtr<ID3DBlob> blob;
ComPtr<ID3D11VertexBuffer> mVertexShader;
md3dDevice->CreateVertexShader(blob->GetBufferPointer(),blob->GetBufferSize(),
nullptr,mVertexShader.GetAddressOf());
绑定输入布局
在HLSL中,用于输入结构体为:
//着色器文件HLSL
struct Vertexin
{
float3 pos:POSITION;
float4 color:COLOR;
};
#include <directxmath.h> //XMFLOAT3 数学库
//C++文件中
struct VertexPosColor
{
DirectX::XMFLOAT3 pos;
DirectX::XMFLOAT4 colr;
static const D3D11_INPUT_ELEMENT_DESC inputLayer[2]; //静态资源,可以提前赋值
};
如何建立对应关系
顶点缓冲区是二进制文件(Blob),为了建立C++结构体与HLSL结构体的对应关系,需要使用ID3D11InputLayer
- 输入布局来描述每一个成员的用途,语义,大小等信息
- C++的static const D3D11_INPUT_ELEMENT_DESC inputLayer[2]不属于结构体的成员,是为了方便描述布局关系的
描述输入布局
typedef struct D3D11_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName; //语义名
UINT SemanticIndex; //语义索引
DXGI_FORMAT Format; //数据格式
UINT InputSlot; //输入槽索引(0-15)
UIINT AlignedByteOffset;//初始位置
D3D11_INPUT_CLASSIFICATION InputSlotClass; //输入类型
UINT InstanceDataStepRate; //忽略
} D3D11_INPUT_ELEMENT_DESC;
inputLayer[0] =
{
{
"POSITION", //看一下HLSL结构体
0,
DXGI_FORMAT_R32G32B32_FLOAT, //数据存储方式,3个Float的类型值
0,
0,
D3D11_INPUT_PER_VERTEX_DATA,
0
},
}
inputLayer[1] =
{
{
"COLOR",
0,
DXGI_FORMAT_R32G32B32A32_FLOAT, //4个Float的类型
0,
12, //初始位置 字节偏移量
D3D11_INPUT_PER_VERTEX_DATA,
0
},
}
创建输入布局 CreateInputLayer
HRESULT ID3D11Device::CreateInputLayer(
const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs, //输入布局描述
UINT NumElements, //上述数组元素个数,2个
const void* pShaderBytecodeWithInputSignature, //着色器字节码
ID3D11InputLayer** ppInputLayout //获取输入布局
);
顶点缓冲区(Vertex Buffer)
- 顶点缓冲区把顶点数组以ID3D11Buffer的形式提供给输入装配阶段,即需要把顶点数组存储到顶点缓冲区
创建顶点缓冲区
描述缓冲区
typedef struct D3D11_BUFFER_DESC
{
UINT ByteWidth; //数据字节数
D3D11_USAGE Usage; //CPU和GPU读写权限
UINT BindFlags; //缓冲区类型标志
UINT CPUAccessFlags; //CPU读写权限指定
UINT MiscFlags; //忽略
UINT StructureByteStride; //忽略
};
//初始化三角形三个点的位置和颜色,顺时针选择点
VertexPosColor vertices[] =
{
{XMFLOAT3(0.0f,0.5f,0.5f),XMFLOAT4(0.0f,1.0f,0.0f,1.0f)}, //上顶点,颜色绿色 不透明
{XMFLOAT3(0.5f,-0.5f,0.5f),XMFLOAT4(0.0f,0.0f,1.0f,1.0f)}, //右顶点,颜色蓝色 不透明
{XMFLOAT3(-0.5f,-0.5f,0.5f),XMFLOAT4(1.0f,1.0f,0.0f,1.0f)}, //左顶点,颜色红色 不透明
};
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd,sizeof(vbd));
vbd.ByteUsage = D3D11_USAGE_IMMUTABLE; //GPU只读,CPU不可写也不可读
vbd.ByteWidth = sizeof(vertices);
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUACCESSFlags = 0;
指定初始化数据
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData,sizeof(initData));
initData.pSysMem = vertices;
创建缓冲区
将顶点数据以缓冲区的形式提供给输入装配阶段
//描述顶点缓冲区
//指定数据
//获得顶点缓冲区
d3dDevice->CreateVertexBuffer(&vbd,&initData,mVertexShader->GetAddressOf());
Device Context阶段
装配阶段设置缓冲区,即布置到渲染管道
注意: 类似给渲染管线绑定资源的一切方法,即Set方法,在绑定之后就会一直生效,而不是说仅能够使用一次。所以,以后如果你需要用别的特效去绘制当前物体,就要重新绑定好渲染管线所需要的一切资源。
void ID3D11DeviceContext::IASetVertexBuffers(
UINT StartSlot, //输入槽索引
UINT NumBuffers, //缓冲区数目
ID3D11Buffer *const *ppVertexBuffers, //指向缓冲区数组的指针
const UINT* pStrides, //一个数组,规定了对所有缓冲区每次读取的字节数分别是多少
const UINT* pOffsets //数组,规定了对所有缓冲区的初始字节偏移量
);
UINT stride = sizeof(VertexPosColor);
UINT offset = 0;
d3dImmediateDevice->IASetVertexBuffers(0,1,mVertexBuffer.GetAddressOf(),&stride,&offset);
只要绘制的内容不变,只需要设置一次,如果绘制不同的内容或者效果,需要重新设置
输入装配阶段设置图元类型
void ID3D11DeviceContext::IASetPrimitivetopology(D3D11_PRIMITIVE_TOPOLOGY topology);
给渲染管线某一个着色器阶段设置对应的着色器
void ID3D11DeviceContext::VSSetShader(
ID3D11VertexShader* pVertexShader, //着色器
ID3D11ClassInstance* const*ppClassInstance,
UINT NumClassInstance
);
总结
创建着色器
- 编译顶点着色器(D3DCompileFromFile)
- 创建顶点着色器(CreateVetexShader)
- 建立C++与HLSL中的对应关系,又叫创建并绑定顶点布局(CreateInputLayout)
- 编译像素着色器,ID3DBlob释放掉之前顶点着色器,在获取新的字节码(D3DCompileFromFile)
- 创建像素着色器(CreatePixelShader)
创建资源
- 创建顶点缓冲区 - 将顶点数据以缓冲区的形式提供给输入装配阶段
- 设置三角性三个顶点坐标(vertices[3])
- 描述缓冲区: 设置内存大小,sizeof(vertices); D3D11_BUFFER_DESC(Buffer Description)
- 指定要用的初始化数据vertices,即三角形三个顶点的实际数据;D3D11_SUBRESOURCE_DATA(子资源数据)
- 最后创建缓冲区(CreateBuffer)
- 渲染管线输入装配阶段设置顶点缓冲区 (IASetVertexBuffers)
- 输入装配装配阶段设置图元类型 (IASetPrimitiveTopology)
- 输入装配阶段设置输入布局 (IASetInputLayout)
- 给渲染管线某一个着色器阶段设置对应的着色器 (VSSetShader,PSSetShader)
- 绘图 (Draw/DrawIndexed)
XMFloat3类
DirectX命名空间下的一个结构体,一个很简单向量类
struct XMFLOAT3
{
float x;
float y;
float z;
XMFLOAT3() = default;
XMFLOAT3(const XMFLOAT3&) = default;
XMFLOAT3& operator=(const XMFLOAT3&) = default;
XMFLOAT3(XMFLOAT3&&) = default;
XMFLOAT3& operator=(XMFLOAT3&&) = default;
XM_CONSTEXPR XMFLOAT3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
explicit XMFLOAT3(_In_reads_(3) const float *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]) {}
};
跨平台的考量
//可以替换XMFLoat3
struct Vector3 {
float x;
float y;
float z;
Vector3(float x1,float y1,float z1):x(x1),y(y1),z(z1) {}
};
后续补充
画一个矩形用六个点
顺时针选点画图
// V0,V1,V2 第一个三角形
// V1,V3,V2 第二个三角形
// V0 V1
// ---------------------
// | |
// | |
// | |
// ---------------------
// V2 V3
VertexPosColor vertices[] =
{
{ XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
//图元类型为 Triangle List
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
画一个矩形用四个点
// V0 V1
// ---------------------
// | |
// | |
// | |
// ---------------------
// V2 V3
VertexPosColor vertices[] =
{
{ XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
//需要更改图元类型为Triangle strip
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
两个顶点缓冲区分别绘制不同的物体
两个顶点缓冲区
ComPtr<ID3D11Buffer> m_pVertexBuffer1; // 顶点缓冲区1
ComPtr<ID3D11Buffer> m_pVertexBuffer2; // 顶点缓冲区2
顶点缓冲区存储不同的坐标
// 矩形6点坐标
VertexPosColor vertices[] =
{
{ XMFLOAT3(-0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
{ XMFLOAT3(-0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
// 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices; //指向数组第一个元素
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer1.GetAddressOf()));
// 设置三角形顶点
VertexPosColor vertices2[] =
{
{ XMFLOAT3(0.5f, 0.5f, 0.5f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
{ XMFLOAT3(0.8f, 0.5f, 0.5f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
{ XMFLOAT3(0.5f, -0.5f, 0.5f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }
};
// 设置顶点缓冲区描述,可以共享之前的顶点描述,只需要更改顶点占用空间与实际数据
vbd.ByteWidth = sizeof vertices2;
InitData.pSysMem = vertices2;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffer2.GetAddressOf()));
后期将会使用vector容器存储顶点数据,而不是普通的数组。
注意点
因为渲染管线中各个部分的设置方法一经调用就会立即生效。然而如果需要绘制不同的内容或者效果,则需要在绘制前给渲染管线绑定好各种所需的资源
例子1:设置顶点缓冲区方法 IASetVertexBuffer
//输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosColor); // 跨越字节数
UINT offset = 0; // 起始偏移量
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer1.GetAddressOf(), &stride, &offset);
m_pd3dImmediateContext->Draw(6, 0); //由于立即生效,所以需要再下次设置之前,画出矩阵
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer2.GetAddressOf(), &stride, &offset);
m_pd3dImmediateContext->Draw(3, 0);
例子2:图元类型 IASetPrimitiveTopology
//新设置图元类型 Line Strip
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP);
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer1.GetAddressOf(), &stride, &offset);
m_pd3dImmediateContext->Draw(6, 0);
m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer2.GetAddressOf(), &stride, &offset);
m_pd3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); //Triangle List, 放在Draw之前
m_pd3dImmediateContext->Draw(3, 0);
结果:
渲染过程(图解)
现在用到的渲染管道不多,主要是输入装配阶段,顶点着色器阶段,像素着色器阶段
D3DDevice 创建
先编译hlsl文件后创建着色器,顶点布局,顶点缓冲区,创建顺序无所谓
D3DDeviceContext
绑定或者设置上面创建好的着色器,输入布局,缓冲区到渲染管道的不同阶段:
Introduction to Game Programming with DirectX 11
GitHub源码链接(感谢X_Jun)