D3D12渲染技术之绘制网格

本文深入讲解Direct3D12的渲染技术,包括定义顶点和像素着色器,配置渲染管道,创建和绑定顶点缓冲区。学习目标涵盖Direct3D接口方法,输入布局描述,常量缓冲区数据绑定等,帮助理解Unity自定义渲染管线。
摘要由CSDN通过智能技术生成

在前几篇博客中,我们主要关注渲染管道的概念和数学方面。 反过来,从本篇博客开始重点介绍配置渲染管道,定义顶点和像素着色器以及将几何图形提交到渲染管道以进行绘制所需的Direct3D API接口和方法。 学习配置渲染管道,对于学习Unity的自定义渲染管线有很大帮助,它们的原理类似的,做到举一反三。

  • 学习目标:

1、掌握用于定义,存储和绘制几何数据的Direct3D接口方法。
2、学习如何编写基本顶点和像素着色器。
3、了解如何使用管道状态对象配置渲染管道。
4、了解如何创建常量缓冲区数据并将其绑定到管道。

  • 顶点和输入布局
    Direct3D中的顶点可以包含除空间位置之外的其他数据, 要创建自定义顶点格式,我们首先创建一个包含选择的顶点数据的结构。 例如,下面说明了两种不同的顶点格式; 一个由位置和颜色组成,第二个由位置,法线向量和两组2D纹理坐标组成。
struct Vertex1
{
  XMFLOAT3 Pos;
  XMFLOAT4 Color;
};

struct Vertex2
{
  XMFLOAT3 Pos;
  XMFLOAT3 Normal;
  XMFLOAT2 Tex0;
  XMFLOAT2 Tex1;
};

一旦我们定义了顶点结构,需要为Direct3D提供顶点结构的描述,此描述以输入布局描述的形式提供给Direct3D,由D3D12_INPUT_LAYOUT_DESC结构表示:

typedef struct D3D12_INPUT_LAYOUT_DESC
{
  const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
  UINT NumElements;
} D3D12_INPUT_LAYOUT_DESC;

输入布局描述仅仅是D3D12_INPUT_ELEMENT_DESC元素的数组,以及数组中元素的数量。
D3D12_INPUT_ELEMENT_DESC数组中的每个元素描述并对应顶点结构中的一个组件。 因此,如果顶点结构有两个组件,那么相应的D3D12_INPUT_ELEMENT_DESC数组将有两个元素。 D3D12_INPUT_ELEMENT_DESC结构定义为:

typedef struct D3D12_INPUT_ELEMENT_DESC
{
  LPCSTR SemanticName;
  UINT SemanticIndex;
  DXGI_FORMAT Format;
  UINT InputSlot;
  UINT AlignedByteOffset;
  D3D12_INPUT_CLASSIFICATION InputSlotClass;
  UINT InstanceDataStepRate;
} D3D12_INPUT_ELEMENT_DESC;

看看下图中我们定义的D3D12_INPUT_ELEMENT_DESC与Shader的关系如下所示:
这里写图片描述
我们在声明数据结构时也要考虑它们在内存中的布局,记得刚毕业时,面试题就有关于这方面的,在这里也是跟读者回顾一下,在下面的顶点结构中,元素Pos具有0字节的偏移,因为它的开始与顶点结构的开始位置是一致的; Normal元素有一个12字节的偏移量,因为我们必须跳过Pos的字节才能达到Normal; 元素Tex0有一个24字节的偏移量,因为我们需要跳过Pos和Normal的字节来到Tex0; 元素Tex1有一个32字节的偏移量,因为我们需要跳过Pos,Normal和Tex0的字节来到Tex1。

struct Vertex2
{
  XMFLOAT3 Pos;  // 0-byte offset
  XMFLOAT3 Normal; // 12-byte offset
  XMFLOAT2 Tex0;  // 24-byte offset
  XMFLOAT2 Tex1;  // 32-byte offset
};

相对应的输入描述如下所示:

D3D12_INPUT_ELEMENT_DESC desc1[] =
{
 {
  "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, 
    D3D12_INPUT_PER_VERTEX_DATA, 0},
  {
  "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, 
    D3D12_INPUT_PER_VERTEX_DATA, 0}
};

D3D12_INPUT_ELEMENT_DESC desc2[] =
{
  {
  "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, 
    D3D12_INPUT_PER_VERTEX_DATA, 0},
  {
  "NORMAL",  0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12,
    D3D12_INPUT_PER_VERTEX_DATA, 0},
  {
  "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,  0, 24, 
    D3D12_INPUT_PER_VERTEX_DATA, 0}
  {
  "TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT,  0, 32, 
    D3D12_INPUT_PER_VERTEX_DATA, 0}
};
  • 顶点缓冲区
    为了让GPU访问顶点数组,需要将它们放在称为缓冲区的GPU资源(ID3D12Resource)中, 我们调用一个缓冲区来存储顶点顶点缓冲区。 缓冲区比纹理更简单; 它们不是多维的,并且没有mipmap,过滤器或多重采样支持。 每当我们需要为GPU提供一系列数据元素(如顶点)时,我们都会使用缓冲区。
    我们通过填写描述缓冲区资源的D3D12_RESOURCE_DESC结构,然后
