陶瓷网站建设,网页设计网站建设过程报告,跟网站开发有关的内容,《建设监理》网站当我们在ue4中制作了一个美术材质之后#xff0c;引擎本身会为我们做很多事情#xff0c;它会把结点翻译为hlsl#xff0c;生成多个shader变体#xff0c;并在多个mesh pass中去选择性的调用所需的shader#xff0c;其中一个重要的过程就是获取shader绑定的数据。 本文将主… 当我们在ue4中制作了一个美术材质之后引擎本身会为我们做很多事情它会把结点翻译为hlsl生成多个shader变体并在多个mesh pass中去选择性的调用所需的shader其中一个重要的过程就是获取shader绑定的数据。 本文将主要讨论ue4是如何处理来自材质的不同的输入它们将以怎样的形式传递给shader以怎样的频率更新并在调用层做了怎样的优化处理。
输入类型 我们在材质中能控制输入的地方有两处一个是材质直接输入另一个是材质参数集合 ① 材质直接输入。 我们可以在母材质中开放给shader的参数如美术纹理和参数。静态的参数我们使用静态材质动态可运行时修改的参数我们使用动态材质。 这种输入的特点是每个材质独享一份参数输入因此它在底层设计为每个Material实例独立的Uniform Buffer在ue4中使用FUniformExpressionCache数据结构来描述。 ② 材质参数集合MPC, Material Parameter Collection 我们还可以创建MPC资产可以添加vector或scalar参数。 这种输入的特点是参数可以在多个材质共享比较适合一些全场景的效果控制但一个材质支持输入的MPC数量是比较有限的。 它在底层设计为场景中全局的Uniform Buffer集合同时每个shader引用到的MPC索引由FUniformExpressionCache记录可方便我们快速查找。 材质会生成多个shader变体其中可能包括prepass, shadowpass, basepass的vs和ps根据顶点类型的不同可能还包括了不同的顶点工厂Vertex Factory比如staticmesh, instance或skeletal。这些shader是在代码中定义的每个特定的shader还可以指定一些输入。 ③ 顶点工厂的输入 主要包括的是顶点属性(Vertex Attribute的输入包括位置、法线、顶点色、实例位置等。我们可以使用Vertex Buffer或Buffer传输这些数据。 ④ 顶点或像素着色器的输入 主要包括的是更加底层模块的一些输入比如光照/阴影/大气等的输入。 这部分的输入格式是由代码指定的和材质中主要通过Uniform Buffer来绑定不一样这里输入的格式更加灵活一些共用的数据可能会放在Uniform Buffer中而一些常量Loose Data可能直接绑定到shader中我们还可以绑定一些资源类型的数据SRV)比如Texture2D, Buffer, Structure Buffer等。 ⑤ 全局输入 在多个shader中共享的数据或者一些必需的数据会设计在全局的Uniform Buffer中。 比如在basepass和defer pass中都可能会用到的各种LightUniformBuffer存储灯光方向等信息几乎在所有pass都会用到的ViewUniformBuffer存储相机位置等信息。 还设计了一些高频数据作为独立的Unform Buffer比如逐物件的PrimitivieUniformBuffer等
输入上传 ue4在开始计算场景可见性SceneVisibility前会先尽早地完成一些数据的上传让GPU先开始忙碌起来这样的话可以不阻碍后续的一些渲染工作包括 ① 上传Primitive Uniform Buffer静态物件初始化调用动态物件多帧上传 ② 上传GPU Skin蒙皮物件动画信息 ③ 上传Material Uniform Buffer静态材质初始化调用动态材质多帧上传 ④ 上传Material Parameter Collection更新时上传 ⑤ 上传一些代码中定义的Uniform Buffer如View等 还有一部分数据比如LightmapUniformBuffer一般都是静态的所以不会频繁更新我们也较难捕获到这方面的数据。 单个ub数据上传的时间并不算太长大概是us的量级。但如果场景中使用了大量的动态物件和动态材质整体的上传时间还是比较可观的。
输入绑定 当我们向shader传入特定输入的时候意味着shader中应该有对应的变量。材质中的变量是由ue4自动生成的而代码中则是程序指定的变量。 在整个绘制工作流中我们会首先完成shader的编译并且去收集这些shader中存在的绑定信息。当我们在C中收集绑定输入的实际值时会先去校验shader中对应的绑定点和slot id。 绑定类型 ue4的Mesh Draw管线中我们使用Shader Binding来完成这一点它是一种延迟的设计因为它会先去收集所有可用的绑定实参提交前再调用实际的RHI层的绑定。 它支持的类型包括Uniform BufferSamplerSRVtexture, bufferLoose Data顶点的输入则由Input Stream负责不包含在Shader Binding负责的范畴中。所有的绑定信息我们可以认为是一个Input Layout它可编码为缓冲区。 其中Uniform Buffer, Sampler, SRV记录的是实际分配的引用是一种分离式的设计而Loose Data是我们直接绑定在shader上的参数存储了实际的数据可以理解为一个内联的常量数据。 API映射 我们在API中完成一次drawcall通常会设置首先去各种状态量和绑定量包括 ● SetPipelineState ● SetVertexBuffer/ SetIndexBuffer ● SetShaderBinding 对于CPU端来说消耗主要体现在数据的准备和调用上实际指令执行的过程中GPU也会产生状态切换的消耗。 在API底层如在Vulkan中Shader Binding的调用会被映射为vkCmdBindDescriptorSetsdx12则相对复杂它可能会映射到SetGraphicsRootConstantBufferView或SetGraphicsRootDescriptorTable等。 Vulkan的设计可能会产生更少的调用而DX12的设计会更加适合输入排列的复用但大多数的游戏引擎并不会优化到这么细致。
输入调用 在输入调用上Shader Binding之所以要设计为延迟调用是为了尽可能缓存一些绑定命令减少CPU端渲染指令调用的次数。缓存的可复用性依赖于绘制对象的排序我们应该尽可能把共享相同状态的对象合并到一起。 在缓存机制上我们可以去缓存的内容包括PipelineState它包含的最重要内容就是Shader如果Shader Code完全一致仅仅是输入不同我们是可以缓存的。其次是一些输入比如Uniform Buffer SRV等这些数据的缓存会对一些图形API产生收益。 Vertex Buffer和Index Buffer是比较特殊的阶段我们没有办法去缓存这些数据。 实际实现中Shader Binding会去维护一个缓存的状态只有在绑定发生变化的时候才去实际调用RHI层的设置接口当我们把具有相同状态的对象排列在一起时尤其是使用相同Shader的物体缓存优化会得到较好的收益。