目录
1、前言
这一次距上一篇教程又隔了有一段时间了,如果你看了之前的教程的话,马上就会想到悲催的我是不是又遇到了什么麻烦?应该说你能这样想那么恭喜你,你猜对了,这次搞得我人仰马翻的是DDS纹理。其实在我之前的教程中乐观的估计了WIC库,其实这个库根本就不能用来加载DDS型的纹理。当然教程到这里,我已经被纹理问题搞翻两回了,还是感慨于基础没有打牢,所以我建议各位,除了跟我继续学习D3D12本身编程的一些知识外,务必要打好其他基础。这没有什么捷径,就是多看书,多读资料,多看代码。
说起DDS其实这许多年来,它一直像D3D的地下恋人一样,二人既不能光明正大的结合,又不肯彻底分离,就这样一直如影随形的不离不弃。因为在正式的引擎中,比如Unity3D或Unreal中都有它的替代品。当然这些引擎都可以方便的导入DDS,但并不作为主流推荐的格式。
言归正传,本章我们要学习下利用D3D12扩展封装中的DDSTextureLoad函数加载DDS型纹理以及一个重要的概念捆绑包。本章的目标就是实现一个简单的天空盒,并且有一个星球在旋转的场景,效果如下:
碍于我没有找到合适的纹理,画面在分辨率上有问题,当然这不是程序的问题,基本都是美工需要搞定的事情,我们关注的还是程序。
其实在整个准备这些教程示例代码的过程中,我深深感觉到一个非常麻烦的事情,那就是模型数据,因为D3D接口本质上只是支持渲染的接口,所以它没有关于模型数据加载转换等的支持功能,所以在编写例子代码时,就会感觉很麻烦,比如这章教程中为了画一个在空间中旋转的球体,光找模型就花费了很长时间,因为基本没有简单的模型可以供我使用,也没有简单的方法可以方便的加载模型。所以本章例子中,我只能先找到了一个不太合适的球体模型,天空盒子的纹理都是通过古老的DXTex工具和VS自身的编辑功能制作的,效果就不敢恭维了,当然这对我们讲D3D12的基础来说足够了。未来考虑讲解一下assimp和FBX SDK导入模型的方法,因为后面要讲解3D蒙皮动画的话,必须要导入带动画数据的3D模型,就需要这些库来帮忙了。
2、加载DDS
DDS是一种图片格式,是DirectDraw Surface的缩写,它是DirectX纹理压缩(DirectX Texture Compression,简称DXTC)的产物。由NVIDIA公司开发。大部分3D游戏引擎都可以使用DDS格式的图片用作贴图,也可以制作法线贴图。通过安装DDS插件后可以在PhotoShop中打开。
在这里再次纠正一下之前教程中说的WIC库其实只能加载常见的图片格式,而DDS是专为DX而生。二者的应用场景是不同的,所以使用上也是有很大差别的。
目前DDS的加载代码有很多版本,目前支持DX12的版本我就见到了3个之多,而且其加载代码,尤其是解析文件的部分,跟创建纹理资源的代码纠缠在一起,读起来很是费劲。很有意思的就是它的很多我认为是格式信息的内容却需要复杂的计算来得到,比如我们之前熟悉的BPP参数,就需要一大段代码来分析,我是始终没看懂这是为啥,难道一个文件头中就不能简单的存储一下吗?当然它的主要功用就是能够充分利用现代显卡的高效图片压缩技术,可以支持高压缩比的纹理图片,这对于3D场景渲染来说是非常重要的一个支撑功能。如我们之前所说,现代的3D应用中,为了高质量的渲染,纹理的尺寸和分辨率已经是惊人的提高了,所以在存储传输加载等方面就会耗费很多时间,为了提高效率,在DDS中就加入了很多高压缩比的特殊格式,同时这些压缩和解压工作可以直接在GPU上完成,其性能也是很高的,所以这样就很好的解决了空间和时间之间的矛盾。
目前关于DDS文件的生成和加载的全部完整源代码已经完整的发布在GitHub上,地址在:https://github.com/Microsoft/DirectXTex,大家可以下载学习。通过这个库的源代码大家可以在未来自己封装引擎工具的时候,方便的生成DDS文件,或者自定义格式的等价文件。我们本章教程中使用的DDS加载工具代码就来自于这个库的DDSTextureLoader12.h和DDSTextureLoader12.cpp文件。
大家要注意的是在DX12的示例源代码中也有类似的支持D3D12的DDSTextureLoader代码和文件,但是它是和DX12例子中的代码紧密捆绑在一起的,基本没法单独使用,也不利于学习,所以我推荐大家使用这里说的DDSTextureLoader12的版本。
但是这个版本的封装也很让人费解,它的接口是返回一个可以装载整个DDS纹理的隐式默认堆资源指针和待加载上传的DDS图片数据,需要你自己去完成“两个Copy动作”。这样一来我实在看不出它为什么还需要创建一个隐式默认堆上的资源,在这里推荐有能力的同学可以改造这里的封装,直接返回一个用于创建纹理资源的结构体指针(可以作为输入输出参数)和DDS图片数据即可,至于要创建怎样的默认堆就交给程序去决定,甚至功力深厚的同学可以直接利用Builder设计模式封装一下,同时还可以考虑兼容WIC库,两全其美。
ok,思路性的东西就不介绍太多了,毕竟我们还是要回到D3D12本身的学习上来。
要使用DDSTextureLoader12工具库,首先就需要将它的源代码复制到本地,因为这个库可以独立使用,所以仅需要复制其头文件和CPP文件即可。建议你将它们和你的D3Dx12.h文件放在一起,作为扩展的D3D12工具库来使用。当然不要忘记了在你的项目中包含这两个文件,以免遇到编译链接错误问题。
接着就是在代码中包含它:#include "..\WindowsCommons\DDSTextureLoader12.h",注意我将它放在了与D3Dx12.h相同的文件夹中。
接着就是统一使用using namespace DirectX; 目前我们使用的DirectXMath库、D3Dx12、DDSTextureLoader12都被放在了DirectX这个统一的名字空间中。因为我们的例子代码还不会和他们冲突,同时也为了调用编写上的方便,我们就直接使用这个名字空间了。在正式的项目中建议保留这个名字空间。
要加载文件中的DDS纹理的话,就需要像下面这样来调用了:
std::unique_ptr<uint8_t[]> ddsData;
std::vector<D3D12_SUBRESOURCE_DATA> subresources;
DDS_ALPHA_MODE emAlphaMode = DDS_ALPHA_MODE_UNKNOWN;
bool bIsCube = false;
TCHAR pszSkyboxTextureFile[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Texture\\Texture1.dds");
//HRESULT DirectX::LoadDDSTextureFromFile(
// ID3D12Device* d3dDevice,
// const wchar_t* fileName,
// ID3D12Resource** texture,
// std::unique_ptr<uint8_t[]>& ddsData,
// std::vector<D3D12_SUBRESOURCE_DATA>& subresources,
// size_t maxsize,
// DDS_ALPHA_MODE* alphaMode,
// bool* isCubeMap);
ID3D12Resource* pIResSkyBox = nullptr;
GRS_THROW_IF_FAILED(LoadDDSTextureFromFile(
pID3DDevice.Get()
, pszSkyboxTextureFile
, &pIResSkyBox
, ddsData
, subresources
, SIZE_MAX
, &emAlphaMode
, &bIsCube));
//上面函数加载的纹理在隐式默认堆上,两个Copy动作需要我们自己完成
pITxtSkybox.Attach(pIResSkyBox);
这个函数的调用很简单,它的参数借助使用了STL模版库中的常用的模版类型,实质就是一个二进制数据缓冲和一个子资源数据的结构体数组。
在这里补充说明一下我们这里为什么使用DDS文件,因为我们需要加载一个天空盒子的立方体纹理,因为这类纹理中一般包含六个面的纹理存储在统一的一个DDS文件中是比较方便的,否则就需要我们加载六副图片,然后再作为一个立方体纹理的六个子资源上传加载,代码上的复杂度是很高的,同时如果考虑还有Mipmap效果的话就更复杂了,所以使用DDS格式的Cube Map像这里这样使用一个函数载入是比较方便的。至于如何生成这个DDS,那就是美工考虑的事情了,我们就不过多介绍了。前面的关于DDS的介绍中已经说的很清楚了。
加载完了以后,就需要我们自己来完成两个Copy动作了,首先我们需要创建上传堆:
//获取Skybox的资源大小,并创建上传堆
n64szUploadBufSkybox = GetRequiredIntermediateSize(pITxtSkybox.Get(), 0, static_cast<UINT>(subresources.size()));
stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64szUploadBufSkybox, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHpSkybox)));
然后我们就利用D3Dx12中的工具函数来一次性的完成“两个Copy动作”(希望你看过前面的教程,并知道我在说什么):
UpdateSubresources(pICmdListDirect.Get()
, pITxtSkybox.Get()
, pITxtUpSkybox.Get()
, 0
, 0
, static_cast<UINT>(subresources.size())
, subresources.data());
pICmdListDirect->ResourceBarrier(1
, &CD3DX12_RESOURCE_BARRIER::Transition(pITxtSkybox.Get()
, D3D12_RESOURCE_STATE_COPY_DEST
, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
上面的代码就让我们非常清楚的认识到了UpdateSubresources函数的威力。当然它的基本实现思路就是循环套循环来完成第一个Copy动作,然后就是使用命令列表来完成第二个Copy动作,建议你好好读下它的源代码,当然比较难理解的是第一个Copy动作,而第二个Copy动作就没什么好稀奇的了。其实我们之前加载普通的纹理的复制动作代码可以看做是这个函数的简化版本(其实我就是从它的源代码复制粘贴过来然后改造了一下,方便大家学习和理解)。
接着就是执行下命令列表,并等待第二个Copy动作完成,这样纹理就加载到了默认堆上,这个代码我就不再粘贴了。
这里再次说明一下,我们目前主要集中精力在D3D12接口本身的学习上,所以关于天空盒的原理,DDS纹理的等的其他3D图形学方面的基础知识我默认你是知道并明白的,本教程中就不再展开篇幅去给大家讲解基础知识了。后面看我的时间和精力,如果充裕我再写写其他的文章再给大家详细讲解下。
3、捆绑包(Bundles)
在之前的教程中,我们在讲解渲染管线状态对象(简称PSO)时提到过根签名+PSO实质上可以理解为完整描述了一个“渲染管线函数体”的静态结构。按我们一般的概念,函数是需要被执行的,执行的时候我们是需要传递参数的,等价的,如果按照我们之前的隐喻将渲染管线看做一个Shader代码组成的“大函数”来说,调用它来执行渲染的话,我们就需要传入网格数据、纹理数据、设置状态等,然后执行它并等待完成渲染,同时整个过程的传参调用的代码,都是放在渲染循环中反复进行的。如之前例子中的:
pICommandList->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get() };
pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//设置SRV
pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandle(pISRVHeap->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pICommandList->SetGraphicsRootDescriptorTable(1, stGPUCBVHandle);
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
//设置Sample
pICommandList->SetGraphicsRootDescriptorTable(2, hGPUSampler);
//注意我们使用的渲染手法是三角形列表,也就是通常的Mesh网格
pICommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
pICommandList->IASetVertexBuffers(0, 1, &stVertexBufferView);
pICommandList->IASetIndexBuffer(&stIndexBufferView);
//Draw Call!!!
pICommandList->DrawIndexedInstanced(_countof(pBoxIndices), 1, 0, 0, 0);
上面的代码将在循环中反复被调用,如果你对性能问题很敏感的话,立刻就会想到,对于一些相对单调的物体来说,比如场景中的建筑物、树木、地形、天空盒子甚至一些怪物角色、人形角色来说,好像循环中上述的这些代码或者说命令列表中的命令,基本是不变的,注意每次循环变动的可能只是MVP或者动画矩阵调色板之类,它们只是数据不是渲染命令,而我们这里说的是渲染命令,那么问题就是,每次在循环中调用它们一遍是不是显得浪费了?何况最终不管我们记录了多少命令在最终执行完成后,我们都需要将命令列表,命令分配器Reset一下,以清除之前记录的内容,然后再开始记录新的一轮命令,有时候这些新的命令跟之前循环的命令基本上大同小异。
如果你还记得我们之前在讲解命令列表时举得餐馆点菜单的例子的话,你应该立即想到,假如这是我们经常去的一家馆子,我们就爱吃他家的那几道拿手菜,并且是每次必点的话,为了提高上菜速度,是不是考虑跟老板商量好一个比较固定的菜单?每次一去只需要跟老板说老样子来一套,只是调整几个小菜或主食即可,是不是很方便?那么我们有没有什么方法来简单做到类似的记录命令的效果呢?
这时就需要D3D12中为我们提供的捆绑包(Bundles)对象来Hold住这种局面了。
按照微软官方的说法:除了命令列表之外,D3D12中还通过添加第二级命令列表(称为bundle)来利用GPU硬件中的功能。捆绑包的目的是允许应用程序将少量API命令组合在一起,以便稍后执行。在绑定包创建时,驱动程序将执行尽可能多的预处理,以便在以后执行这些操作时开销更少。捆绑包被设计为可被使用和重用的任意次数。而普通的命令列表通常只执行一次。但实际上命令列表可以多次执行(只要应用程序确保在提交新的执行之前完成了以前的执行)。按照D3D12规范的要求,Direct3D 12驱动程序能够在记录命令的同时完成与捆绑包相关的大部分工作,从而使ExecuteBundle API能够以较低的开销运行捆绑包。但捆绑包引用的所有渲染管线状态对象都必须具有相同的渲染目标格式、深度蜡板缓冲格式和采样器描述符。
下面的渲染命令调用不允许在捆绑包中记录和执行:
任何Clear方法(ClearRenderTargetView等)
任何Copy方法(CopyTextureRegion等)
DiscardResource
ExecuteBundle
ResourceBarrier
ResolveSubresource
SetPredication
BeginQuery
EndQuery
SOSetTargets
OMSetRenderTargets
RSSetViewports
RSSetScissorRects
上述方法不能在捆绑包中记录。
SetDescriptorHeap可以被捆绑包调用,但捆绑包中的描述符堆必须和调用命令列表描述堆一致。如果在捆绑包上调用了这些被禁止的渲染命令中的任何一个,运行时就会停止调用。当发生这种情况时,如果在调试状态,调试层将发出一个错误。
OK,通过上面的非官方和官方的描述之后,我想你对捆绑包应该有所理解了。要使用捆绑包,首先就需要创建捆绑包,其实本质上它任然是一个命令队列,就好像我们举得那个餐馆例子中所说,为你定制的菜单任然是一个菜单一样。创建捆绑包代码如下:
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_BUNDLE
, IID_PPV_ARGS(&pICmdAllocEarth)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_BUNDLE
, pICmdAllocEarth.Get(), nullptr, IID_PPV_ARGS(&pIBundlesEarth)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_BUNDLE
, IID_PPV_ARGS(&pICmdAllocSkybox)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_BUNDLE
, pICmdAllocSkybox.Get(), nullptr, IID_PPV_ARGS(&pIBundlesSkybox)));
接着,我们就使用捆绑包来记录渲染的命令,代码如下:
//球体的捆绑包
pIBundlesEarth->SetGraphicsRootSignature(pIRootSignature.Get());
pIBundlesEarth->SetPipelineState(pIPSOEarth.Get());
ID3D12DescriptorHeap* ppHeapsEarth[] = { pISRVHpEarth.Get(),pISampleHpEarth.Get() };
pIBundlesEarth->SetDescriptorHeaps(_countof(ppHeapsEarth), ppHeapsEarth);
//设置SRV
pIBundlesEarth->SetGraphicsRootDescriptorTable(0, pISRVHpEarth->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandleEarth(pISRVHpEarth->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pIBundlesEarth->SetGraphicsRootDescriptorTable(1, stGPUCBVHandleEarth);
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSamplerEarth(pISampleHpEarth->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
//设置Sample
pIBundlesEarth->SetGraphicsRootDescriptorTable(2, hGPUSamplerEarth);
//注意我们使用的渲染手法是三角形列表,也就是通常的Mesh网格
pIBundlesEarth->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
pIBundlesEarth->IASetVertexBuffers(0, 1, &stVBVEarth);
pIBundlesEarth->IASetIndexBuffer(&stIBVEarth);
//Draw Call!!!
pIBundlesEarth->DrawIndexedInstanced(nSphereIndexCnt, 1, 0, 0, 0);
pIBundlesEarth->Close();
//Skybox的捆绑包
pIBundlesSkybox->SetPipelineState(pIPSOSkyBox.Get());
pIBundlesSkybox->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHpSkybox.Get(),pISampleHpSkybox.Get() };
pIBundlesSkybox->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//设置SRV
pIBundlesSkybox->SetGraphicsRootDescriptorTable(0, pISRVHpSkybox->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandleSkybox(pISRVHpSkybox->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pIBundlesSkybox->SetGraphicsRootDescriptorTable(1, stGPUCBVHandleSkybox);
pIBundlesSkybox->SetGraphicsRootDescriptorTable(2, pISampleHpSkybox->GetGPUDescriptorHandleForHeapStart());
pIBundlesSkybox->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
pIBundlesSkybox->IASetVertexBuffers(0, 1, &stVBVSkybox);
//Draw Call!!!
pIBundlesSkybox->DrawInstanced(_countof(stSkyboxVertices), 1, 0, 0);
pIBundlesSkybox->Close();
从代码中可以看出,捆绑包跟普通的命令列表没什么区别,都是记录命令,最后Close即可。
最后我们的渲染循环就需要做些改造了,代码如下:
// 通过资源屏障判定后缓冲已经切换完毕可以开始渲染了
pICmdListDirect->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nCurrentFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
//偏移描述符指针到指定帧缓冲视图位置
CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart(), nCurrentFrameIndex, nRTVDescriptorSize);
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(pIDSVHeap->GetCPUDescriptorHandleForHeapStart());
//设置渲染目标
pICmdListDirect->OMSetRenderTargets(1, &stRTVHandle, FALSE, &dsvHandle);
pICmdListDirect->RSSetViewports(1, &stViewPort);
pICmdListDirect->RSSetScissorRects(1, &stScissorRect);
// 继续记录命令,并真正开始新一帧的渲染
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
pICmdListDirect->ClearRenderTargetView(stRTVHandle, clearColor, 0, nullptr);
pICmdListDirect->ClearDepthStencilView(pIDSVHeap->GetCPUDescriptorHandleForHeapStart()
, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
//=====================================================================================
//31、执行Skybox的捆绑包
ID3D12DescriptorHeap* ppHeapsSkybox[] = { pISRVHpSkybox.Get(),pISampleHpSkybox.Get() };
pICmdListDirect->SetDescriptorHeaps(_countof(ppHeapsSkybox), ppHeapsSkybox);
pICmdListDirect->ExecuteBundle(pIBundlesSkybox.Get());
//=====================================================================================
//=====================================================================================
//32、执行球体的捆绑包
ID3D12DescriptorHeap* ppHeapsEarth[] = { pISRVHpEarth.Get(),pISampleHpEarth.Get() };
pICmdListDirect->SetDescriptorHeaps(_countof(ppHeapsEarth), ppHeapsEarth);
pICmdListDirect->ExecuteBundle(pIBundlesEarth.Get());
//=====================================================================================
//又一个资源屏障,用于确定渲染已经结束可以提交画面去显示了
pICmdListDirect->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nCurrentFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
//关闭命令列表,可以去执行了
GRS_THROW_IF_FAILED(pICmdListDirect->Close());
//执行命令列表
ID3D12CommandList* ppCommandLists[] = { pICmdListDirect.Get() };
pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
......
可以看到执行捆绑包之前,为了保持命令列表中的Heaps(描述符堆列表)和捆绑包中的一致,我们就重新设置了一下,然后调用直接命令列表的ExecuteBundle执行捆绑包即可。需要注意的就是只有直接命令列表可以执行捆绑包,其实这很好理解,因为捆绑包中记录的就是关于渲染的命令,当然需要能够执行3D命令的直接命令列表来执行。
从代码中我们可以直观的看出,这样的渲染循环较之前渲染循环中的代码要简洁多了。大家可以试着将记录在捆绑包中的命令直接复制粘贴到对应的捆绑包被执行的位置,改为直接使用命令列表记录执行的样子,看看代码的复杂度,就可以直观的感受到捆绑包除了在效率上带来改进以外,在代码的简洁性上也带来了很大的改观。
这里还需要注意的一个问题就是如我们之前介绍的内容中说的,捆绑包记录的是渲染命令,而不是渲染需要的数据,比如MVP啥的,所以可以被固化,而渲染需要的数据任然需要在每帧开始的时候更新,代码如下:
n64tmCurrent = ::GetTickCount();
//计算旋转的角度:旋转角度(弧度) = 时间(秒) * 角速度(弧度/秒)
//下面这句代码相当于经典游戏消息循环中的OnUpdate函数中需要做的事情
dModelRotationYAngle += ((n64tmCurrent - n64tmFrameStart) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋转角度是2PI周期的倍数,去掉周期数,只留下相对0弧度开始的小于2PI的弧度即可
if (dModelRotationYAngle > XM_2PI)
{
dModelRotationYAngle = fmod(dModelRotationYAngle, XM_2PI);
}
//计算 视矩阵 view * 裁剪矩阵 projection
XMMATRIX xmMVP = XMMatrixMultiply(XMMatrixLookAtLH(XMLoadFloat3(&f3EyePos)
, XMLoadFloat3(&f3LockAt)
, XMLoadFloat3(&f3HeapUp))
, XMMatrixPerspectiveFovLH(XM_PIDIV4
, (FLOAT)iWndWidth / (FLOAT)iWndHeight, 0.1f, 1000.0f));
//设置Skybox的MVP
XMStoreFloat4x4(&pMVPBufSkybox->m_MVP, xmMVP);
//模型矩阵 model 这里是放大后旋转
XMMATRIX xmRot = XMMatrixMultiply(XMMatrixScaling(fSphereSize, fSphereSize, fSphereSize)
, XMMatrixRotationY(static_cast<float>(dModelRotationYAngle)));
//计算球体的MVP
xmMVP = XMMatrixMultiply(xmRot, xmMVP);
XMStoreFloat4x4(&pMVPBufEarth->m_MVP, xmMVP);
这里我们可以看到数据通道和命令管道其实是完全分离的,这也是D3D12为我们带来的又一个重大改进之一。也为最终实现相对独立渲染管线状态对象提供了有力支撑。
4、完整代码
需要的附加资源的下载链接:https://download.csdn.net/download/u014038143/10794746
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料
#include <windows.h>
#include <tchar.h>
#include <fstream> //for ifstream
using namespace std;
//添加WTL支持 方便使用COM
#include <wrl.h>
using namespace Microsoft;
using namespace Microsoft::WRL;
#include <dxgi1_6.h>
#include <DirectXMath.h>
//for d3d12
#include <d3d12.h>
#include <d3dcompiler.h>
//linker
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "d3dcompiler.lib")
#if defined(_DEBUG)
#include <dxgidebug.h>
#endif
//for WIC
#include <wincodec.h>
#include "..\WindowsCommons\d3dx12.h"
#include "..\WindowsCommons\DDSTextureLoader12.h"
using namespace DirectX;
#define GRS_WND_CLASS_NAME _T("Game Window Class")
#define GRS_WND_TITLE _T("DirectX12 Texture Sample")
#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }
//新定义的宏用于上取整除法
#define GRS_UPPER_DIV(A,B) ((UINT)(((A)+((B)-1))/(B)))
//更简洁的向上边界对齐算法 内存管理中常用 请记住
#define GRS_UPPER(A,B) ((UINT)(((A)+((B)-1))&~(B - 1)))
class CGRSCOMException
{
public:
CGRSCOMException(HRESULT hr) : m_hrError(hr)
{
}
HRESULT Error() const
{
return m_hrError;
}
private:
const HRESULT m_hrError;
};
struct WICTranslate
{
GUID wic;
DXGI_FORMAT format;
};
static WICTranslate g_WICFormats[] =
{//WIC格式与DXGI像素格式的对应表,该表中的格式为被支持的格式
{ GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT_R32G32B32A32_FLOAT },
{ GUID_WICPixelFormat64bppRGBAHalf, DXGI_FORMAT_R16G16B16A16_FLOAT },
{ GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT_R16G16B16A16_UNORM },
{ GUID_WICPixelFormat32bppRGBA, DXGI_FORMAT_R8G8B8A8_UNORM },
{ GUID_WICPixelFormat32bppBGRA, DXGI_FORMAT_B8G8R8A8_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppBGR, DXGI_FORMAT_B8G8R8X8_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppRGBA1010102XR, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppRGBA1010102, DXGI_FORMAT_R10G10B10A2_UNORM },
{ GUID_WICPixelFormat16bppBGRA5551, DXGI_FORMAT_B5G5R5A1_UNORM },
{ GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT_B5G6R5_UNORM },
{ GUID_WICPixelFormat32bppGrayFloat, DXGI_FORMAT_R32_FLOAT },
{ GUID_WICPixelFormat16bppGrayHalf, DXGI_FORMAT_R16_FLOAT },
{ GUID_WICPixelFormat16bppGray, DXGI_FORMAT_R16_UNORM },
{ GUID_WICPixelFormat8bppGray, DXGI_FORMAT_R8_UNORM },
{ GUID_WICPixelFormat8bppAlpha, DXGI_FORMAT_A8_UNORM },
};
// WIC 像素格式转换表.
struct WICConvert
{
GUID source;
GUID target;
};
static WICConvert g_WICConvert[] =
{
// 目标格式一定是最接近的被支持的格式
{ GUID_WICPixelFormatBlackWhite, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT
{ GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT
{ GUID_WICPixelFormat16bppBGR555, GUID_WICPixelFormat16bppBGRA5551 }, // DXGI_FORMAT_B5G5R5A1_UNORM
{ GUID_WICPixelFormat32bppBGR101010, GUID_WICPixelFormat32bppRGBA1010102 }, // DXGI_FORMAT_R10G10B10A2_UNORM
{ GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat48bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat48bppBGR, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPRGBA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat32bppRGBE, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat64bppCMYK, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat40bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat80bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat32bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat64bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
};
bool GetTargetPixelFormat(const GUID* pSourceFormat, GUID* pTargetFormat)
{//查表确定兼容的最接近格式是哪个
*pTargetFormat = *pSourceFormat;
for (size_t i = 0; i < _countof(g_WICConvert); ++i)
{
if (InlineIsEqualGUID(g_WICConvert[i].source, *pSourceFormat))
{
*pTargetFormat = g_WICConvert[i].target;
return true;
}
}
return false;
}
DXGI_FORMAT GetDXGIFormatFromPixelFormat(const GUID* pPixelFormat)
{//查表确定最终对应的DXGI格式是哪一个
for (size_t i = 0; i < _countof(g_WICFormats); ++i)
{
if (InlineIsEqualGUID(g_WICFormats[i].wic, *pPixelFormat))
{
return g_WICFormats[i].format;
}
}
return DXGI_FORMAT_UNKNOWN;
}
struct ST_GRS_VERTEX
{//这次我们额外加入了每个顶点的法线,但Shader中还暂时没有用
XMFLOAT3 m_vPos; //Position
XMFLOAT2 m_vTex; //Texcoord
XMFLOAT3 m_vNor; //Normal
};
struct ST_GRS_SKYBOX_VERTEX
{//天空盒子的顶点结构
XMFLOAT4 m_vPos;
};
struct ST_GRS_FRAME_MVP_BUFFER
{
XMFLOAT4X4 m_MVP; //经典的Model-view-projection(MVP)矩阵.
};
UINT nCurrentSamplerNO = 1; //当前使用的采样器索引 ,这里默认使用第一个
UINT nSampleMaxCnt = 5; //创建五个典型的采样器
//初始的默认摄像机的位置
XMFLOAT3 f3EyePos = XMFLOAT3(0.0f, 0.0f, -10.0f); //眼睛位置
XMFLOAT3 f3LockAt = XMFLOAT3(0.0f, 0.0f, 0.0f); //眼睛所盯的位置
XMFLOAT3 f3HeapUp = XMFLOAT3(0.0f, 1.0f, 0.0f); //头部正上方位置
float fYaw = 0.0f; // 绕正Z轴的旋转量.
float fPitch = 0.0f; // 绕XZ平面的旋转量
double fPalstance = 10.0f * XM_PI / 180.0f; //物体旋转的角速度,单位:弧度/秒
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
::CoInitialize(nullptr); //for WIC & COM
const UINT nFrameBackBufCount = 3u;
int iWndWidth = 1024;
int iWndHeight = 768;
UINT nCurrentFrameIndex = 0;
UINT nDXGIFactoryFlags = 0U;
UINT nRTVDescriptorSize = 0U;
HWND hWnd = nullptr;
MSG msg = {};
ST_GRS_FRAME_MVP_BUFFER* pMVPBufEarth = nullptr;
ST_GRS_FRAME_MVP_BUFFER* pMVPBufSkybox = nullptr;
//常量缓冲区大小上对齐到256Bytes边界
SIZE_T szMVPBuf = GRS_UPPER(sizeof(ST_GRS_FRAME_MVP_BUFFER), 256);
float fSphereSize = 3.0f;
D3D12_VERTEX_BUFFER_VIEW stVBVEarth = {};
D3D12_INDEX_BUFFER_VIEW stIBVEarth = {};
D3D12_VERTEX_BUFFER_VIEW stVBVSkybox = {};
D3D12_INDEX_BUFFER_VIEW stIBVSkybox = {};
UINT64 n64FenceValue = 0ui64;
HANDLE hFenceEvent = nullptr;
UINT nTxtWEarth = 0u;
UINT nTxtHEarth = 0u;
UINT nTxtWSkybox = 0u;
UINT nTxtHSkybox = 0u;
UINT nBPPEarth = 0u;
UINT nBPPSkybox = 0u;
UINT nRowPitchEarth = 0;
UINT nRowPitchSkybox = 0;
UINT64 n64szUploadBufEarth = 0;
UINT64 n64szUploadBufSkybox = 0;
DXGI_FORMAT emTxtFmtEarth = DXGI_FORMAT_UNKNOWN;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayoutsEarth = {};
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayoutsSkybox = {};
D3D12_RESOURCE_DESC stTextureDesc = {};
D3D12_RESOURCE_DESC stDestDesc = {};
UINT nSamplerDescriptorSize = 0; //采样器大小
CD3DX12_VIEWPORT stViewPort(0.0f, 0.0f, static_cast<float>(iWndWidth), static_cast<float>(iWndHeight));
CD3DX12_RECT stScissorRect(0, 0, static_cast<LONG>(iWndWidth), static_cast<LONG>(iWndHeight));
//球体的网格数据
ST_GRS_VERTEX* pstSphereVertices = nullptr;
UINT nSphereVertexCnt = 0;
UINT* pSphereIndices = nullptr;
UINT nSphereIndexCnt = 0;
//Sky Box的网格数据
UINT nSkyboxIndexCnt = 4;
ST_GRS_SKYBOX_VERTEX stSkyboxVertices[4] = {};
//======================================================================================================
//加载Skybox的Cube Map需要的变量
std::unique_ptr<uint8_t[]> ddsData;
std::vector<D3D12_SUBRESOURCE_DATA> subresources;
DDS_ALPHA_MODE emAlphaMode = DDS_ALPHA_MODE_UNKNOWN;
bool bIsCube = false;
//======================================================================================================
ComPtr<IDXGIFactory5> pIDXGIFactory5;
ComPtr<IDXGIAdapter1> pIAdapter;
ComPtr<ID3D12Device4> pID3DDevice;
ComPtr<ID3D12CommandQueue> pICommandQueue;
ComPtr<ID3D12CommandAllocator> pICmdAllocDirect;
ComPtr<ID3D12CommandAllocator> pICmdAllocSkybox;
ComPtr<ID3D12CommandAllocator> pICmdAllocEarth;
ComPtr<ID3D12GraphicsCommandList> pICmdListDirect;
ComPtr<ID3D12GraphicsCommandList> pIBundlesSkybox;
ComPtr<ID3D12GraphicsCommandList> pIBundlesEarth;
ComPtr<IDXGISwapChain1> pISwapChain1;
ComPtr<IDXGISwapChain3> pISwapChain3;
ComPtr<ID3D12Resource> pIARenderTargets[nFrameBackBufCount];
ComPtr<ID3D12DescriptorHeap> pIRTVHeap;
ComPtr<ID3D12DescriptorHeap> pIDSVHeap; //深度缓冲描述符堆
ComPtr<ID3D12Resource> pIDepthStencilBuffer; //深度蜡板缓冲区
ComPtr<ID3D12Heap> pITxtHpEarth;
ComPtr<ID3D12Heap> pIUploadHpEarth;
ComPtr<ID3D12Heap> pITxtHpSkybox;
ComPtr<ID3D12Heap> pIUploadHpSkybox;
ComPtr<ID3D12Resource> pITxtEarth;
ComPtr<ID3D12Resource> pITxtUpEarth;
ComPtr<ID3D12Resource> pICBVUpEarth;
ComPtr<ID3D12Resource> pIVBEarth;
ComPtr<ID3D12Resource> pIIBEarth;
ComPtr<ID3D12Resource> pITxtSkybox;
ComPtr<ID3D12Resource> pITxtUpSkybox;
ComPtr<ID3D12Resource> pICBVUpSkybox;
ComPtr<ID3D12Resource> pIVBSkybox;
ComPtr<ID3D12DescriptorHeap> pISRVHpEarth;
ComPtr<ID3D12DescriptorHeap> pISampleHpEarth;
ComPtr<ID3D12DescriptorHeap> pISRVHpSkybox;
ComPtr<ID3D12DescriptorHeap> pISampleHpSkybox;
ComPtr<ID3D12Fence> pIFence;
ComPtr<ID3DBlob> pIVSEarth;
ComPtr<ID3DBlob> pIPSEarth;
ComPtr<ID3DBlob> pIVSSkybox;
ComPtr<ID3DBlob> pIPSSkybox;
ComPtr<ID3D12RootSignature> pIRootSignature;
ComPtr<ID3D12PipelineState> pIPSOEarth;
ComPtr<ID3D12PipelineState> pIPSOSkyBox;
ComPtr<IWICImagingFactory> pIWICFactory;
ComPtr<IWICBitmapDecoder> pIWICDecoder;
ComPtr<IWICBitmapDecoder> pIWICSkyboxPicDecoder;
ComPtr<IWICBitmapFrameDecode> pIWICFrame;
ComPtr<IWICBitmapSource> pIBMPEarth;
ComPtr<IWICBitmapSource> pIBMPSkybox;
try
{
//1、创建窗口
{
//---------------------------------------------------------------------------------------------
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_GLOBALCLASS;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); //防止无聊的背景重绘
wcex.lpszClassName = GRS_WND_CLASS_NAME;
RegisterClassEx(&wcex);
DWORD dwWndStyle = WS_OVERLAPPED | WS_SYSMENU;
RECT rtWnd = { 0, 0, iWndWidth, iWndHeight };
AdjustWindowRect(&rtWnd, dwWndStyle, FALSE);
hWnd = CreateWindowW(GRS_WND_CLASS_NAME
, GRS_WND_TITLE
, dwWndStyle
, CW_USEDEFAULT
, 0
, rtWnd.right - rtWnd.left
, rtWnd.bottom - rtWnd.top
, nullptr
, nullptr
, hInstance
, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}
//2、使用WIC加载图片,并转换为DXGI兼容的格式
{
ComPtr<IWICFormatConverter> pIConverter;
ComPtr<IWICComponentInfo> pIWICmntinfo;
WICPixelFormatGUID wpf = {};
GUID tgFormat = {};
WICComponentType type;
ComPtr<IWICPixelFormatInfo> pIWICPixelinfo;
//---------------------------------------------------------------------------------------------
//使用纯COM方式创建WIC类厂对象,也是调用WIC第一步要做的事情
GRS_THROW_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICFactory)));
//使用WIC类厂对象接口加载纹理图片,并得到一个WIC解码器对象接口,图片信息就在这个接口代表的对象中了
WCHAR* pszTexcuteFileName = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Texture\\金星.jpg");
GRS_THROW_IF_FAILED(pIWICFactory->CreateDecoderFromFilename(
pszTexcuteFileName, // 文件名
NULL, // 不指定解码器,使用默认
GENERIC_READ, // 访问权限
WICDecodeMetadataCacheOnDemand, // 若需要就缓冲数据
&pIWICDecoder // 解码器对象
));
// 获取第一帧图片(因为GIF等格式文件可能会有多帧图片,其他的格式一般只有一帧图片)
// 实际解析出来的往往是位图格式数据
GRS_THROW_IF_FAILED(pIWICDecoder->GetFrame(0, &pIWICFrame));
//获取WIC图片格式
GRS_THROW_IF_FAILED(pIWICFrame->GetPixelFormat(&wpf));
//通过第一道转换之后获取DXGI的等价格式
if (GetTargetPixelFormat(&wpf, &tgFormat))
{
emTxtFmtEarth = GetDXGIFormatFromPixelFormat(&tgFormat);
}
if (DXGI_FORMAT_UNKNOWN == emTxtFmtEarth)
{// 不支持的图片格式 目前退出了事
// 一般 在实际的引擎当中都会提供纹理格式转换工具,
// 图片都需要提前转换好,所以不会出现不支持的现象
throw CGRSCOMException(S_FALSE);
}
if (!InlineIsEqualGUID(wpf, tgFormat))
{// 这个判断很重要,如果原WIC格式不是直接能转换为DXGI格式的图片时
// 我们需要做的就是转换图片格式为能够直接对应DXGI格式的形式
//创建图片格式转换器
GRS_THROW_IF_FAILED(pIWICFactory->CreateFormatConverter(&pIConverter));
//初始化一个图片转换器,实际也就是将图片数据进行了格式转换
GRS_THROW_IF_FAILED(pIConverter->Initialize(
pIWICFrame.Get(), // 输入原图片数据
tgFormat, // 指定待转换的目标格式
WICBitmapDitherTypeNone, // 指定位图是否有调色板,现代都是真彩位图,不用调色板,所以为None
NULL, // 指定调色板指针
0.f, // 指定Alpha阀值
WICBitmapPaletteTypeCustom // 调色板类型,实际没有使用,所以指定为Custom
));
// 调用QueryInterface方法获得对象的位图数据源接口
GRS_THROW_IF_FAILED(pIConverter.As(&pIBMPEarth));
}
else
{
//图片数据格式不需要转换,直接获取其位图数据源接口
GRS_THROW_IF_FAILED(pIWICFrame.As(&pIBMPEarth));
}
//获得图片大小(单位:像素)
GRS_THROW_IF_FAILED(pIBMPEarth->GetSize(&nTxtWEarth, &nTxtHEarth));
//获取图片像素的位大小的BPP(Bits Per Pixel)信息,用以计算图片行数据的真实大小(单位:字节)
GRS_THROW_IF_FAILED(pIWICFactory->CreateComponentInfo(tgFormat, pIWICmntinfo.GetAddressOf()));
GRS_THROW_IF_FAILED(pIWICmntinfo->GetComponentType(&type));
if (type != WICPixelFormat)
{
throw CGRSCOMException(S_FALSE);
}
GRS_THROW_IF_FAILED(pIWICmntinfo.As(&pIWICPixelinfo));
// 到这里终于可以得到BPP了,这也是我看的比较吐血的地方,为了BPP居然饶了这么多环节
GRS_THROW_IF_FAILED(pIWICPixelinfo->GetBitsPerPixel(&nBPPEarth));
// 计算图片实际的行大小(单位:字节),这里使用了一个上取整除法即(A+B-1)/B ,
// 这曾经被传说是微软的面试题,希望你已经对它了如指掌
nRowPitchEarth = GRS_UPPER_DIV(uint64_t(nTxtWEarth) * uint64_t(nBPPEarth), 8);
}
//3、打开显示子系统的调试支持
{
#if defined(_DEBUG)
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// 打开附加的调试支持
nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif
}
//4、创建DXGI Factory对象
{
GRS_THROW_IF_FAILED(CreateDXGIFactory2(nDXGIFactoryFlags, IID_PPV_ARGS(&pIDXGIFactory5)));
// 关闭ALT+ENTER键切换全屏的功能,因为我们没有实现OnSize处理,所以先关闭
GRS_THROW_IF_FAILED(pIDXGIFactory5->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
}
//5、枚举适配器创建设备
{//选择NUMA架构的独显来创建3D设备对象,暂时先不支持集显了,当然你可以修改这些行为
DXGI_ADAPTER_DESC1 desc = {};
D3D12_FEATURE_DATA_ARCHITECTURE stArchitecture = {};
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc = {};
pIAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{//跳过软件虚拟适配器设备
continue;
}
GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));
GRS_THROW_IF_FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE
, &stArchitecture, sizeof(D3D12_FEATURE_DATA_ARCHITECTURE)));
if (!stArchitecture.UMA)
{
break;
}
pID3DDevice.Reset();
}
//---------------------------------------------------------------------------------------------
if (nullptr == pID3DDevice.Get())
{// 可怜的机器上居然没有独显 还是先退出了事
throw CGRSCOMException(E_FAIL);
}
}
//6、创建直接命令队列
{
D3D12_COMMAND_QUEUE_DESC stQueueDesc = {};
stQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandQueue(&stQueueDesc, IID_PPV_ARGS(&pICommandQueue)));
}
//7、创建直接命令列表、捆绑包
{
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT
, IID_PPV_ARGS(&pICmdAllocDirect)));
//创建直接命令列表,在其上可以执行几乎所有的引擎命令(3D图形引擎、计算引擎、复制引擎等)
//注意初始时并没有使用PSO对象,此时其实这个命令列表依然可以记录命令
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT
, pICmdAllocDirect.Get(), nullptr, IID_PPV_ARGS(&pICmdListDirect)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_BUNDLE
, IID_PPV_ARGS(&pICmdAllocEarth)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_BUNDLE
, pICmdAllocEarth.Get(), nullptr, IID_PPV_ARGS(&pIBundlesEarth)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_BUNDLE
, IID_PPV_ARGS(&pICmdAllocSkybox)));
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_BUNDLE
, pICmdAllocSkybox.Get(), nullptr, IID_PPV_ARGS(&pIBundlesSkybox)));
}
//8、创建交换链
{
DXGI_SWAP_CHAIN_DESC1 stSwapChainDesc = {};
stSwapChainDesc.BufferCount = nFrameBackBufCount;
stSwapChainDesc.Width = iWndWidth;
stSwapChainDesc.Height = iWndHeight;
stSwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
stSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
stSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
stSwapChainDesc.SampleDesc.Count = 1;
GRS_THROW_IF_FAILED(pIDXGIFactory5->CreateSwapChainForHwnd(
pICommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
hWnd,
&stSwapChainDesc,
nullptr,
nullptr,
&pISwapChain1
));
//注意此处使用了高版本的SwapChain接口的函数
GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));
nCurrentFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
//创建RTV(渲染目标视图)描述符堆(这里堆的含义应当理解为数组或者固定大小元素的固定大小显存池)
D3D12_DESCRIPTOR_HEAP_DESC stRTVHeapDesc = {};
stRTVHeapDesc.NumDescriptors = nFrameBackBufCount;
stRTVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
stRTVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stRTVHeapDesc, IID_PPV_ARGS(&pIRTVHeap)));
//得到每个描述符元素的大小
nRTVDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
//---------------------------------------------------------------------------------------------
CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < nFrameBackBufCount; i++)
{//这个循环暴漏了描述符堆实际上是个数组的本质
GRS_THROW_IF_FAILED(pISwapChain3->GetBuffer(i, IID_PPV_ARGS(&pIARenderTargets[i])));
pID3DDevice->CreateRenderTargetView(pIARenderTargets[i].Get(), nullptr, stRTVHandle);
stRTVHandle.Offset(1, nRTVDescriptorSize);
}
}
//9、创建深度缓冲及深度缓冲描述符堆
{
D3D12_DEPTH_STENCIL_VIEW_DESC stDepthStencilDesc = {};
stDepthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
stDepthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
stDepthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;
D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0;
//使用隐式默认堆创建一个深度蜡板缓冲区,
//因为基本上深度缓冲区会一直被使用,重用的意义不大,所以直接使用隐式堆,图方便
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT)
, D3D12_HEAP_FLAG_NONE
, &CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT
, iWndWidth, iWndHeight, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)
, D3D12_RESOURCE_STATE_DEPTH_WRITE
, &depthOptimizedClearValue
, IID_PPV_ARGS(&pIDepthStencilBuffer)
));
//==============================================================================================
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&pIDSVHeap)));
pID3DDevice->CreateDepthStencilView(pIDepthStencilBuffer.Get()
, &stDepthStencilDesc
, pIDSVHeap->GetCPUDescriptorHandleForHeapStart());
}
//9、创建 SRV CBV Sample堆
{
//我们将纹理视图描述符和CBV描述符放在一个描述符堆上
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 2; //1 SRV + 1 CBV
stSRVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
stSRVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSRVHeapDesc, IID_PPV_ARGS(&pISRVHpEarth)));
D3D12_DESCRIPTOR_HEAP_DESC stSamplerHeapDesc = {};
stSamplerHeapDesc.NumDescriptors = nSampleMaxCnt;
stSamplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
stSamplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc, IID_PPV_ARGS(&pISampleHpEarth)));
//===================================================================================================
//Skybox 的 SRV CBV Sample 堆
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSRVHeapDesc, IID_PPV_ARGS(&pISRVHpSkybox)));
stSamplerHeapDesc.NumDescriptors = 1; //天空盒子就一个采样器
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc, IID_PPV_ARGS(&pISampleHpSkybox)));
nSamplerDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
}
//10、创建根签名
{//这个例子中,球体和Skybox使用相同的根签名,因为渲染过程中需要的参数是一样的
D3D12_FEATURE_DATA_ROOT_SIGNATURE stFeatureData = {};
// 检测是否支持V1.1版本的根签名
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
if (FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &stFeatureData, sizeof(stFeatureData))))
{
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}
// 在GPU上执行SetGraphicsRootDescriptorTable后,我们不修改命令列表中的SRV,因此我们可以使用默认Rang行为:
// D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE
CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[3];
stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
stDSPRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);
stDSPRanges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[3];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);//SRV仅PS可见
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_ALL); //CBV是所有Shader可见
stRootParameters[2].InitAsDescriptorTable(1, &stDSPRanges[2], D3D12_SHADER_VISIBILITY_PIXEL);//SAMPLE仅PS可见
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;
stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
, 0, nullptr
, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> pISignatureBlob;
ComPtr<ID3DBlob> pIErrorBlob;
GRS_THROW_IF_FAILED(D3DX12SerializeVersionedRootSignature(&stRootSignatureDesc
, stFeatureData.HighestVersion
, &pISignatureBlob
, &pIErrorBlob));
GRS_THROW_IF_FAILED(pID3DDevice->CreateRootSignature(0
, pISignatureBlob->GetBufferPointer()
, pISignatureBlob->GetBufferSize()
, IID_PPV_ARGS(&pIRootSignature)));
}
//11、编译Shader创建渲染管线状态对象
{
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
//编译为行矩阵形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Shader\\TextureCube.hlsl");
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr
, "VSMain", "vs_5_0", compileFlags, 0, &pIVSEarth, nullptr));
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr
, "PSMain", "ps_5_0", compileFlags, 0, &pIPSEarth, nullptr));
// 我们多添加了一个法线的定义,但目前Shader中我们并没有使用
D3D12_INPUT_ELEMENT_DESC stIALayoutEarth[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 20, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// 创建 graphics pipeline state object (PSO)对象
D3D12_GRAPHICS_PIPELINE_STATE_DESC stPSODesc = {};
stPSODesc.InputLayout = { stIALayoutEarth, _countof(stIALayoutEarth) };
stPSODesc.pRootSignature = pIRootSignature.Get();
stPSODesc.VS = CD3DX12_SHADER_BYTECODE(pIVSEarth.Get());
stPSODesc.PS = CD3DX12_SHADER_BYTECODE(pIPSEarth.Get());
stPSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
stPSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
stPSODesc.DepthStencilState.DepthEnable = FALSE;
stPSODesc.DepthStencilState.StencilEnable = FALSE;
stPSODesc.SampleMask = UINT_MAX;
stPSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
stPSODesc.NumRenderTargets = 1;
stPSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
stPSODesc.DSVFormat = DXGI_FORMAT_D32_FLOAT;
stPSODesc.DepthStencilState.DepthEnable = TRUE;
stPSODesc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;//启用深度缓存写入功能
stPSODesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS; //深度测试函数(该值为普通的深度测试)
stPSODesc.SampleDesc.Count = 1;
GRS_THROW_IF_FAILED(pID3DDevice->CreateGraphicsPipelineState(&stPSODesc
, IID_PPV_ARGS(&pIPSOEarth)));
//编译为行矩阵形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszSMFileSkybox[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Shader\\SkyBox.hlsl");
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszSMFileSkybox, nullptr, nullptr
, "SkyboxVS", "vs_5_0", compileFlags, 0, &pIVSSkybox, nullptr));
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszSMFileSkybox, nullptr, nullptr
, "SkyboxPS", "ps_5_0", compileFlags, 0, &pIPSSkybox, nullptr));
// 天空盒子只有顶点只有位置参数
D3D12_INPUT_ELEMENT_DESC stIALayoutSkybox[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// 创建Skybox的(PSO)对象 注意天空盒子不需要深度测试
stPSODesc.DepthStencilState.DepthEnable = FALSE;
stPSODesc.DepthStencilState.StencilEnable = FALSE;
stPSODesc.InputLayout = { stIALayoutSkybox, _countof(stIALayoutSkybox) };
stPSODesc.VS = CD3DX12_SHADER_BYTECODE(pIVSSkybox.Get());
stPSODesc.PS = CD3DX12_SHADER_BYTECODE(pIPSSkybox.Get());
GRS_THROW_IF_FAILED(pID3DDevice->CreateGraphicsPipelineState(&stPSODesc
, IID_PPV_ARGS(&pIPSOSkyBox)));
}
//12、使用DDSLoader辅助函数加载Skybox的纹理
{
TCHAR pszSkyboxTextureFile[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Texture\\Texture1.dds");
//HRESULT DirectX::LoadDDSTextureFromFile(
// ID3D12Device* d3dDevice,
// const wchar_t* fileName,
// ID3D12Resource** texture,
// std::unique_ptr<uint8_t[]>& ddsData,
// std::vector<D3D12_SUBRESOURCE_DATA>& subresources,
// size_t maxsize,
// DDS_ALPHA_MODE* alphaMode,
// bool* isCubeMap);
ID3D12Resource* pIResSkyBox = nullptr;
GRS_THROW_IF_FAILED(LoadDDSTextureFromFile(
pID3DDevice.Get()
, pszSkyboxTextureFile
, &pIResSkyBox
, ddsData
, subresources
, SIZE_MAX
, &emAlphaMode
, &bIsCube));
//上面函数加载的纹理在隐式默认堆上,两个Copy动作需要我们自己完成
pITxtSkybox.Attach(pIResSkyBox);
}
//13、创建纹理的默认堆
{
D3D12_HEAP_DESC stTextureHeapDesc = {};
//为堆指定纹理图片至少2倍大小的空间,这里没有详细去计算了,只是指定了一个足够大的空间,够放纹理就行
//实际应用中也是要综合考虑分配堆的大小,以便可以重用堆
stTextureHeapDesc.SizeInBytes = GRS_UPPER(2 * nRowPitchEarth * nTxtHEarth, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//指定堆的对齐方式,这里使用了默认的64K边界对齐,因为我们暂时不需要MSAA支持
stTextureHeapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
stTextureHeapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; //默认堆类型
stTextureHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stTextureHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//拒绝渲染目标纹理、拒绝深度蜡板纹理,实际就只是用来摆放普通纹理
stTextureHeapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stTextureHeapDesc, IID_PPV_ARGS(&pITxtHpEarth)));
}
//14、创建2D纹理
{
stTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
stTextureDesc.MipLevels = 1;
stTextureDesc.Format = emTxtFmtEarth; //DXGI_FORMAT_R8G8B8A8_UNORM;
stTextureDesc.Width = nTxtWEarth;
stTextureDesc.Height = nTxtHEarth;
stTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
stTextureDesc.DepthOrArraySize = 1;
stTextureDesc.SampleDesc.Count = 1;
stTextureDesc.SampleDesc.Quality = 0;
//-----------------------------------------------------------------------------------------------------------
//使用“定位方式”来创建纹理,注意下面这个调用内部实际已经没有存储分配和释放的实际操作了,所以性能很高
//同时可以在这个堆上反复调用CreatePlacedResource来创建不同的纹理,当然前提是它们不在被使用的时候,才考虑
//重用堆
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pITxtHpEarth.Get()
, 0
, &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D来简化结构体的初始化
, D3D12_RESOURCE_STATE_COPY_DEST
, nullptr
, IID_PPV_ARGS(&pITxtEarth)));
//-----------------------------------------------------------------------------------------------------------
//获取上传堆资源缓冲的大小,这个尺寸通常大于实际图片的尺寸
n64szUploadBufEarth = GetRequiredIntermediateSize(pITxtEarth.Get(), 0, 1);
}
//15、创建上传堆
{
//-----------------------------------------------------------------------------------------------------------
D3D12_HEAP_DESC stUploadHeapDesc = { };
//尺寸依然是实际纹理数据大小的2倍并64K边界对齐大小
stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64szUploadBufEarth, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//注意上传堆肯定是Buffer类型,可以不指定对齐方式,其默认是64k边界对齐
stUploadHeapDesc.Alignment = 0;
stUploadHeapDesc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; //上传堆类型
stUploadHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stUploadHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//上传堆就是缓冲,可以摆放任意数据
stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHpEarth)));
//-----------------------------------------------------------------------------------------------------------
//=====================================================================================================
//获取Skybox的资源大小,并创建上传堆
n64szUploadBufSkybox = GetRequiredIntermediateSize(pITxtSkybox.Get(), 0, static_cast<UINT>(subresources.size()));
stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64szUploadBufSkybox, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHpSkybox)));
}
//16、使用“定位方式”创建用于上传纹理数据的缓冲资源
{
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHpEarth.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64szUploadBufEarth)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITxtUpEarth)));
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHpSkybox.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64szUploadBufSkybox)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITxtUpSkybox)));
}
//17、加载图片数据至上传堆,即完成第一个Copy动作,从memcpy函数可知这是由CPU完成的
{
//按照资源缓冲大小来分配实际图片数据存储的内存大小
void* pbPicData = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, n64szUploadBufEarth);
if (nullptr == pbPicData)
{
throw CGRSCOMException(HRESULT_FROM_WIN32(GetLastError()));
}
//从图片中读取出数据
GRS_THROW_IF_FAILED(pIBMPEarth->CopyPixels(nullptr
, nRowPitchEarth
, static_cast<UINT>(nRowPitchEarth * nTxtHEarth) //注意这里才是图片数据真实的大小,这个值通常小于缓冲的大小
, reinterpret_cast<BYTE*>(pbPicData)));
//{//下面这段代码来自DX12的示例,直接通过填充缓冲绘制了一个黑白方格的纹理
// //还原这段代码,然后注释上面的CopyPixels调用可以看到黑白方格纹理的效果
// const UINT rowPitch = nRowPitchEarth; //nTxtWEarth * 4; //static_cast<UINT>(n64szUploadBufEarth / nTxtHEarth);
// const UINT cellPitch = rowPitch >> 3; // The width of a cell in the checkboard texture.
// const UINT cellHeight = nTxtWEarth >> 3; // The height of a cell in the checkerboard texture.
// const UINT textureSize = static_cast<UINT>(n64szUploadBufEarth);
// UINT nTexturePixelSize = static_cast<UINT>(n64szUploadBufEarth / nTxtHEarth / nTxtWEarth);
// UINT8* pData = reinterpret_cast<UINT8*>(pbPicData);
// for (UINT n = 0; n < textureSize; n += nTexturePixelSize)
// {
// UINT x = n % rowPitch;
// UINT y = n / rowPitch;
// UINT i = x / cellPitch;
// UINT j = y / cellHeight;
// if (i % 2 == j % 2)
// {
// pData[n] = 0x00; // R
// pData[n + 1] = 0x00; // G
// pData[n + 2] = 0x00; // B
// pData[n + 3] = 0xff; // A
// }
// else
// {
// pData[n] = 0xff; // R
// pData[n + 1] = 0xff; // G
// pData[n + 2] = 0xff; // B
// pData[n + 3] = 0xff; // A
// }
// }
//}
//获取向上传堆拷贝纹理数据的一些纹理转换尺寸信息
//对于复杂的DDS纹理这是非常必要的过程
UINT nNumSubresources = 1u; //我们只有一副图片,即子资源个数为1
UINT nTextureRowNum = 0u;
UINT64 n64TextureRowSizes = 0u;
UINT64 n64RequiredSize = 0u;
stDestDesc = pITxtEarth->GetDesc();
pID3DDevice->GetCopyableFootprints(&stDestDesc
, 0
, nNumSubresources
, 0
, &stTxtLayoutsEarth
, &nTextureRowNum
, &n64TextureRowSizes
, &n64RequiredSize);
//因为上传堆实际就是CPU传递数据到GPU的中介
//所以我们可以使用熟悉的Map方法将它先映射到CPU内存地址中
//然后我们按行将数据复制到上传堆中
//需要注意的是之所以按行拷贝是因为GPU资源的行大小
//与实际图片的行大小是有差异的,二者的内存边界对齐要求是不一样的
BYTE* pData = nullptr;
GRS_THROW_IF_FAILED(pITxtUpEarth->Map(0, NULL, reinterpret_cast<void**>(&pData)));
BYTE* pDestSlice = reinterpret_cast<BYTE*>(pData) + stTxtLayoutsEarth.Offset;
BYTE* pSrcSlice = reinterpret_cast<BYTE*>(pbPicData);
for (UINT y = 0; y < nTextureRowNum; ++y)
{
memcpy(pDestSlice + static_cast<SIZE_T>(stTxtLayoutsEarth.Footprint.RowPitch) * y
, pSrcSlice + static_cast<SIZE_T>(nRowPitchEarth) * y
, nRowPitchEarth);
}
//取消映射 对于易变的数据如每帧的变换矩阵等数据,可以撒懒不用Unmap了,
//让它常驻内存,以提高整体性能,因为每次Map和Unmap是很耗时的操作
//因为现在起码都是64位系统和应用了,地址空间是足够的,被长期占用不会影响什么
pITxtUpEarth->Unmap(0, NULL);
//释放图片数据,做一个干净的程序员
::HeapFree(::GetProcessHeap(), 0, pbPicData);
}
//18、上传Skybox的纹理
{
UpdateSubresources(pICmdListDirect.Get()
, pITxtSkybox.Get()
, pITxtUpSkybox.Get()
, 0
, 0
, static_cast<UINT>(subresources.size())
, subresources.data());
pICmdListDirect->ResourceBarrier(1
, &CD3DX12_RESOURCE_BARRIER::Transition(pITxtSkybox.Get()
, D3D12_RESOURCE_STATE_COPY_DEST
, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
}
//19、向直接命令列表发出从上传堆复制纹理数据到默认堆的命令,执行并同步等待,即完成第二个Copy动作,由GPU上的复制引擎完成
//注意此时直接命令列表还没有绑定PSO对象,因此它也是不能执行3D图形命令的,但是可以执行复制命令,因为复制引擎不需要什么
//额外的状态设置之类的参数
{
CD3DX12_TEXTURE_COPY_LOCATION Dst(pITxtEarth.Get(), 0);
CD3DX12_TEXTURE_COPY_LOCATION Src(pITxtUpEarth.Get(), stTxtLayoutsEarth);
pICmdListDirect->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);
//设置一个资源屏障,同步并确认复制操作完成
//直接使用结构体然后调用的形式
D3D12_RESOURCE_BARRIER stResBar = {};
stResBar.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
stResBar.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
stResBar.Transition.pResource = pITxtEarth.Get();
stResBar.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
stResBar.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
stResBar.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
pICmdListDirect->ResourceBarrier(1, &stResBar);
}
//20、执行第二个Copy命令并确定所有的纹理都上传到了默认堆中
{
//---------------------------------------------------------------------------------------------
// 执行命令列表并等待纹理资源上传完成,这一步是必须的
GRS_THROW_IF_FAILED(pICmdListDirect->Close());
ID3D12CommandList* ppCommandLists[] = { pICmdListDirect.Get() };
pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
//---------------------------------------------------------------------------------------------
// 17、创建一个同步对象——围栏,用于等待渲染完成,因为现在Draw Call是异步的了
GRS_THROW_IF_FAILED(pID3DDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pIFence)));
n64FenceValue = 1;
//---------------------------------------------------------------------------------------------
// 18、创建一个Event同步对象,用于等待围栏事件通知
hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (hFenceEvent == nullptr)
{
GRS_THROW_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));
}
//---------------------------------------------------------------------------------------------
// 19、等待纹理资源正式复制完成先
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
//---------------------------------------------------------------------------------------------
// 看命令有没有真正执行到围栏标记的这里,没有就利用事件去等待,注意使用的是命令队列对象的指针
if (pIFence->GetCompletedValue() < fence)
{
GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));
WaitForSingleObject(hFenceEvent, INFINITE);
}
//---------------------------------------------------------------------------------------------
//命令分配器先Reset一下,刚才已经执行过了一个复制纹理的命令
GRS_THROW_IF_FAILED(pICmdAllocDirect->Reset());
//Reset命令列表,并重新指定命令分配器和PSO对象
GRS_THROW_IF_FAILED(pICmdListDirect->Reset(pICmdAllocDirect.Get(), pIPSOEarth.Get()));
//---------------------------------------------------------------------------------------------
}
//21、定义Sky的3D数据结构
{
float fHighW = -1.0f - (1.0f / (float)iWndWidth);
float fHighH = -1.0f - (1.0f / (float)iWndHeight);
float fLowW = 1.0f + (1.0f / (float)iWndWidth);
float fLowH = 1.0f + (1.0f / (float)iWndHeight);
stSkyboxVertices[0].m_vPos = XMFLOAT4(fLowW, fLowH, 1.0f, 1.0f);
stSkyboxVertices[1].m_vPos = XMFLOAT4(fLowW, fHighH, 1.0f, 1.0f);
stSkyboxVertices[2].m_vPos = XMFLOAT4(fHighW, fLowH, 1.0f, 1.0f);
stSkyboxVertices[3].m_vPos = XMFLOAT4(fHighW, fHighH, 1.0f, 1.0f);
}
//22、加载球体的网格数据
{
ifstream fin;
char input;
fin.open("D:\\Projects_2018_08\\D3D12 Tutorials\\5-SkyBox\\Mesh\\sphere.txt");
if (fin.fail())
{
throw CGRSCOMException(E_FAIL);
}
fin.get(input);
while (input != ':')
{
fin.get(input);
}
fin >> nSphereVertexCnt;
nSphereIndexCnt = nSphereVertexCnt;
fin.get(input);
while (input != ':')
{
fin.get(input);
}
fin.get(input);
fin.get(input);
pstSphereVertices = (ST_GRS_VERTEX*)HeapAlloc(::GetProcessHeap()
, HEAP_ZERO_MEMORY
, nSphereVertexCnt * sizeof(ST_GRS_VERTEX));
pSphereIndices = (UINT*)HeapAlloc(::GetProcessHeap()
, HEAP_ZERO_MEMORY
, nSphereVertexCnt * sizeof(ST_GRS_VERTEX));
for (UINT i = 0; i < nSphereVertexCnt; i++)
{
fin >> pstSphereVertices[i].m_vPos.x >> pstSphereVertices[i].m_vPos.y >> pstSphereVertices[i].m_vPos.z;
fin >> pstSphereVertices[i].m_vTex.x >> pstSphereVertices[i].m_vTex.y;
//pstSphereVertices[i].m_vTex.x = fabs(pstSphereVertices[i].m_vTex.x);
//pstSphereVertices[i].m_vTex.x *= 0.5f;
fin >> pstSphereVertices[i].m_vNor.x >> pstSphereVertices[i].m_vNor.y >> pstSphereVertices[i].m_vNor.z;
pSphereIndices[i] = i;
}
}
//23、使用“定位方式”创建顶点缓冲和索引缓冲,使用与上传纹理数据缓冲相同的一个上传堆
{
//---------------------------------------------------------------------------------------------
//使用定位方式在相同的上传堆上以“定位方式”创建顶点缓冲,注意第二个参数指出了堆中的偏移位置
//按照堆边界对齐的要求,我们主动将偏移位置对齐到了64k的边界上
UINT64 n64BufferOffset = GRS_UPPER(n64szUploadBufEarth, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHpEarth.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(nSphereVertexCnt * sizeof(ST_GRS_VERTEX))
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIVBEarth)));
//使用map-memcpy-unmap大法将数据传至顶点缓冲对象
//注意顶点缓冲使用是和上传纹理数据缓冲相同的一个堆,这很神奇
UINT8* pVertexDataBegin = nullptr;
CD3DX12_RANGE stReadRange(0, 0); // We do not intend to read from this resource on the CPU.
GRS_THROW_IF_FAILED(pIVBEarth->Map(0, &stReadRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, pstSphereVertices, nSphereVertexCnt * sizeof(ST_GRS_VERTEX));
pIVBEarth->Unmap(0, nullptr);
//创建资源视图,实际可以简单理解为指向顶点缓冲的显存指针
stVBVEarth.BufferLocation = pIVBEarth->GetGPUVirtualAddress();
stVBVEarth.StrideInBytes = sizeof(ST_GRS_VERTEX);
stVBVEarth.SizeInBytes = nSphereVertexCnt * sizeof(ST_GRS_VERTEX);
//计算边界对齐的正确的偏移位置
n64BufferOffset = GRS_UPPER(n64BufferOffset + nSphereVertexCnt * sizeof(ST_GRS_VERTEX), D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHpEarth.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(nSphereIndexCnt * sizeof(UINT))
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIIBEarth)));
UINT8* pIndexDataBegin = nullptr;
GRS_THROW_IF_FAILED(pIIBEarth->Map(0, &stReadRange, reinterpret_cast<void**>(&pIndexDataBegin)));
memcpy(pIndexDataBegin, pSphereIndices, nSphereIndexCnt * sizeof(UINT));
pIIBEarth->Unmap(0, nullptr);
stIBVEarth.BufferLocation = pIIBEarth->GetGPUVirtualAddress();
stIBVEarth.Format = DXGI_FORMAT_R32_UINT;
stIBVEarth.SizeInBytes = nSphereIndexCnt * sizeof(UINT);
n64BufferOffset = GRS_UPPER(n64BufferOffset + nSphereIndexCnt * sizeof(UINT), D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 创建常量缓冲 注意缓冲尺寸设置为256边界对齐大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHpEarth.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuf)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpEarth)));
// Map 之后就不再Unmap了 直接复制数据进去 这样每帧都不用map-copy-unmap浪费时间了
GRS_THROW_IF_FAILED(pICBVUpEarth->Map(0, nullptr, reinterpret_cast<void**>(&pMVPBufEarth)));
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
//加载天空盒子的数据
n64BufferOffset = GRS_UPPER(n64szUploadBufSkybox, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHpSkybox.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(nSkyboxIndexCnt * sizeof(ST_GRS_SKYBOX_VERTEX))
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIVBSkybox)));
//使用map-memcpy-unmap大法将数据传至顶点缓冲对象
//注意顶点缓冲使用是和上传纹理数据缓冲相同的一个堆,这很神奇
pVertexDataBegin = nullptr;
GRS_THROW_IF_FAILED(pIVBSkybox->Map(0, &stReadRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, &stSkyboxVertices, nSkyboxIndexCnt * sizeof(ST_GRS_SKYBOX_VERTEX));
pIVBSkybox->Unmap(0, nullptr);
//创建资源视图,实际可以简单理解为指向顶点缓冲的显存指针
stVBVSkybox.BufferLocation = pIVBSkybox->GetGPUVirtualAddress();
stVBVSkybox.StrideInBytes = sizeof(ST_GRS_SKYBOX_VERTEX);
stVBVSkybox.SizeInBytes = nSkyboxIndexCnt * sizeof(ST_GRS_SKYBOX_VERTEX);
n64BufferOffset = GRS_UPPER(n64BufferOffset + nSkyboxIndexCnt * sizeof(ST_GRS_SKYBOX_VERTEX), D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 创建常量缓冲 注意缓冲尺寸设置为256边界对齐大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHpSkybox.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuf)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpSkybox)));
// Map 之后就不再Unmap了 直接复制数据进去 这样每帧都不用map-copy-unmap浪费时间了
GRS_THROW_IF_FAILED(pICBVUpSkybox->Map(0, nullptr, reinterpret_cast<void**>(&pMVPBufSkybox)));
//---------------------------------------------------------------------------------------------
}
//24、创建SRV描述符
{
D3D12_SHADER_RESOURCE_VIEW_DESC stSRVDesc = {};
stSRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
stSRVDesc.Format = emTxtFmtEarth;
stSRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
stSRVDesc.Texture2D.MipLevels = 1;
pID3DDevice->CreateShaderResourceView(pITxtEarth.Get(), &stSRVDesc, pISRVHpEarth->GetCPUDescriptorHandleForHeapStart());
//---------------------------------------------------------------------------------------------
D3D12_RESOURCE_DESC stDescSkybox = pITxtSkybox->GetDesc();
stSRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
stSRVDesc.Format = stDescSkybox.Format;
stSRVDesc.TextureCube.MipLevels = stDescSkybox.MipLevels;
pID3DDevice->CreateShaderResourceView(pITxtSkybox.Get(), &stSRVDesc, pISRVHpSkybox->GetCPUDescriptorHandleForHeapStart());
}
//25、创建CBV描述符
{
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = pICBVUpEarth->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<UINT>(szMVPBuf);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(pISRVHpEarth->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
//---------------------------------------------------------------------------------------------
cbvDesc.BufferLocation = pICBVUpSkybox->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<UINT>(szMVPBuf);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandleSkybox(pISRVHpSkybox->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandleSkybox);
//---------------------------------------------------------------------------------------------
}
//26、创建各种采样器
{
CD3DX12_CPU_DESCRIPTOR_HANDLE hSamplerHeap(pISampleHpEarth->GetCPUDescriptorHandleForHeapStart());
D3D12_SAMPLER_DESC stSamplerDesc = {};
stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
stSamplerDesc.MinLOD = 0;
stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
stSamplerDesc.MipLODBias = 0.0f;
stSamplerDesc.MaxAnisotropy = 1;
stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
// Sampler 1
stSamplerDesc.BorderColor[0] = 1.0f;
stSamplerDesc.BorderColor[1] = 0.0f;
stSamplerDesc.BorderColor[2] = 1.0f;
stSamplerDesc.BorderColor[3] = 1.0f;
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 2
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 3
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 4
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 5
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
//---------------------------------------------------------------------------------------------
//创建Skybox的采样器
stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
stSamplerDesc.MinLOD = 0;
stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
stSamplerDesc.MipLODBias = 0.0f;
stSamplerDesc.MaxAnisotropy = 1;
stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
stSamplerDesc.BorderColor[0] = 0.0f;
stSamplerDesc.BorderColor[1] = 0.0f;
stSamplerDesc.BorderColor[2] = 0.0f;
stSamplerDesc.BorderColor[3] = 0.0f;
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
pID3DDevice->CreateSampler(&stSamplerDesc, pISampleHpSkybox->GetCPUDescriptorHandleForHeapStart());
//---------------------------------------------------------------------------------------------
}
//27、用捆绑包记录固化的命令
{
//=================================================================================================
//球体的捆绑包
pIBundlesEarth->SetGraphicsRootSignature(pIRootSignature.Get());
pIBundlesEarth->SetPipelineState(pIPSOEarth.Get());
ID3D12DescriptorHeap* ppHeapsEarth[] = { pISRVHpEarth.Get(),pISampleHpEarth.Get() };
pIBundlesEarth->SetDescriptorHeaps(_countof(ppHeapsEarth), ppHeapsEarth);
//设置SRV
pIBundlesEarth->SetGraphicsRootDescriptorTable(0, pISRVHpEarth->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandleEarth(pISRVHpEarth->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pIBundlesEarth->SetGraphicsRootDescriptorTable(1, stGPUCBVHandleEarth);
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSamplerEarth(pISampleHpEarth->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
//设置Sample
pIBundlesEarth->SetGraphicsRootDescriptorTable(2, hGPUSamplerEarth);
//注意我们使用的渲染手法是三角形列表,也就是通常的Mesh网格
pIBundlesEarth->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
pIBundlesEarth->IASetVertexBuffers(0, 1, &stVBVEarth);
pIBundlesEarth->IASetIndexBuffer(&stIBVEarth);
//Draw Call!!!
pIBundlesEarth->DrawIndexedInstanced(nSphereIndexCnt, 1, 0, 0, 0);
pIBundlesEarth->Close();
//=================================================================================================
//=================================================================================================
//Skybox的捆绑包
pIBundlesSkybox->SetPipelineState(pIPSOSkyBox.Get());
pIBundlesSkybox->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHpSkybox.Get(),pISampleHpSkybox.Get() };
pIBundlesSkybox->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//设置SRV
pIBundlesSkybox->SetGraphicsRootDescriptorTable(0, pISRVHpSkybox->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandleSkybox(pISRVHpSkybox->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pIBundlesSkybox->SetGraphicsRootDescriptorTable(1, stGPUCBVHandleSkybox);
pIBundlesSkybox->SetGraphicsRootDescriptorTable(2, pISampleHpSkybox->GetGPUDescriptorHandleForHeapStart());
pIBundlesSkybox->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
pIBundlesSkybox->IASetVertexBuffers(0, 1, &stVBVSkybox);
//Draw Call!!!
pIBundlesSkybox->DrawInstanced(_countof(stSkyboxVertices), 1, 0, 0);
pIBundlesSkybox->Close();
//=================================================================================================
}
//---------------------------------------------------------------------------------------------
//28、创建定时器对象,以便于创建高效的消息循环
HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);
LARGE_INTEGER liDueTime = {};
liDueTime.QuadPart = -1i64;//1秒后开始计时
SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的周期
//29、记录帧开始时间,和当前时间,以循环结束为界
ULONGLONG n64tmFrameStart = ::GetTickCount64();
ULONGLONG n64tmCurrent = n64tmFrameStart;
//计算旋转角度需要的变量
double dModelRotationYAngle = 0.0f;
//---------------------------------------------------------------------------------------------
//30、开始消息循环,并在其中不断渲染
DWORD dwRet = 0;
BOOL bExit = FALSE;
while (!bExit)
{//注意这里我们调整了消息循环,将等待时间设置为0,同时将定时性的渲染,改成了每次循环都渲染
//但这不表示说MsgWait函数就没啥用了,坚持使用它是因为后面例子如果想加入多线程控制就非常简单了
dwRet = ::MsgWaitForMultipleObjects(1, &phWait, FALSE, 0, QS_ALLINPUT);
switch (dwRet - WAIT_OBJECT_0)
{
case 0:
case WAIT_TIMEOUT:
{//计时器时间到
}
break;
case 1:
{//处理消息
while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (WM_QUIT != msg.message)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
bExit = TRUE;
}
}
}
break;
default:
break;
}
//GRS_TRACE(_T("开始第%u帧渲染{Frame Index = %u}:\n"),nFrame,nCurrentFrameIndex);
//开始记录命令
//---------------------------------------------------------------------------------------------
// 准备一个简单的旋转MVP矩阵 让方块转起来
{
n64tmCurrent = ::GetTickCount();
//计算旋转的角度:旋转角度(弧度) = 时间(秒) * 角速度(弧度/秒)
//下面这句代码相当于经典游戏消息循环中的OnUpdate函数中需要做的事情
dModelRotationYAngle += ((n64tmCurrent - n64tmFrameStart) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋转角度是2PI周期的倍数,去掉周期数,只留下相对0弧度开始的小于2PI的弧度即可
if (dModelRotationYAngle > XM_2PI)
{
dModelRotationYAngle = fmod(dModelRotationYAngle, XM_2PI);
}
//计算 视矩阵 view * 裁剪矩阵 projection
XMMATRIX xmMVP = XMMatrixMultiply(XMMatrixLookAtLH(XMLoadFloat3(&f3EyePos)
, XMLoadFloat3(&f3LockAt)
, XMLoadFloat3(&f3HeapUp))
, XMMatrixPerspectiveFovLH(XM_PIDIV4
, (FLOAT)iWndWidth / (FLOAT)iWndHeight, 0.1f, 1000.0f));
//设置Skybox的MVP
XMStoreFloat4x4(&pMVPBufSkybox->m_MVP, xmMVP);
//模型矩阵 model 这里是放大后旋转
XMMATRIX xmRot = XMMatrixMultiply(XMMatrixScaling(fSphereSize, fSphereSize, fSphereSize)
, XMMatrixRotationY(static_cast<float>(dModelRotationYAngle)));
//计算球体的MVP
xmMVP = XMMatrixMultiply(xmRot, xmMVP);
XMStoreFloat4x4(&pMVPBufEarth->m_MVP, xmMVP);
}
//---------------------------------------------------------------------------------------------
// 通过资源屏障判定后缓冲已经切换完毕可以开始渲染了
pICmdListDirect->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nCurrentFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
//偏移描述符指针到指定帧缓冲视图位置
CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart(), nCurrentFrameIndex, nRTVDescriptorSize);
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(pIDSVHeap->GetCPUDescriptorHandleForHeapStart());
//设置渲染目标
pICmdListDirect->OMSetRenderTargets(1, &stRTVHandle, FALSE, &dsvHandle);
//---------------------------------------------------------------------------------------------
pICmdListDirect->RSSetViewports(1, &stViewPort);
pICmdListDirect->RSSetScissorRects(1, &stScissorRect);
//---------------------------------------------------------------------------------------------
// 继续记录命令,并真正开始新一帧的渲染
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
pICmdListDirect->ClearRenderTargetView(stRTVHandle, clearColor, 0, nullptr);
pICmdListDirect->ClearDepthStencilView(pIDSVHeap->GetCPUDescriptorHandleForHeapStart()
, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
//===============================================================================================
//31、执行Skybox的捆绑包
ID3D12DescriptorHeap* ppHeapsSkybox[] = { pISRVHpSkybox.Get(),pISampleHpSkybox.Get() };
pICmdListDirect->SetDescriptorHeaps(_countof(ppHeapsSkybox), ppHeapsSkybox);
pICmdListDirect->ExecuteBundle(pIBundlesSkybox.Get());
//===============================================================================================
//===============================================================================================
//32、执行球体的捆绑包
ID3D12DescriptorHeap* ppHeapsEarth[] = { pISRVHpEarth.Get(),pISampleHpEarth.Get() };
pICmdListDirect->SetDescriptorHeaps(_countof(ppHeapsEarth), ppHeapsEarth);
pICmdListDirect->ExecuteBundle(pIBundlesEarth.Get());
//===============================================================================================
//---------------------------------------------------------------------------------------------
//又一个资源屏障,用于确定渲染已经结束可以提交画面去显示了
pICmdListDirect->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nCurrentFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
//关闭命令列表,可以去执行了
GRS_THROW_IF_FAILED(pICmdListDirect->Close());
//---------------------------------------------------------------------------------------------
//执行命令列表
ID3D12CommandList* ppCommandLists[] = { pICmdListDirect.Get() };
pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
//---------------------------------------------------------------------------------------------
//提交画面
GRS_THROW_IF_FAILED(pISwapChain3->Present(1, 0));
//---------------------------------------------------------------------------------------------
//开始同步GPU与CPU的执行,先记录围栏标记值
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
//---------------------------------------------------------------------------------------------
// 看命令有没有真正执行到围栏标记的这里,没有就利用事件去等待,注意使用的是命令队列对象的指针
if (pIFence->GetCompletedValue() < fence)
{
GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));
WaitForSingleObject(hFenceEvent, INFINITE);
}
//执行到这里说明一个命令队列完整的执行完了,在这里就代表我们的一帧已经渲染完了,接着准备执行下一帧渲染
//---------------------------------------------------------------------------------------------
//获取新的后缓冲序号,因为Present真正完成时后缓冲的序号就更新了
nCurrentFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
//---------------------------------------------------------------------------------------------
//命令分配器先Reset一下
GRS_THROW_IF_FAILED(pICmdAllocDirect->Reset());
//Reset命令列表,并重新指定命令分配器和PSO对象
GRS_THROW_IF_FAILED(pICmdListDirect->Reset(pICmdAllocDirect.Get(), pIPSOEarth.Get()));
//GRS_TRACE(_T("第%u帧渲染结束.\n"), nFrame++);
}
//::CoUninitialize();
::HeapFree(::GetProcessHeap(), 0, pstSphereVertices);
::HeapFree(::GetProcessHeap(), 0, pSphereIndices);
}
catch (CGRSCOMException& e)
{//发生了COM异常
e;
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
{
USHORT n16KeyCode = (wParam & 0xFF);
if (VK_SPACE == n16KeyCode)
{//按空格键切换不同的采样器看效果,以明白每种采样器具体的含义
//UINT nCurrentSamplerNO = 0; //当前使用的采样器索引
//UINT nSampleMaxCnt = 5; //创建五个典型的采样器
++nCurrentSamplerNO;
nCurrentSamplerNO %= nSampleMaxCnt;
//=================================================================================================
//重新设置球体的捆绑包
//pIBundlesEarth->Reset(pICmdAllocEarth.Get(), pIPSOEarth.Get());
//pIBundlesEarth->SetGraphicsRootSignature(pIRootSignature.Get());
//pIBundlesEarth->SetPipelineState(pIPSOEarth.Get());
//ID3D12DescriptorHeap* ppHeapsEarth[] = { pISRVHpEarth.Get(),pISampleHpEarth.Get() };
//pIBundlesEarth->SetDescriptorHeaps(_countof(ppHeapsEarth), ppHeapsEarth);
设置SRV
//pIBundlesEarth->SetGraphicsRootDescriptorTable(0, pISRVHpEarth->GetGPUDescriptorHandleForHeapStart());
//CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandleEarth(pISRVHpEarth->GetGPUDescriptorHandleForHeapStart()
// , 1
// , pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
设置CBV
//pIBundlesEarth->SetGraphicsRootDescriptorTable(1, stGPUCBVHandleEarth);
//CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSamplerEarth(pISampleHpEarth->GetGPUDescriptorHandleForHeapStart()
// , nCurrentSamplerNO
// , nSamplerDescriptorSize);
设置Sample
//pIBundlesEarth->SetGraphicsRootDescriptorTable(2, hGPUSamplerEarth);
注意我们使用的渲染手法是三角形列表,也就是通常的Mesh网格
//pIBundlesEarth->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//pIBundlesEarth->IASetVertexBuffers(0, 1, &stVBVEarth);
//pIBundlesEarth->IASetIndexBuffer(&stIBVEarth);
Draw Call!!!
//pIBundlesEarth->DrawIndexedInstanced(nSphereIndexCnt, 1, 0, 0, 0);
//pIBundlesEarth->Close();
//=================================================================================================
}
if (VK_ADD == n16KeyCode || VK_OEM_PLUS == n16KeyCode)
{
//double fPalstance = 10.0f * XM_PI / 180.0f; //物体旋转的角速度,单位:弧度/秒
fPalstance += 10 * XM_PI / 180.0f;
if (fPalstance > XM_PI)
{
fPalstance = XM_PI;
}
}
if (VK_SUBTRACT == n16KeyCode || VK_OEM_MINUS == n16KeyCode)
{
fPalstance -= 10 * XM_PI / 180.0f;
if (fPalstance < 0.0f)
{
fPalstance = XM_PI / 180.0f;
}
}
//根据用户输入变换
//XMVECTOR f3EyePos = XMVectorSet(0.0f, 5.0f, -10.0f, 0.0f); //眼睛位置
//XMVECTOR f3LockAt = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); //眼睛所盯的位置
//XMVECTOR f3HeapUp = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //头部正上方位置
XMFLOAT3 move(0, 0, 0);
float fMoveSpeed = 2.0f;
float fTurnSpeed = XM_PIDIV2 * 0.005f;
if ('w' == n16KeyCode || 'W' == n16KeyCode)
{
move.z -= 1.0f;
}
if ('s' == n16KeyCode || 'S' == n16KeyCode)
{
move.z += 1.0f;
}
if ( 'd' == n16KeyCode || 'D' == n16KeyCode)
{
move.x += 1.0f;
}
if ('a' == n16KeyCode || 'A' == n16KeyCode)
{
move.x -= 1.0f;
}
if (fabs(move.x) > 0.1f && fabs(move.z) > 0.1f)
{
XMVECTOR vector = XMVector3Normalize(XMLoadFloat3(&move));
move.x = XMVectorGetX(vector);
move.z = XMVectorGetZ(vector);
}
if (VK_UP == n16KeyCode)
{
fPitch += fTurnSpeed;
}
if (VK_DOWN == n16KeyCode)
{
fPitch -= fTurnSpeed;
}
if (VK_RIGHT == n16KeyCode)
{
fYaw -= fTurnSpeed;
}
if (VK_LEFT == n16KeyCode)
{
fYaw += fTurnSpeed;
}
// Prevent looking too far up or down.
fPitch = min(fPitch, XM_PIDIV4);
fPitch = max(-XM_PIDIV4, fPitch);
// Move the camera in model space.
float x = move.x * -cosf(fYaw) - move.z * sinf(fYaw);
float z = move.x * sinf(fYaw) - move.z * cosf(fYaw);
f3EyePos.x += x * fMoveSpeed;
f3EyePos.z += z * fMoveSpeed;
// Determine the look direction.
float r = cosf(fPitch);
f3LockAt.x = r * sinf(fYaw);
f3LockAt.y = sinf(fPitch);
f3LockAt.z = r * cosf(fYaw);
if ( VK_TAB == n16KeyCode )
{//按Tab键还原摄像机位置
f3EyePos = XMFLOAT3(0.0f, 0.0f, -10.0f); //眼睛位置
f3LockAt = XMFLOAT3(0.0f, 0.0f, 0.0f); //眼睛所盯的位置
f3HeapUp = XMFLOAT3(0.0f, 1.0f, 0.0f); //头部正上方位置
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
skybox.hlsl
cbuffer cbPerObject : register( b0 )
{
row_major matrix g_mWorldViewProjection;
}
TextureCube g_EnvironmentTexture : register( t0 );
SamplerState g_sam : register( s0 );
struct SkyboxVS_Input
{
float4 Pos : POSITION;
};
struct SkyboxVS_Output
{
float4 Pos : SV_POSITION;
float3 Tex : TEXCOORD0;
};
SkyboxVS_Output SkyboxVS( SkyboxVS_Input Input )
{
SkyboxVS_Output Output;
Output.Pos = Input.Pos;
Output.Tex = normalize( mul(Input.Pos, g_mWorldViewProjection) );
return Output;
}
float4 SkyboxPS( SkyboxVS_Output Input ) : SV_TARGET
{
float4 color = g_EnvironmentTexture.Sample(g_sam, Input.Tex);
return color;
}
texturecube.hlsl
struct PSInput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
};
cbuffer MVPBuffer : register(b0)
{
float4x4 m_MVP;
};
Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);
PSInput VSMain(float4 position : POSITION, float2 uv : TEXCOORD)
{
PSInput result;
result.position = mul(position, m_MVP);
result.uv = uv;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return g_texture.Sample(g_sampler, input.uv);
}
转载自原文链接, 如需删除请联系管理员。
原文链接:DirectX12(D3D12)基础教程(五)——理解和使用捆绑包,加载并使用DDS Cube Map,转载请注明来源!