Unity - 导出的FBX模型,无法将 vector4 保存在 uv 中(使用 Unity Mesh 保存即可)

文章目录

  • 目的
  • 问题
  • 解决方案
  • 验证
  • 保存为 Unity Mesh 结果 - OK
  • 保存为 *.obj 文件结果 - not OK,但是可以 DIY importer
  • 注意
  • References


目的

备忘,便于日后自己索引


问题

为了学习了解大厂项目的效果:
上周为了将 王者荣耀的 杨玉环 的某个皮肤的头发效果还原
所以我想直接抓模型,再还原 shader
我使用的还是以前的老方法: GPA + 夜神模拟器,具体可以查看以前的另一篇教程,具体参考:教你如何使用GPA导出模型,另送一个 GPA CSV2MESH Tool in unity
在这里插入图片描述

抓出来的数据,导出 FBX 后,我看不出什么异常

直到,我逐行的 shader 还原效果的时候
发现 vertex input 数据有 float4 uv1 : TEXCOORD1; float4 uv2 : TEXCOORD2;

但是发现 shader 调试发现,uv1, uv2 使用颜色输出都发现了数据不对的 BUG

然后我还想在 unity Game 视图下,使用 RenderDoc 抓帧分析一下
结果 Load RenderDoc 之后,直接导致 unity 闪退
在这里插入图片描述
在这里插入图片描述

瞄了一下 CSharp 代码,发现我使用的是 Mesh.uv API,getter and setter 都是 Vector2[] 的,所以 zw 是不可能设置上的
在这里插入图片描述

然后瞄了一下 Mesh 是有 void SetUVs(int channel, Vector4[] uvs) 的 API 的
在这里插入图片描述
但是经过测试,还是发现 UV的 zw 无法保存下来
在这里插入图片描述

最终我问了一下unity 技术官方,结果他们测试是OK的 (因为他们是对 Mesh 内存数据的实时修改)
然后我也试了一下,确实OK,但是经过自己跟进一步测试,发现使用 FBX Exporter 导出之后,UV 还是会丢失的
我将测试总结一下: unity Mesh 中会保存 uv vector4 的数据,到时经过 FBX Exporter 插件导出之后,uv 就不可能保存 Vector4 了
在这里插入图片描述

然后我分析了一下 FBX Exporter 插件的代码
发现一丢丢问题:

  1. 我将 FBX Exporter Local 化后,再按照我下面截图的内容,修改后,还是无法导出 (如何 local 化,可以参考我之前的文章:Unity - 如何修改一个 Package 或是如何将 Package Local化 )
    在这里插入图片描述

  2. 发现 Unity 中 AutoDesk 的 package 里面封装的 API FbxLayerElemetnUV.Create 进入是继承 UV2
    也要先 local,但是这个 package 比较特殊,在 PackageManager 中不显示的,方法可以是先从 Library/PackageCache/com.autodesk.fbx@4.2.0 剪贴到 [项目目录]/Packages/下面,然后使用 Package add from disk 的方式
    然后再开始修改代码
    public class FbxLayerElementUV : FbxLayerElementTemplateFbxVector2 修改为
    public class FbxLayerElementUV : FbxLayerElementTemplateFbxVector4
    结果发现还是不行

在这里插入图片描述
在这里插入图片描述

因为之前说的,unity editor 下,无论 game view, 还是 scene view
直接 Load RenderDoc 都会导致unity 闪退
然后我再使用 RenderDoc + 真机 抓帧分析,果然是没有 vertex input TEXCOORD0 zw 分量数据的
在这里插入图片描述


解决方案

于是我就有点怀疑 FBX 是不能保存 uv 超过4 分量数据的
然后百度: ‘fbx 文本 file header’ 找到这篇:

  • FBX文件结构解读【文本格式】
    • 译文原始地址在这:FBX文件结构解读
    • 翻译之前的原文在这: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) - 这个人遇到的问题,和我一模一样,里面的解决方式就是使用 AssetData.CreateAsset(mesh, path) 的方式来解决的

