| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 游戏开发 -> ShaderLab流程 -> 正文阅读 |
|
[游戏开发]ShaderLab流程 |
ShaderLab结构ShaderLab是Unity构建的一种方便开发者做跨平台Shading开发的语言体系,它主要包括如下四种: ShaderLab Text
ShaderLab Compiler
ShaderLab Asset
ShaderLab Runtime在unity的Profiler里可以看见 ?ShaderLab工作流在我们创建、修改或者导入Shader进Unity系统的时候,Unity的Shader并不是一次性的编译到一个目标平台上的。Unity会把原始的ShaderLab Text发给ShaderLab Compiler的预处理器(Preprocessor)做一次预处理(Preprocess)。 ? ? ? ?Prepocess(预处理):先检查写的shader有没有问题,有问题就会报错,shader报错就是这一阶段产生的。解析完后会把每一种不同的语言Shader Program从Shader Text里切割出来,切割出来后再用对应语言(例如HLSL)的Preprocess Compiler做一遍对应于这个语言的解析检查。通过这几次的检查之后,最终会得到一个完整的Shader Compilation Info,然后写到ShaderCache里。
在unity2020版本Unity引入了一个新的Preprocessor:Caching Preprocessor.它可以使Shader的编译更加快速,可以在project Setting或者选中某个Shader进行单独设置勾选这个选项? Binary compile?前面提到的Preprocess是运行在编辑器下,Unity Editor拿到了Shader Compilation Info,但是它并不能用于渲染,也不能打到最终的包体里,它只是Unity所使用的一种中间状态。那么如何把它最终编译成可运行的版本呢? Shader Compiler里面包含了很多的服务,除了Preprocess外,还有一个叫作Binary compile也就是将我们Shader Program里面的代码输出到对应平台上去。Preprocess后,Unity会把Shader Compilation Info(可以从ShaderCache里取,如果里面没有,就走一遍Preprocess,重新产生Shader Compilation Info)再送到Shader Compiler里,执行Binary compile。 那么什么时候会触发这个过程呢?
综上所述,当我们知道中间的东西最终需要翻译成什么,输出到什么设备上去,当一个平台确定了后这个过程才会发生? 运行? ? ? ?编译完了之后就来到了运行时,这个时候要真正的在真机上把Shader给跑起来了(这里说的不考虑编辑器运行的情况)。真机发起的入口一般是用户的代码,这个用户代码可能是各位写的Warmup,也可能是通过某些引用调用UnityAPI,API再去调用底层。我们把Unity的上层系统简单的抽象成User Code,当User Code说 I need a shader,这个时候去加载一个Shader,怎么加载呢?? 有了Instance后,最后把它叫醒即可,也就是Awake操作,实际上名字叫Awake from main thread,从主线程唤醒,这个里面会做一个类似于我们的C# Awake或者是Start里面做的工作。比如说我们的数据填充进来要做一些处理,有些工作是不能在构造函数里做的,必须在数据进来之后才能做。比如说要确定使用哪个SubShader,如果SubShader的数据都没进来呢,那就没法先确定。再比如使用SRP,我要构建SRP Constant Buffer的结构,如果Shader数据没进来,同样无法构建。所以数据进来之后我们还有个处理操作,就是Awake from main thread。? 除了Shader Class Instance外,Unity内所有的类在构造的时候基本都是这三步(当然了每个类的行为不太一样):
Warmup VariantShader加载进来之后呢,我们经常面临的另外一个问题就是Warmup,此时Unity到底干啥了,为什么有的时候感觉Warmup这么卡? 举个例子, 假如我们的Shader Program如下:
那么运行时的产生的Shader Class Instance里面一共有四种变体组合,TEST1 TESTA、TEST1 TESTB、TEST2 TESTA、TEST2 TESTB,如下图(省略了TEST): 变体实际上是Unity带给大家的一个语法糖。大家都知道所有的糖都是很好吃,但是很有害的,语法糖也一样。大部分语法糖最终都会导致你的代码体积膨胀,比如说我们写c++、c#用到的模板泛型,最终都会导致你发胖。Shader Variant也是一样,当我们用大量的变体排列组合的时候啊,实际上它会把每一种排列组合单独的变成一段代码(点击Compile and show code即可看到)。它们在内存中都是单独的一套完整代码,每个变体都是一个独立的个体,它们彼此之间没有任何联系,通过大家给出的keyworld的排列组合进行索引的。 如果此时我们用Shader.EnableKeyword的API去Warmup 1A,那么使用这个Shader的物体就会变为红色,说明Variant 1A Warmup成功了。但是如果我要去Warmup 2C,由于没有TESTC这个keyword,也就没有Variant 2C,那么又会怎么样?首先不会发生fallback,因为fallback的前提是这个Shader Class Instance没了。其实通过代码测试一下,会发现物体变为了蓝色,也就是Variant 2A被Warmup了。 这是因为Uinty会有一套奇特的打分机制,它会根据你给出的keyworld和现在所有的keyworld进行一个打分。比如说我Enable了 TEST2 和 TESTC,先会拿TEST2到里头去找,看它在不在我有的排列组合里,如果这个排列组合里有TEST2,那么它会得到一个比较高的分。然后再去找TESTC,里面如果没有TESTC再去减分。通过这样的机制分别对拥有的变体进行打分,最后打出来分最高的那个变体,就是Unity要给你的。但是至于是不是你想要的,Unity就不管了,因此上面的例子我们会得到Variant 2A。所以有的时候会出现,有些Shader效果你看起来差不多,但是不太对,可能就是你的变体没有打进去,但是Unity为了保证不崩溃,选了一个打分最高的变体还给你。 当我们去Warmup 一个变体的时候,实际上我们在内存的统计上是会看到一点点变化的,是什么意思呢?我们知道一个变体底下会带有一段代码,一段Binary Code,这段Code在内存里是要占大小的。那么这段Code会一直在内存里面吗?不会,当我们成功的Warmup了某个变体之后,该变体的Code在CPU里的内存就消失了,因为它已经到GPU那块了,到显存里去了,所以Unity会很聪明的帮你把它删掉。示意图如下: 所以当大家观察到我们的ShaderLab非常非常大的时候,那么有一种可能是你打包了非常多的变体进去,但是很多其实你都没有用,都留在了CPU这一端。因此对于不是C#控制的keyworld我们应该使用shader_feature而非multi_compile,这也是一个优化上的小技巧。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/27 20:23:13- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |