Unity烘焙:光照貼圖分場(chǎng)景烘焙與綜合加載

前言:
???????文章的內(nèi)容在于分享在Unity中如何對(duì)一整套模型分批烘焙,最后再綜合加載的,以及加載后存在的問(wèn)題。不會(huì)詳細(xì)的講解烘焙參數(shù)以及方法相關(guān)的知識(shí),對(duì)這方面知識(shí)有需求的小伙伴可以自行查看其他資料。
???????最后再補(bǔ)充一點(diǎn),這里只是分享分場(chǎng)景烘焙與綜合加載的方法,至于在實(shí)際項(xiàng)目中是否實(shí)際可行,就需要小伙伴根據(jù)自己項(xiàng)目自行考慮啦。
? ? 如果我分享的內(nèi)容存在問(wèn)題也請(qǐng)大伙指正,我會(huì)及時(shí)修改,好了讓我們開(kāi)始正文吧。
? ?部分借鑒:https://blog.csdn.net/Tel17610887670/article/details/113181709
????該文章部分代碼并未涉及,完全照做達(dá)不到最后的效果,部分內(nèi)容據(jù)說(shuō)需要下載示例工程。至少我木得積分,并沒(méi)有下載。
正文:
????????首先介紹一下我的環(huán)境以及使用的素材給大伙一個(gè)參考:
????????????1、Unity 3D版本: 2020.3.30f1c1
????????????2、模型素材:Asset Store中的"Medieval Tavern Pack"(免費(fèi))
????????準(zhǔn)備工作就已經(jīng)結(jié)束了。
? ??????先來(lái)看一眼原始場(chǎng)景、整體烘焙后場(chǎng)景和分場(chǎng)景烘焙綜合加載效果。文章的核心在于分享方案,所以請(qǐng)自動(dòng)忽略這個(gè)效果不好的問(wèn)題.....【實(shí)在是能力有限】



????????烘焙的相關(guān)知識(shí)這里不會(huì)展開(kāi)講解,因?yàn)檫@是一個(gè)比較大的內(nèi)容,如果有需要可以單獨(dú)開(kāi)一篇來(lái)進(jìn)行講解。所以下面只會(huì)對(duì)關(guān)鍵面板進(jìn)行截圖展示并告訴大家修改某些參數(shù),至于具體功能和其他參數(shù)這里不做過(guò)多贅述。
????????這里稍微說(shuō)一下烘焙的方式,有基礎(chǔ)小伙伴可以跳過(guò)。這里不對(duì)燈光和烘焙參數(shù)做過(guò)多的設(shè)置。
????????首先我們找到自己的模型文件,將所有用到的模型的“Generate Lightmap UVs”勾選,點(diǎn)擊“Apply”為我們的模型生成光照UV,如果不做這一步,能會(huì)造成烘焙效果不正確,當(dāng)然省略這一步會(huì)不會(huì)影響我們后續(xù)的操作,但是為了效果還是建議勾選。

????????將場(chǎng)景中所有模型勾選“Static”,否則烘焙后無(wú)法得到想要的數(shù)據(jù)。

????? ? 將燈光的模式改為“Baked”,稍微調(diào)高光強(qiáng)和間接光強(qiáng)。

?????????Lighting面板可以在頂部菜單欄"Window->Rendering->Lighting"打開(kāi)。
????????然后在Lighting面板新生成一個(gè)“Lighting Settings”,勾選“Mixed Lighting”下的“Baked Global illumination”選項(xiàng),并將“Lighting Mode”設(shè)置為“Shadowmask”.

????????這個(gè)地方建議大家選用較小的模型來(lái)測(cè)試,模型的大小直接決定我們的烘焙速度。將"Lightmapper" 改為“Progressive GPU(Preview)”可以增加速度(我的電腦能快5-10倍),如果場(chǎng)景不大,其他的選項(xiàng)可以不改。

????最后點(diǎn)擊“Generate Lighting”開(kāi)始烘焙。順便提一下,關(guān)閉“Auto Generate”。
????這些數(shù)值不重要,可以照著填,也可以不改。

????????接下來(lái),讓我們看看場(chǎng)景烘焙完以后會(huì)的得到些什么。我這里是把場(chǎng)景分成了3份,3個(gè)場(chǎng)景分別烘焙的。所以會(huì)有3個(gè)文件夾。如果你沒(méi)有分,就只會(huì)得到1個(gè)。這個(gè)地方解釋一下,希望不要給讀者造成困擾。


????????烘焙完成后,會(huì)在場(chǎng)景同級(jí)目錄中生成一個(gè)同名文件夾,用來(lái)存放場(chǎng)景的烘焙數(shù)據(jù)類似光照貼圖等。光照貼圖會(huì)被加載到"Lighting"-“Baked Lightmaps”下。
????????再來(lái)看一下場(chǎng)景中的模型是如何利用這些光照貼圖的??梢钥吹剑姹汉蟮摹癕esh Renderer”組件下的“Lightmapping”多了一個(gè)“Baked Lightmap”,并且他還記錄著光照貼圖序號(hào)(Lightmap Index)和Tilling/Offset等,這就是我們想要的東西。



