前言

一个立方体有8个顶点,然而绘制一个立方体需要画12个三角形,如果按照前面的方法绘制的话,则需要提供36个顶点,而且这里面的顶点数据会重复4次甚至5次。这样的绘制方法会占用大量的内存空间。

接下来会讲另外一种绘制方法,可以只提供立方体的8个顶点数据,然后用一个索引数组来指代使用哪些顶点,按怎样的顺序绘制。

目标如下:

  1. 将顶点缓冲区和索引缓冲区绑定到输入装配阶段
  2. 将常量缓冲区绑定到顶点着色器以提供可用常量信息
  3. 根据索引绘制出第一个立方体

在阅读本章之前,先要了解下面的内容:

章节
02 顶点/像素着色器的创建、顶点缓冲区
HLSL中矩阵的内存布局和mul函数探讨

当然,我也建议你及早开始了解并上手图形调试器,以帮助寻找在CPU调试时无法察觉到的问题:

章节
Visual Studio图形调试器详细使用教程(除了编程捕获部分)

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

索引缓冲区(Index Buffer)

使用索引缓冲区进行替代指定顺序绘制,可以有效减少顶点缓冲区的占用空间,避免提供大量重复的顶点数据。

在使用索引缓冲区前,先讲初始化顶点数组,如下:

// ******************
// 设置立方体顶点
//    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) }
};

然后顶点缓冲区的创建和使用和之前一样:

// 设置顶点缓冲区描述
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_pVertexBuffer.GetAddressOf()));// 输入装配阶段的顶点缓冲区设置
UINT stride = sizeof(VertexPosColor);   // 跨越字节数
UINT offset = 0;                        // 起始偏移量m_pd3dImmediateContext->IASetVertexBuffers(0, 1, m_pVertexBuffer.GetAddressOf(), &stride, &offset);

现在索引数组的初始化如下:

// 索引数组
WORD 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;
ibd.CPUAccessFlags = 0;
// 新建索引缓冲区
InitData.pSysMem = indices;
HR(m_pd3dDevice->CreateBuffer(&ibd, &InitData, m_pIndexBuffer.GetAddressOf()));

ID3D11DeviceContext::IASetIndexBuffer方法--渲染管线输入装配阶段设置索引缓冲区

void ID3D11DeviceContext::IASetIndexBuffer( ID3D11Buffer *pIndexBuffer,     // [In]索引缓冲区DXGI_FORMAT Format,             // [In]数据格式UINT Offset);                   // [In]字节偏移量

在装配的时候你需要指定每个索引所占的字节数:

DXGI_FORMAT 字节数 索引范围
DXGI_FORMAT_R8_UINT 1 0-255
DXGI_FORMAT_R16_UINT 2 0-65535
DXGI_FORMAT_R32_UINT 4 0-2147483647

于是我们可以这样:

// 输入装配阶段的索引缓冲区设置
m_pd3dImmediateContext->IASetIndexBuffer(m_pIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);

使用16位的索引有利于节省空间,现阶段绝大多数模型的顶点数目都少于65535个。

常量缓冲区(Constant Buffer)

在HLSL中,常量缓冲区的变量类似于C++这边的全局常量,供着色器代码使用。下面是一个HLSL常量缓冲区示例:

cbuffer ConstantBuffer : register(b0)
{matrix g_World; matrix g_View;  matrix g_Proj;
}

cbuffer 用于声明一个常量缓冲区

matrix 等价于 float4x4,同样有vector等价于float4.其中D3D中的矩阵默认是行主矩阵形式,但是到了HLSL的matrix默认是列主矩阵形式。

register(b0) 指的是该常量缓冲区位于寄存器索引为0的缓冲区

而在C++应用层,常量缓冲区的对应结构体可以为:

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

目前资源有两种动态更新方式:

  1. 允许常量缓冲区从GPU写入,然后使用ID3D11DeviceContext::UpdateSubresource方法更新,虽然这种方法可能会比较快一点,但还需要拷贝临时资源到GPU中(更新是异步操作),等待资源结束占用才会复制到该资源中。
  2. 允许常量缓冲区从CPU写入,需要等待资源完成使用才能映射到内存中,此操作是阻塞行为。然后修改映射好的内存后解除占用,完成更新。

不仅常量缓冲区,一般的缓冲区和纹理资源更新都可以使用上述两种方式。其中第一种方式适合更新不频繁(隔一段时间更新),或者仅一次更新的数据。

由于常量缓冲区大多数需要频繁更新,因此后续都将主要使用DYNAMIC更新

D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DYNAMIC;
cbd.ByteWidth = sizeof(ConstantBuffer);
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
// 新建常量缓冲区,不使用初始数据
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffer.GetAddressOf()));

