目的
備忘,便于日后自己索引
問題
為了學(xué)習(xí)了解大廠項(xiàng)目的效果:
上周為了將 王者榮耀的 楊玉環(huán) 的某個(gè)皮膚的頭發(fā)效果還原
所以我想直接抓模型,再還原 shader
我使用的還是以前的老方法: GPA + 夜神模擬器,具體可以查看以前的另一篇教程,具體參考:教你如何使用GPA導(dǎo)出模型,另送一個(gè) GPA CSV2MESH Tool in unity
抓出來的數(shù)據(jù),導(dǎo)出 FBX 后,我看不出什么異常
直到,我逐行的 shader
還原效果的時(shí)候
發(fā)現(xiàn) vertex input
數(shù)據(jù)有 float4 uv1 : TEXCOORD1; float4 uv2 : TEXCOORD2;
但是發(fā)現(xiàn) shader 調(diào)試發(fā)現(xiàn),uv1, uv2
使用顏色輸出都發(fā)現(xiàn)了數(shù)據(jù)不對(duì)的 BUG
然后我還想在 unity Game 視圖下,使用 RenderDoc 抓幀分析一下
結(jié)果 Load RenderDoc
之后,直接導(dǎo)致 unity 閃退
瞄了一下 CSharp 代碼,發(fā)現(xiàn)我使用的是 Mesh.uv
API,getter and setter 都是 Vector2[]
的,所以 zw
是不可能設(shè)置上的
然后瞄了一下 Mesh
是有 void SetUVs(int channel, Vector4[] uvs)
的 API 的
但是經(jīng)過測(cè)試,還是發(fā)現(xiàn) UV的 zw 無法保存下來
最終我問了一下unity 技術(shù)官方,結(jié)果他們測(cè)試是OK的 (因?yàn)樗麄兪菍?duì) Mesh 內(nèi)存數(shù)據(jù)的實(shí)時(shí)修改)
然后我也試了一下,確實(shí)OK,但是經(jīng)過自己跟進(jìn)一步測(cè)試,發(fā)現(xiàn)使用 FBX Exporter 導(dǎo)出之后,UV 還是會(huì)丟失的
我將測(cè)試總結(jié)一下: unity Mesh 中會(huì)保存 uv vector4 的數(shù)據(jù),到時(shí)經(jīng)過 FBX Exporter 插件導(dǎo)出之后,uv 就不可能保存 Vector4 了
然后我分析了一下 FBX Exporter 插件的代碼
發(fā)現(xiàn)一丟丟問題:
-
我將 FBX Exporter Local 化后,再按照我下面截圖的內(nèi)容,修改后,還是無法導(dǎo)出 (如何 local 化,可以參考我之前的文章:Unity - 如何修改一個(gè) Package 或是如何將 Package Local化 )
-
發(fā)現(xiàn) Unity 中 AutoDesk 的 package 里面封裝的 API
FbxLayerElemetnUV.Create
進(jìn)入是繼承UV2
的
也要先 local,但是這個(gè) package 比較特殊,在 PackageManager 中不顯示的,方法可以是先從 Library/PackageCache/com.autodesk.fbx@4.2.0 剪貼到 [項(xiàng)目目錄]/Packages/下面,然后使用 Package add from disk 的方式
然后再開始修改代碼
從public class FbxLayerElementUV : FbxLayerElementTemplateFbxVector2
修改為
新public class FbxLayerElementUV : FbxLayerElementTemplateFbxVector4
結(jié)果發(fā)現(xiàn)還是不行
因?yàn)橹罢f的,unity editor 下,無論 game view, 還是 scene view
直接 Load RenderDoc 都會(huì)導(dǎo)致unity 閃退
然后我再使用 RenderDoc + 真機(jī) 抓幀分析,果然是沒有 vertex input TEXCOORD0 zw 分量數(shù)據(jù)的
解決方案
于是我就有點(diǎn)懷疑 FBX 是不能保存 uv 超過4 分量數(shù)據(jù)的
然后百度: ‘fbx 文本 file header’ 找到這篇:
-
FBX文件結(jié)構(gòu)解讀【文本格式】
- 譯文原始地址在這:FBX文件結(jié)構(gòu)解讀
- 翻譯之前的原文在這:A quick tutorial about the FBX ASCII format
google ‘fbx ascii file header’ 找到:
-
FBX binary file format specification - blender 的
再 ‘How to save uv data more than 4 components in fbx file’ 找到: - FBX export/import only supports Vector2 in UV (but the uvs can contain upto Vector4 in Unity) - 這個(gè)人遇到的問題,和我一模一樣,里面的解決方式就是使用 AssetData.CreateAsset(mesh, path) 的方式來解決的
經(jīng)過前面 (還有很多篇)
看完 ascii 格式的 FBX 頭文件后,我就知道,uv 存不了 vector4 了,那我就在猜
王者榮耀 也是使用 unity 開發(fā)的,難不成他們 TEXCOORD[N] 保存超過 2 個(gè)以上的分量數(shù)據(jù)都是使用 unity Mesh 的方式來保存的嗎?
驗(yàn)證
- 試一下 unity mesh 能否成功 - OK
- 測(cè)試一下 *.obj 格式能否將 uv 保存超過 2 個(gè)分量以上的數(shù)據(jù) - OK,但是AB打包可能不會(huì)打進(jìn)去(目錄中注意的部分會(huì)有講到)
保存為 Unity Mesh 結(jié)果 - OK
先構(gòu)建uv數(shù)據(jù)
然后設(shè)置數(shù)據(jù)
然后 shader 打印
之前的z是全黑色,w全白色,現(xiàn)在都有對(duì)應(yīng)的強(qiáng)度了,OK,說明 unity mesh 還是OK的
想要了解 unity mesh 如何保存數(shù)據(jù),我們可以將 AssetDatabase.CreateAsset
之后的 Mesh.asset
文件用文本編輯器打開,瞄一下就好啦
保存為 *.obj 文件結(jié)果 - not OK,但是可以 DIY importer
首先我們用 blender 簡(jiǎn)單整一個(gè) cube,將 uv 展好,如下
然后導(dǎo)出 *.obj 放到 unity 里面瞄一下,如下圖
然后我們直接給 obj 里面的 vt 增加 字段數(shù)據(jù)的分量,看看 unity 有否變化,然后發(fā)現(xiàn)是沒有變化的
然后我們發(fā)現(xiàn)修改不了 原始的 .obj 里面在 library 下的 mesh cache 信息 (.fbx) 同樣如此
比如下面的代碼,我將問題寫在注釋了
var assetObj = AssetDatabase.LoadAssetAtPath<Object>(assetPath); // assetObj == null
var modelPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); // modelPrefab == null
var mesh_filter = modelPrefab.GetComponentInChildren<MeshFilter>(); // 所有導(dǎo)致 modelPrefab 出現(xiàn)空引用的 BUG
完整如下
public class AssetsImporterExt : AssetPostprocessor
{
private void OnPreprocessModel()
{
var mi = assetImporter as ModelImporter;
if (mi == null) return;
// assetPath == "Assets/Test/Test_uv.obj"
var assetPath = assetImporter.assetPath;
var assetObj = AssetDatabase.LoadAssetAtPath<Object>(assetPath); // assetObj == null
var modelPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); // modelPrefab == null
var mesh_filter = modelPrefab.GetComponentInChildren<MeshFilter>(); // 所有導(dǎo)致 modelPrefab 出現(xiàn)空引用的 BUG
if (mesh_filter == null) return;
var mesh = mesh_filter.sharedMesh;
var uvs = new List<Vector4>();
mesh.GetUVs(0, uvs);
var ext = System.IO.Path.GetExtension(assetPath).ToLower();
if (ext == ".obj")
{
var spliter = new string[] { " " };
var sb = new StringBuilder();
var dirty = false;
using(var reader = new StreamReader(assetPath))
{
var idx = 0;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line.StartsWith("vt"))
{
sb.Clear();
var args = line.Split(spliter, System.StringSplitOptions.RemoveEmptyEntries);
if (args.Length > 3) sb.Append(args[3]);
if (args.Length > 4) sb.Append(" " + args[4]);
Debug.Log($"extension uv data zw : {sb}");
var uv_data = uvs[idx]; // get from array
if (args.Length > 3)
{
if (!float.TryParse(args[3], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.z = val; // update z component
}
if (args.Length > 4)
{
if (!float.TryParse(args[4], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.w = val; // update w component
}
uvs[idx] = uv_data; // update to array
++idx;
dirty = true;
} // end of if (line.StartsWith("vt"))
} // end of while (!reader.EndOfStream)
} // end of using(var reader = new StreamReader(assetPath))
if (dirty)
{
EditorUtility.SetDirty(mesh);
EditorUtility.SetDirty(modelPrefab);
AssetDatabase.SaveAssetIfDirty(modelPrefab);
}
} // end of if (ext == ".obj")
}
既然 原始模型的 mesh 修改不了,那么我們可以處理 prefab 里面的 mesh,下面進(jìn)行嘗試一下
其實(shí)這帖子 FBX export/import only supports Vector2 in UV (but the uvs can contain upto Vector4 in Unity) 里面也有人是這樣的思路,如下圖
先來一段代碼,看看能否修改成功
private void OnPostprocessPrefab(GameObject gameObject)
{
var mf = gameObject.GetComponentInChildren<MeshFilter>();
if (mf == null) return;
var mesh_path = AssetDatabase.GetAssetPath(mf.sharedMesh);
Debug.Log($"mehs_path : {mesh_path}");
var mesh = mf.sharedMesh;
var uvs = new List<Vector4>();
mesh.GetUVs(0, uvs);
var ext = System.IO.Path.GetExtension(mesh_path).ToLower();
if (ext == ".obj")
{
var spliter = new string[] { " " };
var sb = new StringBuilder();
var dirty = false;
using (var reader = new StreamReader(mesh_path))
{
var idx = 0;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line.StartsWith("vt"))
{
sb.Clear();
var args = line.Split(spliter, System.StringSplitOptions.RemoveEmptyEntries);
if (args.Length > 3) sb.Append(args[3]);
if (args.Length > 4) sb.Append(" " + args[4]);
Debug.Log($"extension uv data zw : {sb}");
var uv_data = uvs[idx]; // get from array
if (args.Length > 3)
{
if (!float.TryParse(args[3], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.z = val; // update z component
}
if (args.Length > 4)
{
if (!float.TryParse(args[4], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.w = val; // update w component
}
uvs[idx] = uv_data; // update to array
++idx;
dirty = true;
} // end of if (line.StartsWith("vt"))
} // end of while (!reader.EndOfStream)
} // end of using(var reader = new StreamReader(assetPath))
if (dirty)
{
mesh.SetUVs(0, uvs);
EditorUtility.SetDirty(mesh);
EditorUtility.SetDirty(gameObject);
AssetDatabase.SaveAssetIfDirty(gameObject);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
} // end of if (ext == ".obj")
}
OK,有了上面的 postprocess 代碼 + prefab,我們 reimport 測(cè)試一下
可以看到 Test_uv.obj 里面的 mesh 的 uv 從 flaot2 變成了 float4 了,如下圖
然后我們看一下 測(cè)試 shader 的效果,發(fā)現(xiàn)是有數(shù)據(jù)異常的,一部分有設(shè)置成功,一部分沒有,那么很有可能是 *.obj 的頂點(diǎn)數(shù)解析和unity不一樣
首先,瞄一下,*.obj 里面有 14 條 uv 信息 xy 分量是原來的,后面的 zw (0.25, 0.5) 都是我后續(xù)增加的
然后我們斷點(diǎn)發(fā)現(xiàn),unity 解析出來,會(huì)有 24 個(gè) uv 信息,如下圖
觀察了一下規(guī)律,可以發(fā)現(xiàn),他將一些多面共點(diǎn),拆分為分別的三角面的對(duì)應(yīng)的獨(dú)立點(diǎn)
因此我們可以根據(jù) uv.xy 如果坐標(biāo)相同,那么我們就將 uv.zw 記錄一份,共享這些 uv.xy 的數(shù)據(jù)的 zw 數(shù)據(jù)即可
繼續(xù)修改一下代碼
private void OnPostprocessPrefab(GameObject gameObject)
{
var mf = gameObject.GetComponentInChildren<MeshFilter>();
if (mf == null) return;
var mesh_path = AssetDatabase.GetAssetPath(mf.sharedMesh);
Debug.Log($"mehs_path : {mesh_path}");
var mesh = mf.sharedMesh;
var uvs = new List<Vector4>();
mesh.GetUVs(0, uvs);
var ext = System.IO.Path.GetExtension(mesh_path).ToLower();
if (ext == ".obj")
{
var dict = new Dictionary<string, Vector2>();
var spliter = new string[] { " " };
var sb = new StringBuilder();
var dirty = false;
using (var reader = new StreamReader(mesh_path))
{
var idx = 0;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line.StartsWith("vt"))
{
sb.Clear();
var args = line.Split(spliter, System.StringSplitOptions.RemoveEmptyEntries);
if (args.Length > 3) sb.Append(args[3]);
if (args.Length > 4) sb.Append(" " + args[4]);
Debug.Log($"extension uv data zw : {sb}");
var uv_data = uvs[idx]; // get from array
var key1 = float.Parse(args[1]).ToString("0.000000");
var key2 = float.Parse(args[2]).ToString("0.000000");
var key = $"{key1},{key2}";
if (!dict.TryGetValue(key, out var zwVec))
{
if (args.Length > 3)
{
if (!float.TryParse(args[3], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.z = val; // update z component
}
if (args.Length > 4)
{
if (!float.TryParse(args[4], out float val)
|| float.IsNaN(val)
|| float.IsInfinity(val)
)
{
val = 0f;
}
uv_data.w = val; // update w component
}
zwVec.x = uv_data.z;
zwVec.y = uv_data.w;
dict[key] = new Vector2(zwVec.x, zwVec.y); // update to dict
}
uvs[idx] = uv_data; // update to array
++idx;
dirty = true;
} // end of if (line.StartsWith("vt"))
} // end of while (!reader.EndOfStream)
} // end of using(var reader = new StreamReader(assetPath))
if (dirty)
{
// 將 xy 相同的都共用 uv.zw 數(shù)據(jù)
for (int i = 0; i < uvs.Count; i++)
{
var uv = uvs[i];
var key = $"{uv.x.ToString("0.000000")},{uv.y.ToString("0.000000")}";
if (dict.TryGetValue(key, out var zwVec))
{
uv.z = zwVec.x;
uv.w = zwVec.y;
uvs[i] = uv;
}
}
mesh.SetUVs(0, uvs);
EditorUtility.SetDirty(mesh);
EditorUtility.SetDirty(gameObject);
AssetDatabase.SaveAssetIfDirty(gameObject);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
} // end of if (ext == ".obj")
}
查看渲染結(jié)果,正常了
然后我們?cè)囋囆薷?*.obj 里面的uv 擴(kuò)展數(shù)據(jù)瞄一下效果如何
最后的渲染效果如下
注意
-
*.obj 這種方式暫時(shí)沒去驗(yàn)證能否將打包出來的 ab 里面的 mesh 修改(因?yàn)槔镱^的文件信息是再 library 里面的臨時(shí)生成的問題,打包不會(huì)打包進(jìn)去)文章來源:http://www.zghlxwxcb.cn/news/detail-743240.html
-
但是使用 *.asset 來保存 unity mesh 的方式肯定可以,因?yàn)樽兂闪宋募畔?span toymoban-style="hidden">文章來源地址http://www.zghlxwxcb.cn/news/detail-743240.html
References
- FBX export/import only supports Vector2 in UV (but the uvs can contain upto Vector4 in Unity)
到了這里,關(guān)于Unity - 導(dǎo)出的FBX模型,無法將 vector4 保存在 uv 中(使用 Unity Mesh 保存即可)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!