什么是纹理,简单理解叫贴图,比如现在一张1920X1080图片要显示在1920X1080的窗口上,那么图片像素与窗口一一对应简单的复制粘贴。如果图片大小与目标大小不一样时通过某种算法实现显示目标窗口上,这就叫纹理过滤。
纹理坐标 范围0到1,原点在左下角
使用d3d12窗口显示一张图片,如果用gdi+现实简单多了,调用一个函数就可以解决。
1. 读取图片信息 大小,像素深度BPP,d3d12所要的格式, 数据 。这里使用了 WIC接口,全称就是Windows Image Component(Windows图像组件)详情见官网wic接口。为了简化程序我们写类去处理读取图片信息代码:
class CWICIamge
{
public:
DXGI_FORMAT GetImageInfo(const wchar_t *pathImage, UINT& w, UINT& h, UINT& bpp, UINT& nPicRowPitch, BYTE**pbPicData);
private:
void WicLoadImage(const wchar_t * pathImage,ComPtr<IWICImagingFactory>& pIWICFactory, ComPtr<IWICBitmapDecoder>& pIWICDecoder);
void GetImagePixelFormat(ComPtr<IWICBitmapDecoder> pIWICDecoder,ComPtr<IWICBitmapFrameDecode>&pIWICFrame, WICPixelFormatGUID& wpf);
void GetBitmapSource(WICPixelFormatGUID wpf, GUID tgFormat, ComPtr<IWICImagingFactory>pIWICFactory, ComPtr<IWICBitmapFrameDecode>pIWICFrame, ComPtr<IWICBitmapSource>& pIBMP);
void GetBitmapInfo(GUID tgFormat,ComPtr<IWICImagingFactory> pIWICFactory, ComPtr<IWICBitmapSource> pIBMP, UINT& w, UINT& h, UINT& bpp);
bool GetTargetPixelFormat(const GUID* pSourceFormat, GUID* pTargetFormat, DXGI_FORMAT& dxForma);
};
简单调用GetImageInfo()就得到d3d12所需图片所有信息。
2. 编写Shader程序:Texture.hlsl
cbuffer SceneConstantBuffer : register(b0)
{
float4 offset;
float4 padding[60];
};
struct PSInput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
};
Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);
PSInput VSMain(float4 position : POSITION, float4 uv : TEXCOORD)
{
PSInput result;
result.position = position + offset;
result.uv = uv;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return g_texture.Sample(g_sampler, input.uv);
}
说明一下:
register(b0)表示: CBV描述符-常量缓冲区视图
register(t0)表示: CBV描述符-着色器资源视图
register(s0)表示: Sample采样器(sampler / 取样器)
struct PSInput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
};
顶点输入数据排布是:顶点坐标+纹理坐标
3.先了解一下 d3d12相关资源,视图描述符的具体类型:
渲染目标视图描述符(RTV) | 用于描述渲染目标视图的属性,例如关联的资源类型、资源格式等 |
深度模板视图描述符(DSV) | 用于描述深度模板视图的属性,包括关联的资源类型、资源格式、使用的深度模板格式等。 |
着色器资源视图描述符(SRV) | 用于描述着色器资源视图的属性,例如关联的资源类型、资源格式、资源在着色器中的访问方式等。 |
常量缓冲区视图描述(CBV) | 用于描述常量缓冲区视图的属性,包括关联的常量缓冲区的大小。 |
无序访问视图描述符(UAV) | 用于描述无序访问视图的属性,例如关联的资源类型、资源格式、在着色器中的访问方式等。 |
采样器描述符(Sampler) | 用于描述采样器的属性,例如过滤方式、边界模式、向导等。 |
4. D3D12中创建资源的三种方式(这里使用“提交方式”,简单点)
提交方式(CreateCommittedResource) | 为了兼容旧的,D3D12不推荐使用 |
定位方式(CreatePlacedResource) | 创建放置在特定堆中的资源。放置的资源是可用的最轻量资源对象,是创建和销毁速度最快的资源对象,推荐使用。 |
保留方式(CreateReservedResource) | 保留方式创建作为更高级的方法,目前不解 |
5. D3D12中堆的类型
D3D12_HEAP_TYPE_DEFAULT 默认堆 | GPU 可读写,CPU无法访问,通常上传堆对其进行填充资源。 |
D3D12_HEAP_TYPE_UPLOAD 上传堆 | 此堆类型最适合 CPU-write-once、GPU-read-once 数据 |
D3D12_HEAP_TYPE_READBACK 回读堆 | 此堆类型最适合 GPU-write-once、CPU 可读数据。 |
D3D12_HEAP_TYPE_CUSTOM 自定义堆 | 支持自定义堆 |
6. 要用d3d12显示一张图片需要相关资源和流程如图:
第一章显示“三角形”流程差不多,在其代码修改出来成了显示纹理因为其他流程都一样,只是多“加载图片”、“填充纹理”、“相连SRV-视图描述符”这三部份。“加载图片”前面上面讲过,“填充纹理”是这部份难重点(上面流程图编号:4)
4.1 使用 wic读取图片信息 应该没有问题
4.2 创建默认堆, 参数是图片格式、宽度、高度。它的最终目标是放图片数据资源,因它GPU 可读写,CPU无法访问,所以需要上传堆。
4.3 上传堆,图片数据先复制到上传堆,再由上传堆复制到默认堆上(这个操作由gpu来完成,CPU无法访问),这里借助DirectX-Headers的函数UpdateSubresources()来完成复杂的操作,参数:(默认堆,上传堆,图片数据)代码:
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = pImageData;
textureData.RowPitch = w * bpp / 8;
textureData.SlicePitch = textureData.RowPitch * h;
UpdateSubresources(m_commandList.Get(), m_texture.Get(), m_textureUploadHeap.Get(), 0, 0, 1, &textureData);
资源屏障(Resource Barrier)只有当图片数据完全复制到默认堆后默认堆数据才完整,让访问其数据数据才有意义,所以要设置资源屏障,借助DirectX-Headers让代码变得简单
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
通资源前后状态变化,默认堆资源由D3D12_RESOURCE_STATE_COPY_DEST变成D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE说明复制完成。这里有问题上传堆数据复制到默认堆什么时候执行?以上这些操作在命令列表上,加到命令列队列上去执行:
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
填充纹理数据的代码:
void CD3D12Texture::CopyImageDataCommitted(int w,int h,int bpp, BYTE* pImageData)
{
/// bpp为图片格式
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = pImageData;
textureData.RowPitch = w * bpp / 8;
textureData.SlicePitch = textureData.RowPitch * h;
UpdateSubresources(m_commandList.Get(), m_texture.Get(), m_textureUploadHeap.Get(), 0, 0, 1, &textureData);
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
ThrowIfFailed(m_commandList->Close());
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
}
5. SRV-视图描述符 ,先创建描述符堆,再创建SRV-视图描述符,关连图片数据资源默认堆,代码:
void CD3D12Texture::CreateShaderResource(DXGI_FORMAT dxFormat)
{
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 2; // srv +cbv
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&m_srvHeap)));
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = dxFormat;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
m_texture 图片数据资源默认堆
m_device->CreateShaderResourceView(m_texture.Get(), &srvDesc, m_srvHeap->GetCPUDescriptorHandleForHeapStart());
m_nSRVDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}
题外话: 看到这里,相信学习d3d12已有一段时间,严格来说还没有入门。只所以这样说是因为1.这两章显示的东西是2D平面,静态,2.还需要线性代数,学习线性代数以下教程比较精简推荐给大家变换 - LearnOpenGL CN (learnopengl-cn.github.io)
工程HelloCube代码已实现一个自动旋转的立方体 后面有时间会再写一篇
本章完整代码