DX11(五):索引缓冲区和常量缓冲区

05/31/2020

索引缓冲区(Index Buffer)

如果构建一个立方体,需要多少个三角形?一个面需要2个三角形,立方体有六个面,需要12个三角形,每个三角形有三个顶点,那么一共需要36个顶点坐标来构建顶点缓冲区。为了缓解顶点缓冲区的书写的难度,我们使用索引缓冲区来绘制

顶点缓冲区

顶点缓冲区现在来初始化立方体8个点的位置

// ******************
// 设置立方体顶点
//    5________ 6
//    /|      /|
//   /_|_____/ |
//  1|4|_ _ 2|_|7
//   | /     | /
//   |/______|/
//  0       3
VertexPosColor vertices[] =
{
	{ XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 0.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(1.0f, -1.0f, -1.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(-1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 0.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(1.0f, 1.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
	{ XMFLOAT3(1.0f, -1.0f, 1.0f), XMFLOAT4(0.0f, 1.0f, 1.0f, 1.0f) }
};

索引具体36个点的绘制顺序

// 索引数组 为了方便简写,但无法解决有36个点需要描绘
DWORD indices[] = {
	// 正面
	0, 1, 2,
	2, 3, 0,
	// 左面
	4, 5, 1,
	1, 0, 4,
	// 顶面
	1, 5, 6,
	6, 2, 1,
	// 背面
	7, 6, 5,
	5, 4, 7,
	// 右面
	3, 2, 6,
	6, 7, 3,
	// 底面
	4, 0, 3,
	3, 7, 4
};

创建索引缓冲区

// 设置索引缓冲区描述
D3D11_BUFFER_DESC ibd;
ZeroMemory(&ibd, sizeof(ibd));
ibd.Usage = D3D11_USAGE_IMMUTABLE; 			//不允许被改变
ibd.ByteWidth = sizeof indices; 			//空间大小 
ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;	//注意 INDEX_BUFFER/而不是Vertex Buffer
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = indices; 				//设置具体36个点的坐标
HR(m_pd3dDevice->CreateBuffer(&ibd, &InitData, m_pIndexBuffer.GetAddressOf())); 


// 输入装配阶段的索引缓冲区设置,设置每个索引占的字节数
m_pd3dImmediateContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0);

绘制索引缓冲区(DrawIndexed)

DrawIndex可以改变绘制的起始点的下标,不一定需要从数组的0开始绘制,也可以截取部分绘制。

代码描述

void ID3D11DeviceContext::DrawIndexed( 
    UINT IndexCount,            // 索引数目
    UINT StartIndexLocation,    // 起始索引位置
    INT BaseVertexLocation);    // 起始顶点位置
);
//从索引数组下标0开始,索引下标对应的值加上基础索引值,一共绘制IndexCount
m_pd3dImmediateContext->DrawIndexed(36, 0, 0); 

常量缓冲区

常量缓冲区常用来赋值给频繁更新的数据,比如变换矩阵或者纹理资源

描述并创建常量缓冲区

//描述
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC; 				//数据可改变
cbd.ByteWidth = sizeof(ConstantBuffer);			//必须是16的倍数,因为HLSL要求16字节对齐
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;	//允许常量缓冲区从CPU写入


// 新建常量缓冲区,不使用初始数据
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffer.GetAddressOf()));

更改常量缓冲区的值

方式1: map和unmap

//C++ 常量缓冲区的结构体
struct ConstantBuffer
{
	XMMATRIX world;
	XMMATRIX view;
	XMMATRIX proj;
};

m_CBuffer.world = XMMatrixIdentity();	// 单位矩阵的转置是它本身,世界坐标
m_CBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH( //转换成视图矩阵
		XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
		XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
		XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
	));
m_CBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f)); //透视投影矩阵


D3D11_MAPPED_SUBRESOURCE mappedData;
HR(m_pd3dImmediateContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData));
memcpy_s(mappedData.pData, sizeof(m_CBuffer), &m_CBuffer, sizeof(m_CBuffer));
m_pd3dImmediateContext->Unmap(m_pConstantBuffer.Get(), 0); //让指向的资源的指针无效并重新启用GPU对资源的访问权限
描述一下map 函数

在描述常量缓冲区时候,Usage是D3D11_USAGE_DYNAMIC, CPUAccessFlags 为D3D11_CPU_ACCESS_WRITE,允许常量缓冲区由CPU写入,先把显卡的数据映射map到CPU的的内存中,然后用memcpy_s更新数据,最后通过unmap解除占用内存空间。