????????

????????到這里相信已經(jīng)有許多小伙伴已經(jīng)知道我們最后應(yīng)該怎么實(shí)現(xiàn)了,一定想迫不及待的自己試一下。
????????沒(méi)錯(cuò)!就是你想的那個(gè)樣子?。。?/span>
????????下面進(jìn)入我們的代碼環(huán)節(jié),為了讓小伙伴們方便的復(fù)現(xiàn),所有的代碼我都會(huì)完整的貼出來(lái)并且寫(xiě)好注釋,爭(zhēng)取讓大伙都看的明白我每一步想要干什么,至于代碼的優(yōu)化問(wèn)題,我相信小伙伴們都可以完成的,這里就以易讀為主了。
? 代碼部分
?????實(shí)現(xiàn)思路
????????1、? 獲取對(duì)象的? “Mesh Renderer”,并且保存“Lightmap Index”,“l(fā)ightmapScaleOffset”(“Tilling”與“Offset”)。
????????2、往“綜合場(chǎng)景”按照一定的順序加載所有的光照貼圖。
????????3、將第一步記錄的數(shù)據(jù)在“綜合場(chǎng)景”還原。
????具體實(shí)現(xiàn)與詳解
????首先我們需要分割場(chǎng)景,并將分割后的模型分辨放入不同的場(chǎng)景,但是要保證各場(chǎng)景的燈光數(shù)值相同。



????然后分別點(diǎn)擊烘焙,得到三個(gè)上邊講過(guò)的烘焙數(shù)據(jù)文件夾。
????先將場(chǎng)景的模型放到統(tǒng)一父物體下,并且做成預(yù)制體,放到Resources目錄下,這很重要,否則后續(xù)的操作大概率會(huì)失敗。

“LightMapData”存儲(chǔ)每個(gè)對(duì)象的數(shù)據(jù),會(huì)統(tǒng)一存入“SubSceneLightMapData”
[Serializable]
public class LightMapData
{
? ? //Mesh Renderer組件
? ? public MeshRenderer meshRenderer;
? ? //引用的光照貼圖在本場(chǎng)景的下標(biāo)( MeshRenderer.lightmapIndex)
? ? public int lightmapIndex;
? ? //“Tilling”與“Offset”他們合并成一個(gè)Vector4返回(MeshRenderer.lightmapScaleOffset)
? ? public Vector4 lightmapScaleOffset;
? ?public LightMapData(MeshRenderer meshRenderer)
? ? {
? ? ? ? this.meshRenderer = meshRenderer;//略? ? ? ?
? ? ? ? lightmapIndex = meshRenderer.lightmapIndex;//略
? ? ? ? lightmapScaleOffset = meshRenderer.lightmapScaleOffset;//略
? ? }
}

