揭秘Unity的黑盒世界,“ShaderLab”底層原理淺談

文章來源: 作者:frank 發(fā)布時(shí)間:2021年09月01日 點(diǎn)擊數(shù):

在閱讀本文之前我們首先需要弄清楚什么是 ShaderLab?

ShaderLab是由Unity發(fā)明或者說是由Unity首創(chuàng)的一種語言體系,用來幫助大家做跨平臺Shading開發(fā)。它里面除了語言規(guī)則之外,還有很多其他的東西。我們一個(gè)個(gè)來看。

我們先來看看ShaderLab Text(就是它的文本)。

我們大家知道,無論是寫代碼,還是寫shading,實(shí)際上我們寫的都是一堆文本。雖然在IDE 里看起來“花里胡哨”的,但是對計(jì)算機(jī)來講就是文本。大家能看到,這就是我們非常熟悉的Unity的Shader,這個(gè)東西叫ShaderLab文本,是根據(jù)Unity定義的語法規(guī)則來寫的。

我們可以看到,在基礎(chǔ)文本里面會包含一些塊,比如說Shader的名字,在Subshader里面也有它自己的屬性,比如Tag、LOD等等。在Subshader下面還有一個(gè)個(gè)Pass,在Pass里面我們還可以定義不同的pragma、Vertex、Fragment,而其中的每個(gè)都對應(yīng)著不同的代碼塊。

這是ShaderLab第一個(gè)組成的部分,我們稱之為ShaderLab文本。

但光有文本是不行的,就如同你寫C++,如果只是寫了一堆CPP文件,依然是無法被計(jì)算機(jī)認(rèn)可并執(zhí)行的。中間需要有翻譯的過程,這就是Shader Compiler的過程。如果大家在 Windows或者是Mac上有留意過的話,就會發(fā)現(xiàn),打開Unity之后再打開任務(wù)管理器,或者在Mac上打開active monitor,你都會看到一個(gè)叫Unity shader Compiler的東西。如果是早期的Unity版本,你看到的就可能是cgbatch。

這個(gè)東西是干什么的呢?實(shí)際上它類似一種服務(wù),是Unity在后臺提供的一種服務(wù),用來幫助我們?nèi)グ褜懞玫腟haderLab語言翻譯為目標(biāo)機(jī)器能夠認(rèn)可并執(zhí)行的語言(我們會在下文中講解大概是怎么翻譯的)。

這是第二個(gè)組成模塊。

第三個(gè)是一個(gè)體系,我們稱之為ShaderLab Asset。我們寫的ShaderLab大部分是不會進(jìn)入到最終的運(yùn)行環(huán)境中去的,需要經(jīng)過二次加工。ShaderLab里面有很多東西都是不能直接使用的,需要進(jìn)行翻譯。而加工之后的東西,我們叫它Asset(資產(chǎn)),這個(gè)Asset比較常見的是這兩個(gè)地方。

第一個(gè)地方,是我們打Assetbundle,這是大家最常見的,我們把shader 打成一個(gè)包,放到 bundle里。

還有一個(gè)就是在我們打出來包的Resources下面,會有l(wèi)evel或者是 SharedAssets 這樣的包,其實(shí)和Assetbundle的文件結(jié)構(gòu)是很類似的,比如說場景里面直接引用的一些東西,大家既愛又恨的Always include Shader也在這里面。

這是兩個(gè)Asset比較常見的地方。

但是有一個(gè)地方,大家不經(jīng)常會用到。當(dāng)我們在 library 文件夾里打開一個(gè)工程,就會看到一個(gè)叫 ShaderCache 的文件。我們在做預(yù)處理之后的中間產(chǎn)物,都會放到ShaderCache里面。

我們都知道Unity特別喜歡使用GUID做索引的,這個(gè)看起來和我們的library里面的data的東西很相似,也是一堆以數(shù)字或字母開頭的文件夾,然而我們現(xiàn)在看到的這個(gè)也是ID但不是GUID。如果大家曾經(jīng)拆過Unity AssetBundle,你會看到你寫的代碼。