经过前面 (还有很多篇)

看完 ascii 格式的 FBX 头文件后,我就知道,uv 存不了 vector4 了,那我就在猜

王者荣耀 也是使用 unity 开发的,难不成他们 TEXCOORD[N] 保存超过 2 个以上的分量数据都是使用 unity Mesh 的方式来保存的吗?


验证

  • 试一下 unity mesh 能否成功 - OK
  • 测试一下 *.obj 格式能否将 uv 保存超过 2 个分量以上的数据 - OK,但是AB打包可能不会打进去(目录中注意的部分会有讲到)

保存为 Unity Mesh 结果 - OK

先构建uv数据
在这里插入图片描述
然后设置数据
在这里插入图片描述
然后 shader 打印
在这里插入图片描述

之前的z是全黑色,w全白色,现在都有对应的强度了,OK,说明 unity mesh 还是OK的
想要了解 unity mesh 如何保存数据,我们可以将 AssetDatabase.CreateAsset 之后的 Mesh.asset 文件用文本编辑器打开,瞄一下就好啦
在这里插入图片描述


保存为 *.obj 文件结果 - not OK,但是可以 DIY importer

首先我们用 blender 简单整一个 cube,将 uv 展好,如下
在这里插入图片描述

然后导出 *.obj 放到 unity 里面瞄一下,如下图
在这里插入图片描述
然后我们直接给 obj 里面的 vt 增加 字段数据的分量,看看 unity 有否变化,然后发现是没有变化的
在这里插入图片描述

然后我们发现修改不了 原始的 .obj 里面在 library 下的 mesh cache 信息 (.fbx) 同样如此

比如下面的代码,我将问题写在注释了

        var assetObj = AssetDatabase.LoadAssetAtPath<Object>(assetPath); // assetObj == nullvar modelPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); // modelPrefab == nullvar mesh_filter = modelPrefab.GetComponentInChildren<MeshFilter>(); // 所有导致 modelPrefab 出现空引用的 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 == nullvar modelPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath); // modelPrefab == nullvar mesh_filter = modelPrefab.GetComponentInChildren<MeshFilter>(); // 所有导致 modelPrefab 出现空引用的 BUGif (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 arrayif (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,下面进行尝试一下