注意:在创建常量缓冲区时,描述参数ByteWidth必须为16的倍数,因为HLSL的常量缓冲区本身以及对它的读写操作需要严格按16字节对齐

现在来了解更新缓冲区的两种方法所需要用到的函数。

DYNAMIC更新

ID3D11DeviceContext::Map[1]函数--获取指向缓冲区中数据的指针并拒绝GPU对该缓冲区的访问

HRESULT ID3D11DeviceContext::Map(ID3D11Resource           *pResource,          // [In]包含ID3D11Resource接口的资源对象UINT                     Subresource,         // [In]缓冲区资源填0D3D11_MAP                MapType,             // [In]D3D11_MAP枚举值,指定读写相关操作UINT                     MapFlags,            // [In]填0,CPU需要等待GPU使用完毕当前缓冲区D3D11_MAPPED_SUBRESOURCE *pMappedResource     // [Out]获取到的已经映射到缓冲区的内存
);

D3D11_MAP枚举值类型的成员如下:

D3D11_MAP成员 含义
D3D11_MAP_READ 映射到内存的资源用于读取。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_READ标签
D3D11_MAP_WRITE 映射到内存的资源用于写入。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_WRITE标签
D3D11_MAP_READ_WRITE 映射到内存的资源用于读写。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_READ和D3D11_CPU_ACCESS_WRITE标签
D3D11_MAP_WRITE_DISCARD 映射到内存的资源用于写入,之前的资源数据将会被抛弃。该资源在创建的时候必须绑定了D3D11_CPU_ACCESS_WRITE和D3D11_USAGE_DYNAMIC标签
D3D11_MAP_WRITE_NO_OVERWRITE 映射到内存的资源用于写入,但不能复写已经存在的资源。该枚举值只能用于顶点/索引缓冲区。该资源在创建的时候需要有D3D11_CPU_ACCESS_WRITE标签,在Direct3D 11不能用于设置了D3D11_BIND_CONSTANT_BUFFER标签的资源,但在11.1后可以。具体可以查阅MSDN文档

最后映射出来的内存我们可以通过memcpy_s函数来更新。

ID3D11DeviceContext::Unmap函数--让指向资源的指针无效并重新启用GPU对该资源的访问权限

void ID3D11DeviceContext::Unmap(ID3D11Resource *pResource,      // [In]包含ID3D11Resource接口的资源对象UINT           Subresource      // [In]缓冲区资源填0
);

现在需要利用mBuffer结构体变量用于更新常量缓冲区,其中viewproj矩阵需要预先进行一次转置以抵消HLSL列主矩阵的转置,至于world矩阵已经是单位矩阵就不需要了:

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);

DEFAULT更新

ID3D11DeviceContext::UpdateSubresource方法[1]--更新缓冲区的数据

该方法可以用于更新允许GPU写入的资源:

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

该方法仅可以用于以D3D11_USAGE_DEFAULTD3D11_USAGE_STAGE方式创建的资源,并且不能用于深度模板缓冲区和支持多采样的缓冲区。

注意:如果用于更新着色器的常量缓冲区,不能对其中的数据部分更新,必须完整地进行数据的更新。

使用该方法只需要一句代码就可以更新

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);

ID3D11DeviceContext::*SSetConstantBuffers方法--渲染管线某一着色阶段设置常量缓冲区

这里的*可以是V, H, D, C, G, P六种可编程渲染管线阶段,函数的形参都基本一致。

现在更新了数据后,我们还需要给顶点着色阶段设置常量缓冲区供使用。

void ID3D11DeviceContext::VSSetConstantBuffers( UINT StartSlot,     // [In]放入缓冲区的起始索引,例如上面指定了b0,则这里应为0UINT NumBuffers,    // [In]设置的缓冲区数目ID3D11Buffer *const *ppConstantBuffers);    // [In]用于设置的缓冲区数组

绑定常量缓冲区的操作通常只需要调用一次即可:

m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());

HLSL代码

该例程所用的HLSL代码如下:

//Cube.hlslicbuffer ConstantBuffer : register(b0)
{matrix World; matrix View;  matrix Proj;
}struct VertexIn
{float3 posL : POSITION;float4 color : COLOR;
};struct VertexOut
{float4 posH : SV_POSITION;float4 color : COLOR;
};
// Cube_VS.hlsl
#include "Cube.hlsli"VertexOut VS(VertexIn vIn)
{VertexOut vOut;vOut.posH = mul(float4(vIn.posL, 1.0f), gWorld);  // mul 才是矩阵乘法, 运算符*要求操作对象为vOut.posH = mul(vOut.posH, gView);               // 行列数相等的两个矩阵,结果为vOut.posH = mul(vOut.posH, gProj);               // Cij = Aij * BijvOut.color = vIn.color;                         // 这里alpha通道的值默认为1.0return vOut;
}
// Cube_PS.hlsl
#include "Cube.hlsli"float4 PS(VertexOut pIn) : SV_Target
{return pIn.color;
}

注意:在HLSL中,矩阵乘法不能用*运算符,该运算符要求两个矩阵行列数相同,运算的结果也是一个同行列数的矩阵,运算过程为:Cij = Aij * Bij。应该使用mul函数进行替代。

GameApp::UpdateScene方法--逐帧更新数据

绘制之前我们得让立方体转起来,不然就只能看到立方体的正面。在这里我们让魔方同时绕X轴和Y轴旋转,修改世界矩阵即可:

void GameApp::UpdateScene(float dt)
{static float phi = 0.0f, theta = 0.0f;phi += 0.0001f, theta += 0.00015f;m_CBuffer.world = XMMatrixTranspose(XMMatrixRotationX(phi) * XMMatrixRotationY(theta));// 更新常量缓冲区,让立方体转起来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);
}

GameApp::DrawScene方法

ID3D11DeviceContext::DrawIndexed方法--根据顶点和索引缓冲区进行绘制

在输入装配阶段指定好了顶点缓冲区、索引缓冲区和原始拓补类型后,再绑定常量缓冲区到顶点着色阶段,最后就可以使用ID3D11DeviceContext::DrawIndexed方法来绘制:

void ID3D11DeviceContext::DrawIndexed( UINT IndexCount,            // 索引数目UINT StartIndexLocation,    // 起始索引位置INT BaseVertexLocation);    // 起始顶点位置

举个例子,如果按下述方式调用:

m_pd3dImmediateContext->DrawIndexed(6, 6, 4);

假设顶点缓冲区有12个顶点,索引缓冲区有12个索引,存放的值为{11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0},
上面的调用意味着顶点缓冲区我们从索引4作为我们的基准索引,然后索引缓冲区则是使用了索引6到11对应的索引值,即{5, 4, 3, 2, 1, 0},然后加上基准索引值4为最终取得的原来顶点缓冲区索引为{9, 8, 7, 6, 5, 4}的顶点。

现在我们要绘制12个三角形,构成立方体。GameApp::DrawScene方法实现如下:

void GameApp::DrawScene()
{assert(m_pd3dImmediateContext);assert(m_pSwapChain);static float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // RGBA = (0,0,0,255)m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&black));m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);// 绘制立方体m_pd3dImmediateContext->DrawIndexed(36, 0, 0);HR(m_pSwapChain->Present(0, 0));
}

效果如下:

练习题

粗体字部分为自定义题目

  1. 尝试只用5个顶点绘制四棱锥
  2. 尝试将四棱锥、立方体的顶点数据放在同一个顶点缓冲区,索引数据也放在同一个索引缓冲区,然后使用这两个缓冲区来绘制出这两个物体(让四棱锥在左边,立方体在右边,可以修改顶点数据,也可以使用变换矩阵)
  3. 尝试创建动态顶点缓冲区,然后通过MapUnmap的方式给顶点缓冲区写入顶点数据。

关于资源的更新,具体可以了解下面两个链接:

Efficient_Buffer_Management

how-to-use-updatesubresource-and-map-unmap

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

转载于:https://www.cnblogs.com/X-Jun/p/9032810.html

DirectX11 With Windows SDK--03 索引缓冲区、常量缓冲区相关推荐

  1. DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

    DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 原文:DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果 前言 上一 ...

  2. DirectX11 With Windows SDK--27 计算着色器:双调排序

    前言 上一章我们用一个比较简单的例子来尝试使用计算着色器,但是在看这一章内容之前,你还需要了解下面的内容: 章节 26 计算着色器:入门 深入理解与使用缓冲区资源(结构化缓冲区/有类型缓冲区) Vis ...

  3. DirectX11 With Windows SDK--07 添加光照与常用几何模型、光栅化状态

    DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 原文:DirectX11 With Windows SDK--07 添加光照与常用几何模型.光栅化状态 ...

  4. DirectX11 With Windows SDK--15 几何着色器初探

    DirectX11 With Windows SDK--15 几何着色器初探 原文:DirectX11 With Windows SDK--15 几何着色器初探 前言 从这一部分开始,感觉就像是踏入了 ...

  5. DirectX11 With Windows SDK--36 延迟渲染基础

    前言 随着图形硬件变得越来越通用和可编程化,采用实时3D图形渲染的应用程序已经开始探索传统渲染管线的替代方案,以避免其缺点.其中一项最流行的技术就是所谓的延迟渲染.这项技术主要是为了支持大量的动态灯光 ...

  6. DirectX11 With Windows SDK--32 SSAO(屏幕空间环境光遮蔽)

    前言 由于性能的限制,实时光照模型往往会忽略间接光因素(即场景中其他物体所反弹的光线).但在现实生活中,大部分光照其实是间接光.在第7章里面的光照方程里面引入了环境光项: C a = A L ⊗ m ...

  7. DirectX11 With Windows SDK--11 混合状态

    文章目录 前言 混合等式 混合等式 混合状态 混合运算符的设置 混合因子的设置 ID3D11Device::CreateBlendState方法--创建混合状态 ID3D11DeviceContext ...

  8. DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪

    DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪 原文:DirectX11 With Windows SDK--20 硬件实例化与视锥体裁剪 前言 这一章将了解如何 ...

  9. DirectX11 With Windows SDK--09 纹理映射与采样器状态

    DirectX11 With Windows SDK--09 纹理映射与采样器状态 原文:DirectX11 With Windows SDK--09 纹理映射与采样器状态 前言 在之前的Direct ...

最新文章

  1. 滴滴进入寒冬期,将裁员2000人
  2. 考研国家线罕见大幅上涨,12个学科涨幅10分以上,超300万人将落榜
  3. (转)spring aop(下)
  4. [云炬创业学笔记]第三章商业创意的发掘与评估测试4
  5. lua os.date函数定义和示例
  6. Powershell常用命令
  7. mysql 查询语句_SQL语言mysql基础查询语句
  8. Linux 普通用户和超级用户的切换
  9. NVIDIA 发布 60 余项 CUDA-X 库更新,加速量子计算和 6G 研究等
  10. cad填充图案乱理石_CAD图案填充应该这么操作!简单又高效!!!1分钟就能学会...
  11. c++容器(vector|map)中使用函数指针
  12. 开发基于大数据平台的搜索引擎
  13. linux服务器如何进入图形界面,linux进入图形界面的方法
  14. 计算机桌面任务栏过宽怎么处理,任务栏变宽了怎么办 还原变宽任务栏的方法【图文教程】...
  15. 关于web的重定向,js实现重定向的方法
  16. 【Buzz】简介及第一个案例
  17. Eclipse快捷键设置和Eclipse中的常用快捷键
  18. Fabric CA 用户指南
  19. python中dic.get用法
  20. java中考勤管理_JAVA人事员工考勤管理(含论文)源码

热门文章

  1. wpf 网易云歌词_网易云音乐粉丝半年涨500万,隔壁老樊为何成今年乐坛最大黑马 | 案例池...
  2. 检测到无效的异常处理程序例程。_异常控制流(1):异常概述和基本类型
  3. textview 加粗_Android 改变 TextView 内局部样式
  4. 猜数游戏c语言编程while,【游戏编程】猜数字游戏(C语言)
  5. linux php pdo dblib,PDO_DBLIB (MSSQL) on Ubuntu Server
  6. 无法识别设备powerstate_win7系统无法识别usb设备如何解决 电脑无法识别usb设备解决步骤【图文】...
  7. 三维计算机视觉(二)--点云滤波
  8. python性能解决的事_Python程序的性能分析方法
  9. 将汉字转换成笔画代码_0基础学习五笔输入法之汉字的拆分
  10. 【李宏毅机器学习】04:梯度下降Gradient Descent