“SubSceneLightMapData”記錄場(chǎng)景中所有的光照貼圖和各個(gè)物體的信息
public class SubSceneLightMapData : MonoBehaviour
{
? ? //起始下標(biāo)。假如當(dāng)前是第二個(gè)場(chǎng)景,第一個(gè)場(chǎng)景加載了5張光照,那么起始下標(biāo)就是5。在綜合加載時(shí)使用
? ? public int startIndex;
? ? //本場(chǎng)景的所需要的光照貼圖集合
? ? public List<Texture2D> subLightMaps = new List<Texture2D>();
? ? //本場(chǎng)景所有的模型對(duì)象以及光照信息
? ? public List<LightMapData> lightMaps = new List<LightMapData>();
????//在組件的菜單中增加一個(gè)選項(xiàng),不理解的可以直接抄
? ? [ContextMenu("執(zhí)行函數(shù)")]
? ? public void ExecuteAction()
? ? {
? ? ? ? LoadSubSceneLightMap();
? ? ? ? GetLightMapData(transform);
? ?//代碼自動(dòng)保存預(yù)制體,如果感覺(jué)波浪線不好看,可以不寫(xiě)手動(dòng)保存
? ? ? ? if (PrefabUtility.GetPrefabParent(gameObject) != null)
? ? ? ? {
? ? ? ? ? ? PrefabUtility.ReplacePrefab(gameObject, PrefabUtility.GetPrefabParent(gameObject), ReplacePrefabOptions.ConnectToPrefab);
? ? ? ? }
? ? }
? ? //加載本場(chǎng)景的光照貼圖
? ? void LoadSubSceneLightMap()
? ? {
? ? ? ? foreach (var item in LightmapSettings.lightmaps)
? ? ? ? {
? ? ? ? ? ? subLightMaps.Add(item.lightmapColor);
? ? ? ? }
? ? }
? ? //判斷節(jié)點(diǎn)是否具有Mesh Renderer,保存相關(guān)數(shù)據(jù),并判斷當(dāng)前節(jié)點(diǎn)是否具有子節(jié)點(diǎn),如果有那么遞歸操作。
? ? private void GetLightMapData(Transform node)
? ? {
? ? ? ? if (node.gameObject.isStatic)
? ? ? ? {
? ? ? ? ? ? var render = node.GetComponent<MeshRenderer>();
? ? ? ? ? ? if (render != null)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? lightMaps.Add(new LightMapData(render));
? ? ? ? ? ? }
? ? ? ? ? ? for (int i = 0; i < node.childCount; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? GetLightMapData(node.GetChild(i));
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? //綜合加載時(shí),將對(duì)應(yīng)的光照貼圖數(shù)據(jù)再還原回去
? ? public void SetAllLightmapData()
? ? {
? ? ? ? if (lightMaps == null) return;
? ? ? ? for (int i = 0; i < lightMaps.Count; i++)
? ? ? ? {
? ? ? ? ? ? //綜合加載時(shí),光照貼圖的順序要隨著加載順序發(fā)生變化
? ? ? ? ? ? lightMaps[i].meshRenderer.lightmapIndex = lightMaps[i].lightmapIndex + startIndex;
? ? ? ? ? ? lightMaps[i].meshRenderer.lightmapScaleOffset = lightMaps[i].lightmapScaleOffset;
? ? ? ? }
? ? }
}
掛載到最頂級(jí)對(duì)象上,執(zhí)行函數(shù)。


一定記得保存預(yù)制體!雖然代碼里有自動(dòng)保存,但還是再說(shuō)一遍!

最后一個(gè)腳本“SceneLightMapControl”,加載所有的預(yù)制體,光照截圖,為每個(gè)子場(chǎng)景的預(yù)制體設(shè)置一個(gè)正確的順序。
public class SceneLightMapControl : MonoBehaviour
{
? ? //已經(jīng)加載的光照貼圖的數(shù)量,一會(huì)要賦值給SubSceneLightMapData使用
? ? public int lightmapCount = 0;
? ? //光照貼圖總集合
? ? public List<Texture2D> Templightmaps=new List<Texture2D>();
? ? private void Start()
? ? {
? ? ? ? LoadSubPrefab();
? ? }
? ? public void LoadSubPrefab()
? ? {
? ? ? ? LightmapSettings.lightmapsMode = LightmapsMode.NonDirectional;
? ? ? ? //為了方便加載我將預(yù)制體存放在Resources根目錄下,分成了三份。
? ? ? ? for (int i = 0; i < 3; i++)
? ? ? ? {
? ? ? ? ? ? GameObject go = Instantiate(Resources.Load<GameObject>("Part" + i));
? ? ? ? ? ? go.GetComponent<SubSceneLightMapData>().startIndex = lightmapCount;
? ? ? ? ? ? //獲取子場(chǎng)景所有的光照貼圖
? ? ? ? ? ? foreach (var item in go.GetComponent<SubSceneLightMapData>().subLightMaps)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Templightmaps.Add(item);
? ? ? ? ? ? }
? ? ? ? ? ? go.GetComponent<SubSceneLightMapData>().SetAllLightmapData();
? ? ? ? ? ? //更新起始下標(biāo)
? ? ? ? ? ? lightmapCount = Templightmaps.Count;
? ? ? ? }
? ? ? ? List<LightmapData> lightMapArray = new List<LightmapData>();
? ? ? ? for (int i = 0; i < Templightmaps.Count; i++)
? ? ? ? {
? ? ? ? ? ? LightmapData lm = new LightmapData();
? ? ? ? ? ? lm.lightmapColor = Templightmaps[i];
? ? ? ? ? ? lightMapArray.Add(lm);
? ? ? ? }
? ? ? ? //將光照貼圖重新賦值
? ? ? ? LightmapSettings.lightmaps = lightMapArray.ToArray();
? ? }
}

OK,下載將“SceneLightMapControl?”掛到場(chǎng)景中運(yùn)行就可以看到效果了



現(xiàn)在我們的功能就已經(jīng)全部實(shí)現(xiàn)了。
后話
????如果最后加載出來(lái)的場(chǎng)景光影不正常如下圖所示,請(qǐng)檢查加載的貼圖順序是否正常。

另外這種分場(chǎng)景烘焙的方案存在一定的缺陷,比如連續(xù)的陰影存在缺陷


對(duì)于這種情況,只能請(qǐng)美術(shù)大佬想想辦法給我們找補(bǔ)一下了。?
但是更建議我們?cè)诓鸱种熬瓦x擇不產(chǎn)生陰影的地方做分割或者不易被發(fā)現(xiàn)的地方分割。
好了今天的內(nèi)容就到這里,有問(wèn)題或者文章有錯(cuò)誤請(qǐng)?jiān)谠u(píng)論區(qū)內(nèi)留言,我會(huì)及時(shí)改正的!
我們下期再見(jiàn)!