其实这帖子 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 arrayif (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 测试一下
在这里插入图片描述
可以看到 Test_uv.obj 里面的 mesh 的 uv 从 flaot2 变成了 float4 了,如下图
在这里插入图片描述

然后我们看一下 测试 shader 的效果,发现是有数据异常的,一部分有设置成功,一部分没有,那么很有可能是 *.obj 的顶点数解析和unity不一样
在这里插入图片描述

首先,瞄一下,*.obj 里面有 14 条 uv 信息 xy 分量是原来的,后面的 zw (0.25, 0.5) 都是我后续增加的
在这里插入图片描述

然后我们断点发现,unity 解析出来,会有 24 个 uv 信息,如下图
在这里插入图片描述

观察了一下规律,可以发现,他将一些多面共点,拆分为分别的三角面的对应的独立点
因此我们可以根据 uv.xy 如果坐标相同,那么我们就将 uv.zw 记录一份,共享这些 uv.xy 的数据的 zw 数据即可
继续修改一下代码

    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 arrayvar 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 数据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")}

查看渲染结果,正常了
在这里插入图片描述

然后我们试试修改 *.obj 里面的uv 扩展数据瞄一下效果如何
在这里插入图片描述

最后的渲染效果如下
在这里插入图片描述


注意

  • *.obj 这种方式暂时没去验证能否将打包出来的 ab 里面的 mesh 修改(因为里头的文件信息是再 library 里面的临时生成的问题,打包不会打包进去)

  • 但是使用 *.asset 来保存 unity mesh 的方式肯定可以,因为变成了文件信息


References

  • FBX export/import only supports Vector2 in UV (but the uvs can contain upto Vector4 in Unity)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117292.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于nacos的配置获取失败及服务发现问题的排坑记录

nacos配置更新未能获取到导致启动报错 排查思路&#xff1a; 1、是否添加了nacos的启动pom依赖 参考&#xff1a; <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><…

vue3中的route和router

因为我们在 setup 里面没有访问 this&#xff0c;所以我们不能再直接访问 this.$router 或 this.$route。作为替代&#xff0c;我们使用 useRouter 和useRoute函数,或者 Vue3 中提供了一个 getCurrentInstance 方法来获取当前 Vue 实例 <script setup>import { useRoute…

GEE图表——利用NOAA气象数据绘制气温预测图

简介 气象预测是通过气象数据和模型对未来某一时间和地点的天气情况进行预测。 具体步骤如下&#xff1a; 1. 数据采集&#xff1a;从气象观测站、卫星等获取气象数据&#xff0c;包括气压、水汽、风速、温度、降雨、云量等。 2. 数据清洗&#xff1a;对采集到的数据进行质…

学到一招 chrome 浏览器 debug 悬浮样式

前言 今天在想调试一个开源 UI 框架的某个table row的隔行换色的样式设置&#xff0c;发现这个颜色只有鼠标悬浮在row的时候才能拿到&#xff0c;但是想要拷贝 row 样式&#xff0c;鼠标必须离开悬浮区域&#xff0c;去chrome的debug控制台内才能拷贝&#xff0c;但是一离开悬…

Qt之设置QLineEdit只能输入浮点数

Qt提供了QDoubleValidator来进行浮点数校验,但是它同样存在限定范围无效的问题,详见:Qt之彻底解决QSpinBox限定范围无效的问题 因此我们要子类化QDoubleValidator,并重写其中的validate方法,最后调用QLineEdit的setValidator方法,并将这个子类当做参数传入。 QHDoubleVa…

怎么降低Linux内核驱动开发的风险?

降低Linux内核驱动开发的风险是一个重要的目标&#xff0c;因为内核驱动开发可能会对系统的稳定性和安全性产生重要影响。以下是一些降低风险的建议&#xff1a; 1. 深入了解Linux内核&#xff1a;在开始内核驱动开发之前&#xff0c;建议深入学习Linux内核的工作原理和架构&a…

[Unity]将所有 TGA、TIFF、PSD 和 BMP(可自定义)纹理转换为 PNG,以减小项目大小,而不会在 Unity 中造成任何质量损失

如何使用 只需在“项目”窗口中创建一个名为“编辑器”的文件夹&#xff0c;然后在其中添加此脚本即可。然后&#xff0c;打开窗口-Convert Textures to PNG&#xff0c;配置参数并点击“Convert to PNG&#xff01; ”。 就我而言&#xff0c;它已将某些 3D 资源的总文件大小…

交互式 Web 应用 0 基础入门

初探 Gradio&#xff1a;轻松构建交互式 Web 应用 文章目录 初探 Gradio&#xff1a;轻松构建交互式 Web 应用Why Gradio?安装 Gradio创建交互式界面1. gr.Interface2. gr.Blocks 强大的组件库输入输出组件控制组件布局组件 示例交互式数据可视化多组件同时&#xff08;嵌套&a…

vue3 源码解析(1)— reactive 响应式实现

前言 本文是 vue3 源码解析系列的第一篇文章&#xff0c;项目代码的整体实现是参考了 v3.2.10 版本&#xff0c;项目整体架构可以参考之前我写过的文章 rollup 实现多模块打包。话不多说&#xff0c;让我们通过一个简单例子开始这个系列的文章。 举个例子 <!DOCTYPE html…

电力通信与泛在电力物联网技术的应用与发展-安科瑞黄安南

摘要&#xff1a;随着我国社会经济的快速发展&#xff0c;我国科技实力得到了非常大的提升&#xff0c;当前互联网通信技术在社会中得到了广泛的应用。随着电力通信技术的快速发展与更新&#xff0c;泛在电力物联网建设成为电力通讯发展的重要方向。本文已泛在电力物联网系统为…

无中微子双贝塔衰变

无中微子双贝塔衰变 无中微子双贝塔衰变的原子核理论 双贝塔衰变的研究缘起 玛丽亚格佩特-梅耶&#xff08;Maria Goeppert-Mayer&#xff09;在1935年提出了双贝塔衰变的可能性埃托雷马约拉纳&#xff08;Ettore Majorana&#xff09;在1937年证明了如果中微子是否是Majorana…

PCL 透视投影变换(OpenGL)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在现实生活中,我们总会注意到离我们越远的东西看起来更小。这个神奇的效果被称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的这样: 由于透视的原因,平行线…

CVE-2023-46227 Apache inlong JDBC URL反序列化漏洞

项目介绍 Apache InLong&#xff08;应龙&#xff09;是一站式、全场景的海量数据集成框架&#xff0c;同时支持数据接入、数据同步和数据订阅&#xff0c;提供自动、安全、可靠和高性能的数据传输能力&#xff0c;方便业务构建基于流式的数据分析、建模和应用。 项目地址 h…

以太网链路聚合与交换机堆叠,集群

目录 以太网链路聚合 一.链路聚合的基本概念 二.链路聚合的配置 1.手工模式 2.LACP模式 系统优先级 接口优先级 最大活动接口数 活动链路选举 负载分担 负载分担模式 三.典型使用场景 交换机之间 交换机和服务器之间 交换机和堆叠系统 防火墙双机热备心跳线 四…

try catch finally代码块的作用

try-catch-finally 代码块是用于处理程序中可能发生的异常情况的一种结构。它的作用在于&#xff1a; try 代码块中的代码用于包含可能会引发异常的代码。catch 代码块用于捕获并处理 try 代码块中抛出的异常。可以通过 catch 代码块中的逻辑来处理异常&#xff0c;如打印错误…

I/O 模型学习笔记【全面理解BIO/NIO/AIO】

文章目录 I/O 模型什么是 I/O 模型Java支持3种I/O模型BIO&#xff08;Blocking I/O&#xff09;NIO&#xff08;Non-blocking I/O&#xff09;AIO&#xff08;Asynchronous I/O&#xff09; BIO、NIO、AIO适用场景分析 java BIOJava BIO 基本介绍Java BIO 编程流程一个栗子实现…

10个基于.Net开发的Windows开源软件项目

1、基于.NET的强大软件开发工具 一个基于.Net Core构建的简单、跨平台快速开发框架。JNPF开发平台前后端封装了上千个常用类&#xff0c;方便扩展&#xff1b;集成了代码生成器&#xff0c;支持前后端业务代码生成&#xff0c;满足快速开发&#xff0c;提升工作效率&#xff1b…

动态开辟内存空间函数

文章目录 malloc函数calloc函数malloc函数和calloc函数的不同free函数realloc函数 malloc函数 参数是要开辟内存空间的大小 开辟成功则返回值为开辟空间的首地址&#xff0c;若开辟失败则返回一个空指针NULL calloc函数 第一个参数为开辟空间的元素个数&#xff0c;第二个参数…

【软件教程】如何用C++交叉编译出能在Android运行的ELF程序或so动态库

一、配置NDK交叉编译平台 1. 打开Android的官方ndk下载链接https://developer.android.com/ndk/downloads?hlzh-cn&#xff0c;下载windows 64位ndk环境包。 2. 解压后将具有以下文件的路径加入到系统环境变量。 3. 配置好环境变量&#xff0c;如下图所示&#xff0c;Path中存…

多线程进阶

多线程常见面试题 文章目录 多线程常见面试题1. 常见的锁策略1.1乐观锁&悲观锁1.2 轻量级锁&重量级锁1.3 自旋锁&挂起等待锁1.4 读写锁&普通互斥锁1.5 公平锁&非公平锁1.6可重入锁&不可重入锁 2. CAS3. Sychronized原理3.1 锁升级3.2 锁消除3.3 锁粗化…