你會經(jīng)常看到整個(gè)從CGPROGRAM到ENDCG,在你的Asset里看不到了,變成了一個(gè)名字叫 GPU program IDXXX的文件,用一個(gè)ID索引了這一整段。在里面我們可以很簡單地認(rèn)為里面存的就是大家寫的CG program中間的東西,當(dāng)然不是直接存進(jìn)去的,是經(jīng)過了一系列的加工。這是我們存儲幾個(gè)shaderAsset的地方。

最后還有一個(gè)大家會經(jīng)常遇到但是往往被忽略掉的事情——ShaderLab是有Runtime的。既然是一種有語法結(jié)構(gòu)的語言,會包含很多的信息,Runtime能不能把這些信息利用起來就變得很重要。不然的話寫了半天,打上Asset也沒人用,就白白浪費(fèi)掉了。

這個(gè)東西經(jīng)常在哪兒見到呢?答案就是在Memory里面,如果你去使用一個(gè)Sample,你會在ShaderLab里面看到它。

這也是大家問題最集中的地方,為什么我ShaderLab這么大,到底是哪個(gè)Shader大?其實(shí)不是Shader大,而最有可能的是ShaderLab 整體很大。

所以整個(gè)Unity的ShaderLab大致分為四塊,分別是由ShaderLab Text、shaderLab Compiler、shaderLab Asset以及shaderLab Runtime四個(gè)部分組合而成的。

我們了解了 ShaderLab 之后,再簡單地看一下 ShaderLab 的工作流。

首先,當(dāng)我們?nèi)プ?Shader 的時(shí)候,第一步就是去寫ShaderLab的Text,寫完了之后干什么呢?寫完之后你會發(fā)現(xiàn),當(dāng)你回到Unity的時(shí)候,Unity會開始有一個(gè)編譯的過程,如果你第一次導(dǎo)入了很多的Shader,這個(gè)時(shí)候ShaderCompiler就開始工作了。

Unity的Shader 不是一次性編譯到一個(gè)平臺上的。

那么這個(gè)名為ShaderCache的東西,它是在什么時(shí)候產(chǎn)生的呢?其實(shí)在 shader被import進(jìn)Unity系統(tǒng)的時(shí)候,Unity 會把原始的shader文本發(fā)給shaderCompiler去做一次預(yù)處理,預(yù)處理的結(jié)果并不是針對某一個(gè)平臺最終的文本結(jié)構(gòu),這個(gè)時(shí)候編譯出來的東西叫shader compilation info,是一個(gè)中間狀態(tài)的一個(gè)信息集,這個(gè)信息集里面包含了很多重要的東西。以下是其中比較重要的幾點(diǎn):

第一,你的變體。我們知道 Unity 引入了 multi compile 和 shader feature 之后,通過一次編碼就可以產(chǎn)生大量不同的 Shader。第一次我們?nèi)ヌ幚沓鲎凅w的概念,是在我們做 Preprocess 的時(shí)候出現(xiàn)的。經(jīng)過 Preprocess 第一次的處理,在 shader compilation info 里面,就已經(jīng)把各個(gè)變體分開了。

當(dāng)我們?nèi)グ?shader compilation info 編譯出來之后,會把相關(guān)的信息序列化,并且寫到我們 ShaderCache 里面,這就是大家所看到的 ShaderCache。這個(gè)ShaderCache的信息會被用于我們后面的一些加速編譯,不需要每次進(jìn)入Unity都重新走一遍過程。當(dāng)你的shader比較大,變體比較多的時(shí)候,Preprocess的過程是相對比較慢的。Preprocess的過程,如果我們更細(xì)化地說,實(shí)際上做了以下幾件事情。

