渲染引擎开发笔记5

DX12绘制方块初始化

Posted Kongouuu's Blog on December 23, 2021

前言

上一章节主要是构思在渲染一个物体时引擎端和渲染器端的交互。现在开发进度已经到在做阴影贴图了,路上也对整个架构进行了许多的迭代和更改。总的来说上一章节的想法并没有太大的问题,不过有一点是我前面的思路太过于想要让不同的模块变得独立,反而起到了一些反效果。我的渲染器本质上就应该是适配我的引擎的,所以有很多的信息是他们应该能得到的,比如说输入格式/变换矩阵/材质格式等信息。

模块化的最终目的是让我的引擎可以通过一个不分平台的Renderer Interface去使用不同的图形API,而图形API封装的部分则是完全适配于我当前的这个引擎的设计的,也就是说他们有权知道更多的信息。个人现在更倾向于让渲染器内部自己去控制所有的渲染相关的流程,比如说加载哪些Shader,怎么生成Shadow Map等, 而引擎部分只是单纯负责要不要启用Shadow Map这样就好了。简单来说就是让渲染器更渲染器一点,减少引擎对渲染部分的控制,引擎部分只需要管好需要渲染的物体的部分信息即可。

这里的话讲一下单从渲染器侧应该怎么去准备绘制一个方块。实际上之前也有整理过DX12的从建立到渲染的流程。不过对物体的渲染其实讲的比较笼统一点,并且也包含了初始化等信息。

初始化

大致初始化

整体流程差不多是这种感觉,这里提一下部分细节的储存方式

摄像机/变换/材质

摄像机变换和材质这些信息都是我们会线储存在引擎侧的,因为那是对每个物体的形容,即使在没有渲染器的情况下也是应该要储存好的信息。摄像机是属于每个Pass要共用的,而变换材质则是每个物体都有自己独特的信息的。

这些信息在DX12里我们都需要先申请出一块内存,然后把原始数据塞进去,使用的时候会根据Shader里面的形容去读解我们塞进去的原始数据。这里我们是给Pass Constant申请出一份(sizeof(PassConstant))的内存,然后变换和材质都申请(sizeof(ObjectConstant/Material)*N)的内存,后续只要用对应的Offset就能替换不同物体的变换内容。

材质

材质在引擎侧只储存了地址,名字,和图形API返还的id

我们会把需要用到的材质交给DX12去加载资源,虽然龙书用的是dds格式,不过一般我们拿网上的模型他多半用的是png,所以这里我用了WIC去把一个贴图储存到对应的Resource里。

DX12,贴图单单储存到一个资源里是不够的,我们要给每个贴图绑定到一个独立的SRV(Shader resource view) 才可以传到Shader里。在加载贴图的时候我们没有办法进行绑定的操作,因为我们还没有初始化整个Pipeline

我们对SRV RTV DSV堆的描述符是一次性建成的,也就是说在建立整个堆描述符的时候我们需要已经知道使用了多少个材质,所以材质这块是分在两个不同的步骤来处理。

实际初始化

大致上的流程有的,不过细节还是欠缺的,比如说怎么去加载一个物体。加载一个物体首先需要的是一个Mesh的定义

Mesh (面片)

面片是对一个物体的顶点和索引的形容,基本上就是在我们引擎侧去储存一个物体的顶点信息和绑定的贴图id的东西。 目前开发中的Mesh是这种感觉的:

这里特别提一下顶点输入就是最经典的: [Position, Normal, Tangent, Bitangent, TextureUV]的形式

方块

有了Mesh类后我们就可以非常简单的手写出一个方块的形状,包含了法线和贴图的信息。这里因为还不考虑法线贴图所以其他信息保持为0

Render Object

当然只有几何信息是不够的,我们还需要坐标信息的咨询,所以我们在几何外面再包上一层:

这里的话因为目前还没有加入Scene Graph的思想,为了简单的适配之后加载模型的情况,所以暂定让一个Render Object持有多个Mesh,换个说法就是多个面片会共享同样的变换信息。

后续

其实上面就把需要的东西差不多包装完了,比较基础的摄像机什么的跟设计思路关系不大所以这里就不提了。

有了这些信息之后我只需要发送给图形API去加载,图形API就会返还给我的引擎侧每个面片对应的id,这样方便之后去进行操作。我这里的设置流程是:

  1. 引擎侧Load Scene (一次性)
  2. 引擎侧去更新物体的信息,并且通知渲染器
  3. 引擎侧呼叫渲染器的Render函数,渲染器内部把所有RenderItem都渲染了

我之前也看过有一些做法是在引擎侧的每个物体里面放一个比如说DX12Mesh的数据结构,然后在每次渲染的时候跑这样的代码:

1
2
3
4
5
6
7
8
9
10
11
void EngineRender()
{
	for(auto& renderObject : allRenderObjects)
	{
		for(DX12Mesh& m : renderObject.meshes)
		{
			if(...)
				m.render();
		}
	}
}

我个人觉得这样其实也是可行的,如果把mesh->render()函数当作在渲染器侧放到一个渲染队列中这样;不过一想到很多每个帧都肯定要绘制的物体还要在每个帧反复的重新添加到一个队列中就让我并不是很想这么操作。