在 D3D12 中,渲染超大图片可以使用纹理切割技术,将大型纹理分成多个小型纹理,并将它们分别加载到显存中。然后,在渲染时,可以使用纹理数组来绘制超过功能级别最大纹理大小的图片。 下面是使用纹理切割渲染超大图片的代码示例: ```c++ // 定义纹理大小 const UINT textureWidth = 4096; const UINT textureHeight = 4096; // 加载大型纹理 ComPtr<ID3D12Resource> textureResource; ThrowIfFailed(CreateTextureFromFile(device, commandList, L"large_texture.png", textureResource.GetAddressOf())); // 创建小型纹理 const UINT subTextureWidth = 512; const UINT subTextureHeight = 512; for (UINT y = 0; y < textureHeight; y += subTextureHeight) { for (UINT x = 0; x < textureWidth; x += subTextureWidth) { // 计算子纹理的大小和位置 const UINT subTextureX = x; const UINT subTextureY = y; const UINT subTextureW = min(subTextureWidth, textureWidth - x); const UINT subTextureH = min(subTextureHeight, textureHeight - y); // 创建子纹理资源 ComPtr<ID3D12Resource> subTextureResource; ThrowIfFailed(device->CreateCommittedResource(..., D3D12_RESOURCE_STATE_COPY_DEST, ...)); // 将大型纹理的子区域复制到小型纹理中 D3D12_TEXTURE_COPY_LOCATION srcLocation = CD3DX12_TEXTURE_COPY_LOCATION(textureResource.Get(), subTextureX, subTextureY, 0); D3D12_TEXTURE_COPY_LOCATION dstLocation = CD3DX12_TEXTURE_COPY_LOCATION(subTextureResource.Get(), 0, 0, 0); commandList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr); // 添加小型纹理到纹理数组中 m_textureArray.push_back(subTextureResource); } } // 绑定纹理数组到着色器资源视图 CD3DX12_CPU_DESCRIPTOR_HANDLE srvHandle(m_srvDescriptorHeap->GetCPUDescriptorHandleForHeapStart()); for (UINT i = 0; i < m_textureArray.size(); i++) { device->CreateShaderResourceView(m_textureArray[i].Get(), nullptr, srvHandle); srvHandle.Offset(m_srvDescriptorSize); } // 绘制超大图片 const UINT numVertices = 4; const UINT vertexBufferSize = sizeof(Vertex) * numVertices; Vertex quadVertices[] = { { { -1.0f, 1.0f, 0.0f }, { 0.0f, 0.0f } }, { { 1.0f, 1.0f, 0.0f }, { 1.0f, 0.0f } }, { { -1.0f, -1.0f, 0.0f }, { 0.0f, 1.0f } }, { { 1.0f, -1.0f, 0.0f }, { 1.0f, 1.0f } } }; ComPtr<ID3D12Resource> vertexBuffer; ThrowIfFailed(device->CreateCommittedResource(..., D3D12_RESOURCE_STATE_COPY_DEST, ...)); ComPtr<ID3D12Resource> vertexUploadBuffer; ThrowIfFailed(device->CreateCommittedResource(..., D3D12_RESOURCE_STATE_GENERIC_READ, ...)); D3D12_SUBRESOURCE_DATA vertexData = {}; vertexData.pData = reinterpret_cast<BYTE*>(quadVertices); vertexData.RowPitch = vertexBufferSize; vertexData.SlicePitch = vertexBufferSize; UpdateSubresources(commandList, vertexBuffer.Get(), vertexUploadBuffer.Get(), 0, 0, 1, &vertexData); commandList->ResourceBarrier(..., D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER, ...); commandList->IASetVertexBuffers(...); commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); CD3DX12_GPU_DESCRIPTOR_HANDLE srvGpuHandle(m_srvDescriptorHeap->GetGPUDescriptorHandleForHeapStart()); commandList->SetGraphicsRootDescriptorTable(0, srvGpuHandle); commandList->DrawInstanced(numVertices, 1, 0, 0); // 清理资源 m_textureArray.clear(); ``` 在这个示例中,我们首先加载大型纹理,然后将其分成多个小型纹理。然后,我们将每个小型纹理复制到单独的资源中,并将它们添加到纹理数组中。最终,我们绑定纹理数组到着色器资源视图中,并使用它来绘制超过功能级别最大纹理大小的图片。 需要注意的是,纹理切割技术会增加额外的资源开销和绘制开销,因此应该尽可能地避免使用超过功能级别最大纹理大小的图片。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海洋_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值