第一個(gè)是做語法分析,比如說我們解析語法數(shù),就會生成詞法解析器和語法解析器。我們先去做了一次語法解析和詞法解析,當(dāng)然在這個(gè)過程中Unity就會去檢查大家的shader寫得有沒有問題,如果有報(bào)錯(cuò),這個(gè)階段就完成了。

當(dāng)我們解析完了之后,Unity會把每一種不同的語言,從shader的文本中對應(yīng)的部分切割出來。切割出來之后,再用對應(yīng)的語言的Preprocess compile去做一遍對應(yīng)這個(gè)語言的解析檢查。通過這幾次檢查之后,最終我們會得到完整的compilation info,再把它寫到ShaderCache 里面。

如果大家在去做一個(gè)Shader的時(shí)候,發(fā)現(xiàn)寫完的這個(gè)Shader好像不太對,或者有點(diǎn)問題,你感覺沒有進(jìn)行重新編譯,最簡單的方法就是把ShaderCache刪掉,然后再強(qiáng)行導(dǎo)入一次,重新編譯一次,這時(shí)候問題就迎刃而解了。

我們把它編出來,放到ShaderCache 里之后,這個(gè)時(shí)候只是Unity editor拿到了Compile 這個(gè)東西,但并不能用于渲染,也不能打到最終的包里。它只是Unity所使用的中間狀態(tài),如果是編譯的話,大概就類似于IR的東西。

我們?nèi)绾伟阉罱K編譯成可運(yùn)行的版本呢?

我們可以從shader Compilation info,或者是ShaderCache里面找到相應(yīng)的文件。這取決于你有沒有,如果有的話,就能從shadercache里找到;如果沒有的話,就會走一遍Prepocess的過程,再重新產(chǎn)生shader Compilation info。

拿到之后,我們會把這個(gè)東西再送到shader Compiler里面,再做一些其他的事情。這個(gè) shader Compiler里面包含了很多不同的服務(wù),剛才是Preprocess,這次我們要做的就是Binary Compile。這個(gè)事情會在以下幾種情況下發(fā)生。

第一,我們現(xiàn)在啟動了Unity。我們把資源都導(dǎo)入了,點(diǎn)擊play。點(diǎn)的時(shí)候,Unity 會做一件叫Unity Editor warmup all shader的事情(當(dāng)然在第一次導(dǎo)入的時(shí)候,Unity 也會做)。這就是為什么2020年之前的版本,大家在點(diǎn)開始的時(shí)候,會經(jīng)常感覺到卡半天。實(shí)際上,“卡”的過程會把你內(nèi)存里面,或者是資源里面所有shader的變體都warmup。但是真機(jī)上不會卡。

大家在去做一些性能檢查,包括去研究原理的時(shí)候你會驚訝地發(fā)現(xiàn),Unity 實(shí)際上是兩個(gè)版本,運(yùn)行時(shí)和編輯期是兩套完全不同的東西。所以我們在做性能分析,或者是內(nèi)存、CPU、GPU 分析的時(shí)候,不要在編輯器里面做。編輯器的設(shè)計(jì)目的是為了幫助大家以最流暢的速度去編輯,所以有很多的東西,不會去考慮運(yùn)行時(shí)資源環(huán)境的占用,比如CPU或是內(nèi)存的占用。Unity會默認(rèn)認(rèn)為你的電腦非常棒,內(nèi)存不會爆,CPU不會卡,所以它可以盡情地?fù)]霍這些資源,盡量保證大家整體的編輯體驗(yàn)是好的。但是在運(yùn)行時(shí),Unity會考慮實(shí)際的運(yùn)行環(huán)境。比如手機(jī)和PC上的策略會有一些差異。

在這個(gè)地方我們進(jìn)行 Binary Compile。

