从DotsInstancing到GPUResidentDrawer(一,DotsInstancing)
Unity DOTS架构通过SoA数据组织优化CPU性能,其Entities Graphics渲染插件(原HybridRenderer)利用BatchRenderGroup实现非GameObject数据的渲染。Unity 6的GPU Resident Drawer技术进一步优化批次处理,减少DrawCall。DOTS Instancing将属性数据合并为RawBuffer,通过MetaData实现
Dots作为Unity构建面向数据的架构逻辑的一种技术栈,其内部对数据的组织形式为SoA等特性以最大化的发挥CPU的性能
在早期版本中,并没有对Dots推出渲染的支持,直到官方MegaCity Demo发布时,Unity对其定制了一套渲染的机制,后续对该机制进行拓展并可以用在所有使用Dots的渲染上,该渲染机制最初命名为Hybrid Renderer,之后改名为Entities Graphics,该插件在渲染时使用了DotsInstancing技术利用BatchRenderGroup来将不符合目前Unity GameObject形式的数据以物体的形式渲染出来
之后在Unity6发布时作为其渲染的一大亮点的GPUResidentDrawer技术,其内部仍然是使用了上述BatchRenderGroup技术来将场景内物体必要的渲染数据(Layer MeshID MaterialID等)拆分为多个NativeArray并将其组织为一个GroupData传递给全局唯一BatchRenderGroup对象,此时物体的MeshRenderer对象中IsVisible属性为False即不参与原生渲染逻辑,经过内部数据内存保存与组织为最优的批次后,在OnPerformCulling中组装为DrawRange与DrawCommand后实现渲染,即可以自动将场景物体渲染的有着更优批次与更少的DrawCall
DotsInstancing
为了实现Dots 数据的渲染,Unity借助SRPBatcher的流水线将组织的数据传递给GPU,并利用实例化渲染的接口来进行绘制
在Dots中物体渲染的必要数据分别存放到各自的Buffer中,为了实现一次发送必须将多个Buffer合并为单个RawBuffer,以物体的位置信息,BaseColor信息为例,我们将其合并的Buffer如下图所示

在将Buffer发送到GPU后,若我们渲染N个物体,则每个物体的InstanceID从0到N-1,我们组织的各个属性的Buffer也存储了N个对应数据,以上图Buffer为例,前0到N-1的属性存储的是位置信息,之后N到2N-1的属性存储是BaseColor,为了使得渲染时能够正确得到对应的属性偏移,Unity内部编写了一套偏移机制。在C#层的BatchRenderGroup对象中,我们在注册需要传递的RawBuffer时必须注册对应MetaData,其中MetaData便包含着数据偏移的索引值

上图中位置信息与其他信息以对应的MetaData在RawBuffer中正确偏移到对应的位置
MetaData由ShaderPropID与对应的偏移量的特殊处理格式组成,ShaderPropID为Shader.PropertyToID("Shader属性名"),偏移量的特殊处理格式为0x80000000 | byteAddressProp,对属性的地址比特偏移做0x80000000的处理是为了让属性值进行正常区分化与统一化,若对属性地址进行0x80000000的处理,则每个实例都会使用对应的偏移的属性值,若使用0x00000000则每个实例都会使用默认值(在hlsl文件中会有解释)

在Shader层,SRP中有一个UnityDOTSInstancing.hlsl文件,在文件中有着渲染时的索引机制
下面将以URP标准渲染Shader Lit.Shader来对该文件进行说明

在LitInput.hlsl文件中UnityPerMaterial CBuffer下存在着有关Dots的属性块其中UNITY_DOTS_INSTANCED_PROP宏定义在UnityDOTSInstancing.hlsl中


在该文件顶部,可见UNITY_DOTS_INSTANCED_PROP_OVERRIDE_DISABLED_BY_DEFAULT并没有被定义,即采用UNITY_DOTS_INSTANCED_PROP_OVERRIDE_SUPPORTED模式

OVERRIDE_SUPPORTED模式与OVERRIDE_DISABLED模式区别在于是否默认关闭属性值重写,即是否会被MetaData偏移获取在RawBuffer中对应的值覆写掉,该模式默认是打开的
即在默认情况下使用UNITY_DOTS_INSTANCED_PROP(float, _BaseColor)会默认替换为以下形式

可见这里定义了两个值,一个是uint UNITY_DOTS_INSTANCED_METADATA_NAME(type, name),另外一个是static const int UNITY_DOTS_INSTANCED_PROP_OVERRIDE_MODE_NAME(name) = kDotsInstancedPropOverrideSupported
参考文件中宏定义

可知第一个值就是拼接了一个uint类型的属性,第二个值则是说明该值是支持覆写的
可知UNITY_DOTS_INSTANCING_START属性块定义了一系列uint值与其对应的是否支持覆写的参考值
对于如何使用上述值,在Unity文档中可知可以使用以下宏定义来获取
UNITY_ACCESS_DOTS_INSTANCED_PROP
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT
UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT

在LitInput.hlsl中获取属性值使用UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT,以WITH_DEFAULT为例,其宏定义为以下所示

该段中有着多个其他宏定义,第一个UNITY_DOTS_INSTANCED_PROP_IS_OVERRIDE_ENABLED判断这个值是否支持覆写,若支持则会进行LoadDOTSInstancedData_##type操作,由于默认是开启覆写,属性获取都会走该操作,该方法如下所示

第二个参数metadata由UNITY_DOTS_INSTANCED_METADATA_NAME获取即上文Dots属性块中每个属性生成的uint类型的值,可知该值是用于偏移的MataDataValue,在使用metadata时调用了IsDOTSInstancedProperty方法,此处出现了上文中的0x80000000,在此可知若首位为1即0x8时会进行地址偏移Buffer获取对应的属性值,不然则直接返回传递的default_value默认值


以UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_DEFAULT(float, _BaseColor)为例,default_value为传递来的_BaseColor值,该值为UnityPerMaterial中的值
或者在使用UNITY_ACCESS_DOTS_INSTANCED_PROP_WITH_CUSTOM_DEFAULT该方法来获取值时default_value为该方法自定义第二个参数值
另外,在Unity的Instance相关的hlsl文件UnityInstanceing.hlsl中,也有一些关于DotsInstancing的声明与宏定义

这里主要对使用DotsInstancing时获取实例化属性的宏进行Define与实例化渲染在设置InstanceID时进行一些函数的调用,一些是固定来设置PerObject的属性一些用户可以进行Define,例如在LitInput.hlsl文件中对Dots数据的获取进行了Cache操作,由于每次获取Dots数据都需要使用UNITY_ACCESS_DOTS_INSTANCED_PROP相关方法来在RawBuffer中偏移取得,会有开销产生,因此这里Unity设置InstanceID时声明了UNITY_SETUP_DOTS_MATERIAL_PROPERTY_CACHES的宏定义,该宏定义主要是为了缓存获取的Dots属性,在LitInput中可见这些代码

这些主要定义了静态的属性,进行缓存在RawBuffer获取的对应的属性值,之后在将原始属性名Define为静态的属性名来达到缓存的目的
(例如#define _BaseColor unity_DOTS_Sampled_BaseColor,由于unity_DOTS_Sampled_BaseColor已经是在SetCache方法进行值的获取,之后将原始的_BaseColor定义为unity_DOTS_Sampled_BaseColor,后续在Shader代码的值便都是后者了)
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐


所有评论(0)