跨平台引擎Shader编译流程分析

跨平台引擎Shader编译流程分析

背景

过去的一年(2018),笔者参与了一个比较特殊的Unity手游项目,由此开始了Unity学习的旅程。这个项目是由Unity4版本制作同时有整合原来公司自研引擎部分代码,结构比较混乱,这一年笔者也参与了项目的Unity2017/8的升级之路。

项目历史跨度较为久远,由于使用了OpenGL实现的NativeRenderPlugin,所以仅支持OpenGL的渲染,随着OpenGL逐渐退下历史舞台,项目也越来越急迫地需要被升级至最新的Unity版本。

在这段工作中,笔者将一些笔记记录在这个专栏(原先想写Unreal Insight的。。。),一边日后参考。

这篇文章主要记录一些引擎中的材质系统以及Shader编译上的思考。

Unity和Unreal都是跨平台的游戏引擎,而跨平台的基础包括材质描述语言(Shading Language)以及渲染器。在Shader语言上,Unity和Unreal都选择了广为使用的HLSL,这也不可避免的需要实现跨平台的Shader编译方案。

Shader编译上的差异

先看UE4.22的shader编译流程:

首先,Epic Games是一家拥有悠久历史的游戏引擎公司,UE4自己实现了SM5 Level的HLSL前端(使用Flex/Bison工具),结合MesaIR以及开源的Glslang库实现了一套多API兼容、多设备特性兼容的语言翻译器。

shader model 6+(wave intrinsics, explicit 8bit/16bit data type)的特性只能在DXC/glslang编译环境下使用,当然也要判断设备是否支持。
Metal 3已经支持shader动态库。

再看Unity3D的Shader编译流程:

Unity是一个小巧,并拥有精致内核的引擎,在编辑器层面实现了D3DCompiler.dll的跨平台(参考了Wine的PELoader),然后再通过DXBC翻译至多平台Shader语言,同时也能支持一些设备特性,由于D3DCompiler.dll是X86的指令,所以它的编译方案不支持移动设备上的HLSL Compute Shader编译。

现在到了2019年,微软基于LLVM/Clang生态开源了DirectXShaderCompiler,Google贡献了SPIRV的生成器,NVIDIA也在这个项目上提供了RTX SPIRV的翻译,DXR/RTX也将要开始推广了,如何翻新新的跨平台Shader编译方案?

目前,开源的支持HLSL编译的方案主要有微软的DXC以及Khronos的Glslang两个项目,DXC HLSL是微软官方和Google维护,Glslang HLSL主要是Google维护。

NGFX方案

Unity与UE材质系统的差异

Unity这边主要使用ShaderLab作为材质描述语言,ShaderLab可以描述RenderPass以及RenderPipelineState,这对于未来渲染起的扩展是有益的,比如Prebake PipelineState Object;而UE使用可视化节点表达式描述材质,两者各有优缺点。UE的材质系统更适合美术使用,但控制不好容易排列组合爆炸,拖慢项目迭代进度;Unity的ShaderLab的排列组合更好控制(手动),这也带来了更快的迭代速度。

UE的基础Shader修改也容易导致大量材质重新编译,当然你也可以使用Custom Shader Plugin或者自定义Shader节点或者直接固化成ShadingModel实现相应的功能,

Unity 2017/8之后,官方也提供了与UE4类似的节点式材质编辑器


材质Shader系统内卷的最高境界

NGFX Material System
仅支持Metal/Vulkan,仅支持二进制IR翻译

统一的材质描述,shader编译过程没有跨文本语言翻译过程,大幅提升生产迭代(这里指编译)效率,设备上可以运行时根据硬件架构特性和驱动生成最佳字节码。

2023年的WWDC,苹果的Metal Shader Converter开始支持微软DirectX的DXIL字节码,未来Metal应该会更容易兼容Direct3D,扩展Apple的游戏市场。

Metal shader converter - Metal - Apple Developer


Shader语言上的思考

目前主流的实时渲染Shader文本语言主要有HLSL,GLSL,Metal SL,他们都基于类C语言发展而来,天生不能很好的支持模块化Shader编译(编程语言模块化的支持能一定程度提升编译效率)。因此曾经发明了CG语言的NVIDIA,在自家的Falcor渲染器上又基于HLSL的语法发明了Slang语言,来更好地支持模块化以及硬件特性扩展。

Metal Shading Language是基于C++14而来,Metal编译器生成的Binary Shader实际上是标准的LLVM BitCode,如果将Metal的内置函数一一对应到SPIRV的内置函数/类(metal::rendercommand/metal::commandbuffer相对比较复杂),可以轻松的完成Metal Bitcode到SPIRV的二进制翻译;虽然Metal SL编译器闭源,但网上也人在LLVM的基础上实现了开源的Metal SL编译器(Floor),目前的Metal API不提供Binary的Shader导出,得必须使用MacOS的Metal离线编译器才能生成Binary Code(用于Shader Cache提升加载速度),因此Floor或许是一个值得关注的项目。

也有开发者基于LLVM Clang C++增加自己的语法实现了CPU和GPU代码的统一编译和代码生成(CircleLang):

基于近年来,硬件厂商和游戏引擎厂商密切合作,不断开放硬件底层的编程能力(WaveFront/Warp Level并行编程,精确的数据类型uint8/16...),未来Shader语言又会如何进化呢?

编辑于 2023-06-06 17:46・IP 属地上海