第二種情況,是真正開始打包了,比如說我們要給安卓打一個(gè)AssetBundle,或者發(fā)一個(gè)安卓的APK,這時(shí)候也會觸發(fā)這個(gè)過程。總之觸發(fā)這個(gè)過程的必要前提是我的目標(biāo)平臺是明確的,我知道要把中間的東西最終要翻譯成什么。BinaryCompile 的過程其實(shí)是一個(gè)非常神奇的過程,Unity 實(shí)際上也不是直接把大家寫的,比如說CG就直接翻譯到目標(biāo)平臺上,這個(gè)工作量其實(shí)是很大的。

關(guān)于Unity的目標(biāo)平臺就非常多了,比如說大家常見的手機(jī)平臺上有很多的API,加上主機(jī)平臺,他們都有自己整套的語言規(guī)范。

大家可以腦補(bǔ)一下如果我們要是強(qiáng)行翻會怎樣。這是一個(gè)乘的關(guān)系,左邊4個(gè),假如說右邊是10個(gè)不同的平臺,那就是40個(gè),要寫40套不同的代碼,代碼的路徑就非常的繚亂。其一代碼維護(hù)難度很大,其二是也很難寫。

Unity使用了第三方技術(shù),名為HLSLCC,CC的意思是交叉編譯器,大家可以搜到。這個(gè)最終幫助Unity做出了一些優(yōu)化和改變,和Unity使用的版本不是完全一樣的(大家不要把網(wǎng)上的內(nèi)容改一下直接替進(jìn)來,這樣行不通)。

Unity 實(shí)際上做了這樣的工作:Unity會先把前端的一些語言,盡量地翻譯到DX那個(gè)級別上去,通過DX的編輯器進(jìn)行編輯,編輯完了之后,后端再走到HLSLCC,再向目標(biāo)平臺去輸出,相當(dāng)于是一個(gè)兩步編譯的過程。所以整體的難度降低了很多,大部分的工作是由HLSLCC來做的。

這個(gè)編譯過程也會導(dǎo)致一個(gè)問題,比如DX里面沒有,翻譯不過去,中間要經(jīng)過一步,其實(shí)就相當(dāng)于過路費(fèi)要交,但是過路的時(shí)候沒有這個(gè)東西。因此Unity在2020以后的版本,最早的時(shí)候是用的DXBC,而現(xiàn)在用的則是DXRL,Unity也是基礎(chǔ)于DX的編譯器進(jìn)行了自己的擴(kuò)展,以便盡可能地去支持一些新特性。

主站蜘蛛池模板: 最新浮力影院地址第一页| 91情国产l精品国产亚洲区| 国产极品美女高潮无套在线观看| 综合一区自拍亚洲综合图区| 亚洲www视频| 在线看免费毛片| 美国经典三级版在线播放| 五月婷婷丁香六月| 菠萝蜜视频在线播放| 亚洲深深色噜噜狠狠爱网站| 日本在线高清版卡免v| 天天影视综合网| 亚洲成在人线在线播放无码| 在线观看亚洲免费视频| 精品久久久久亚洲| 一级一级毛片看看| 国产中文字幕免费| 日日AV色欲香天天综合网| 高清无码视频直接看| 亚洲五月激情网| 国产成人久久777777| 日本一二三高清| 男女啪啪进出阳道猛进| 91av国产精品| 久久精品欧美一区二区| 国产一区二区三区视频| 妺妺窝人体色WWW聚色窝仙踪 | 老阿姨哔哩哔哩b站肉片茄子芒果 老阿姨哔哩哔哩b站肉片茄子芒果 | 丰满少妇人妻HD高清大乳在线| 日本高清www无色夜在| 性刺激久久久久久久久| 久久亚洲精品成人| 免费看美女隐私全部| 国产精品高清2021在线| 欧美一级久久久久久久大片| 荐片app官网下载ios怎么下载| 一个人看的视频www在线| 亚洲欧美一区二区三区| 国产一区二区三区四| 国产午夜福利短视频| 国产精品午夜福利在线观看地址|