HRESULT ID3D11DeviceContext::Map(
    ID3D11Resource* pResource,          		// [In]包含ID3D11Resource接口的资源对象	
    UINT Subresource,        					 // [In]缓冲区资源填0
    D3D11_MAP MapType,             				 // [In]D3D11_MAP枚举值,指定读写相关操作
    UINT MapFlags,           					 // [In]填0,CPU需要等待GPU使用完毕当前缓冲区
    D3D11_MAPPED_SUBRESOURCE *pMappedResource     // [Out]获取到的已经映射到缓冲区的内存
);
描述MapType的5种方式
  • D3D11_MAP_READ:映射到内存资源用于读取,在描述常量缓冲区时,需要对CPUAccessFlags绑定为D3D11_CPU_ACCESS_READ标签
  • D3D11_MAP_WRITE: 映射到内存资源用于写入,在描述常量缓冲区时,需要对CPUAccessFlags绑定为D3D11_CPU_ACCESS_WRITE标签
  • D3D11_MAP_READ_WRITE:映射到内存资源用于写入,在描述常量缓冲区时,需要对CPUAccessFlags绑定为D3D11_CPU_ACCESS_WRITE和READ两个标签
  • D3D11_MAP_WRITE_DISCARD:映射到内存资源用于写入,之间的资源数据将会被丢弃,必须绑定了D3D11_CPU_ACCESS_WRITE和D3D11_USAGE_DYNAMIC标签
  • D3D11_MAP_WRITE_NO_OVERWRITE:映射到内存的资源用于写入,但不能复写已经存在的资源。该枚举值只能用于顶点/索引缓冲区。该资源在创建的时候需要有D3D11_CPU_ACCESS_WRITE标签

方式二:updateSubresource

描述updateSubresource
void ID3D11DeviceContext::UpdateSubresource( 
    ID3D11Resource *pDstResource,   // [In]需要更新的常量缓冲区
    UINT DstSubresource,            // [In]缓冲区资源填0
    const D3D11_BOX *pDstBox,       // [In]忽略,填nullptr
    const void *pSrcData,           // [In]用于更新的数据源
    UINT SrcRowPitch,               // [In]忽略,填0
    UINT SrcDepthPitch);            // [In]忽略,填0

改方法用于D3D11_USAGE_DEFAULT或者D3D11_USAGE_STAGE方式创建的资源,并且不能用于深度模版缓冲区和多采样缓冲区。

updateResource性能问题

因为CPU和GPU都可以使用常量缓冲区,会发生冲突,它们形成竞争关系。当GPU占用常量缓冲区,然后CPU发出对同一缓冲区的updateResource操作。

  • 发生冲突,updateResource会拷贝源数据2次,第一次是CPU拷贝一份资源在临时的内存空间让GPU命令缓冲能够访问它,发生在该方法被返回之前。然后第二次由GPU从内存拷贝到不可映射的显存区域。第二次拷贝通常是异步发生的,因为这是在GPU命令缓冲被刷新后执行的。
  • 没有冲突,updateResource的行为取决于CPU认为怎样会更快:像第一步那样执行,或者直接从CPU拷贝到最终的显存资源位置。具体行为还是要依赖于系统。
使用updateResource
m_CBuffer.world = XMMatrixIdentity();	// 单位矩阵的转置是它本身
m_CBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
		XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
		XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
		XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
	));
m_CBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));

m_pd3dImmediateContext->UpdateSubresource(m_pConstantBuffer.Get(), 0, nullptr, &m_CBuffer, 0, 0); //更新缓冲区

绑定对应关系

HLSL 常量缓冲区结构体

//寄存器b0 放入一个常量缓冲区
cbuffer ConstantBuffer : register(b0)
{
    matrix g_World; 
    matrix g_View;  
    matrix g_Proj;  
}

C++ 常量缓冲区结构体

struct ConstantBuffer
{
	XMMATRIX world;
	XMMATRIX view;
	XMMATRIX proj;
};

设置对应关系

//寄存器b0 设置顶点着色器阶段设置常量缓冲区,对应b寄存器
m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());

总结

索引缓冲区

基本来说索引缓冲区与顶点缓冲区差不多

  • 设置索引数组 (indices)
  • 描述索引缓冲区(BUFFER_DESC)
  • 设置索引缓冲区的数据(SUBRESOURCE)
  • 创建索引缓冲区(CreateBuffer)
  • 设置装配阶段的索引缓冲区设置(IASetIndexedBuffer)

常量缓冲区

基本与顶点着色器和缓冲区设置差不多

  • 常量缓冲区的变量与C++的全局变量差不多,供着色器代码使用
  • 设置C++ ConstantBuffer结构体与HLSL ConstantBuffer结构体对应
  • 设置常量缓冲区的描述(BUFFER_DESC)
  • 创建常量缓冲区,先不赋值(没有实际数据SUBRESOURCE)(CreateBuffer)
  • 设置数据
  • 更新资源(map/unmap函数或者UpdateSubresource)
  • 设置常量缓冲区供给顶点着色器使用(VSSetConstantBuffers)

提示:渲染管线某一着色阶段设置常量缓冲区(*SSetConstantBuffers)

*:星号代表其他着色器

  • PSSetConstanBuffers 常量缓冲区绑定到像素着色器阶段
  • VSSetContantBuffers 常量缓冲区绑定到顶点着色器的阶段
  • 等等

后续补充

  • 索引缓冲区是为了更加方便确定三角形的顶点,其实是保存了顶点缓冲区的下标

渲染图解

D3DDevice创建缓冲区

Constant Buffer
Index Buffer

D3DDeviceContext 设置缓冲区

橘黄色的字体绑定索引缓冲区与常量缓冲区,顺序不是很重要
Bind index and constant buffer

X_Jun博客园的参考资料


版权声明:本文为weixin_44200074原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。