文章目录
- 吐槽
- GPA Geometry Output 没有 UV
- 问题实例
- 开始实现
- 工具演示导出
- GPA 的 VBV,IBV的 BUG
- must be a multiple of 3 的解决办法
- indices out of bounds vertices - 暂无解决方法
- indices out of bounds vertices - 尝试解决的方法
- Invalid worldAABB.Object is too large or too far away from the origin - 解决方法
- 另一个问题: 有些TEXCOORD[n].y 是不需要翻转的,因为这些数据不是采样用的
- 另一个问题:导出的 FBX 中,UV 无法保存 vector3 或是 vector4 的数据 (已有解决方法)
- 增加了 readable 的选项便于导出后就自动设置
- 关于 must be a multiple of 3; indices out of bounds vertices; AABB too large 的终极解决方法
- Project
- References
以前写过一篇:Unity - RenderDoc 抓帧导出 FBX(带UV)
吐槽
我估计GPA是怕收律师函,因为如果 GPA 将所有资源一键提取,一键导出,那么可能很多开发商会告他
GPA Geometry Output 没有 UV
可以看到也好几个帖子问 GPA 官方,都是被官方忽悠回答了:
(除了这个,我自己还搜索过还几个类似有人问这个问题,结果同样被官方忽悠)
- 我之前尝试过用 GPA,RenderDoc 来抓取模型,结果 GPA 导不出 UV,而且 VBV 数据长度还对不上,RenderDoc 还不能对抓取模拟器 - unity shader - 圣斗士星矢 人物 shader 还原 - GPA 抓帧提取资源、shader,ROOT权限、救砖、ro.debuggable=1(最终还是RenderDoc无法抓帧) - 在现在这篇文章我尝试着将不同 VBV 元素长度的组装,我都以 indices 的长度来读取顶点数据的长度来构建模型,结果发现还真的可行,就是有不少无用的 primitive
- RenderDoc 是可以直接导出带 UV 的,可以查看我之前的一篇:Unity - RenderDoc 抓帧导出 FBX(带UV)
既然GPA不整模型导出带UV,那么今天我们实现一个 GPA 中的模型导出 UV 的工具
问题实例
如果直接将 input gemoetry 导出
会得到一个除了 position 之外,没有其他数据的网格,如下:
然后,对应的 shader vs input 有6个 register
如果我们要向将这些顶点数据都导出来,直接使用 GPA 功能默认的Geometry output是导不出来的
开始实现
提取 VBV, IBV
上面的 VS_INPUT
可以看到 input attribute 的定义
但是这些数据是从哪些 VBV(vertex buffer viewer) 输入的呢?
我们可以点击 Resource 中的 Shader,选择:Vertex shader 后
然后查看使用到的数据有哪些,如下图:
OK,留意:
然后我们可以将每个 VBV 个、单个 IBV 设置导出 CSV Titles 格式,下面以导出 position.csv 为例
- 根据shader input attribute 定义,定位使用的 vbv序号
- 在 resource 中,找到对应的 vbv
- 给 vbv 的 titles 设置好格式
- 最后 导出 csv
其他的 tangent, normal, uv0, uv1, color0 都可以使用类似的方式导出,如上图,这个模型的数据我是使用下面的格式导出的
因为我们使用的是 unity mesh,那么TEXCOORD0 的 position 我们需要将其 semantic 修改为 POSITION,同理,normal 和 uv0 都使用对应的:NORMAL, TEXCOORD0 来替代
参考如下(注意每个 shader 的 VBV 是干嘛用的,需要自行去查看 shader 怎么使用,以此分析他们的所属的 semantic,因为每个 shader 都有可能不一样):
VBV, IBV 导出的 csv format:
-
position : VBV1 -
float POSITION.x;float POSITION.y;float POSITION.z;
-
tangent : VBV2 -
float TANGENT.x;float TANGENT.y;float TANGENT.z;float TANGENT.w;
half
精度的 tangent :half TANGENT.x;half TANGENT.y;half TANGENT.z;half TANGENT.w;
- 有些精度低一些,为了性能考虑的是有的,抓帧逆向效果都可以看到很多地方有类似的做法
-
normal : VBV3 -
float NORMAL.x;float NORMAL.y;float NORMAL.z;
half
精度的 tangent :half NORMAL.x;half NORMAL.y;half NORMAL.z;
-
uv0 : VBV0 -
half TEXCOORD0.x;half TEXCOORD0.y;byte4p
-
uv1 : VBV0 -
byte4p; half TEXCOORD4.x;half TEXCOORD4.y;
-
color0 : VBV1
float TEXCOORD5.x;float TEXCOORD5.y;float TEXCOORD5.z;float TEXCOORD5.w; byte4p
ubyte COLOR0.x;ubyte COLOR0.y;ubyte COLOR0.z;ubyte COLOR0.w; byte4p
ubyte TEXCOORD5.x;ubyte TEXCOORD5.y;ubyte TEXCOORD5.z;ubyte TEXCOORD5.w; byte4p
- 这个会使用比较多,但是这个有些问题:没有 normalizednubyte TEXCOORD5.x;nubyte TEXCOORD5.y;nubyte TEXCOORD5.z;nubyte TEXCOORD5.w; byte4p
- 然后添加了前缀n
后,还是不能 normalized,所以这个我在工具中特殊弄了一些开关,可是设置 VBV 是否需要 normalized
-
index : IBV - 不需要格式,直接导出,只要确保第二列是 index 的值即可
另外要记得[type][n]p的padding设置,这样才能stride到对应的vertex data 长度
比如下面的法线,使用的是 half 精度的,而且有 36 个 byte 的 padding
那么我们使用: half NORMAL.x;half NORMAL.y;half NORMAL.z;half NORMAL.w;byte36p;
list view 格式即可
GPA buffer list view format 攻略
上面我是提取: 《CFDG》 的方式,可以罗列出下面的 buffer view format,基本上 CFDG 的 MRA 流 PBR 都是这套规则即可
// jave.lin : 下面提供 list view format 的样例,可以用于CTRL+C,V到 GPA list view 中
// 注意 : padding 不要处理
// 注意 : semantic 要设置对 (依赖 shader 分析 对应的作用后,才能正确设置 semantic)
// position-float3
float POSITION.x;float POSITION.y;float POSITION.z;
// tangent-float4
float TANGENT.x;float TANGENT.y;float TANGENT.z;float TANGENT.w;
// tangent-half4
half TANGENT.x;half TANGENT.y;half TANGENT.z;half TANGENT.w;
// normal-float4
float NORMAL.x;float NORMAL.y;float NORMAL.z;float NORMAL.w;
// normal-half4
half NORMAL.x;half NORMAL.y;half NORMAL.z;half NORMAL.w;
// normal-float3
float NORMAL.x;float NORMAL.y;float NORMAL.z;
// normal-half3
half NORMAL.x;half NORMAL.y;half NORMAL.z;
// uv0-float2
float TEXCOORD0.x;float TEXCOORD0.y;
// uv0-half2
half TEXCOORD0.x;half TEXCOORD0.y;
// uv1-float2
float TEXCOORD1.x;float TEXCOORD1.y;
// uv1-half2
half TEXCOORD1.x;half TEXCOORD1.y;
// color0-ubyte4
ubyte COLOR0.x;ubyte COLOR0.y;ubyte COLOR0.z;ubyte COLOR0.w;
工具演示导出
GPA 的 VBV,IBV的 BUG
我之前就怀疑还原模型的话,如果这些 IBV, VBV 长度都不对的话,除了浪费数据,还可能出问题
因为很多 VBV 和 IBV 的元素数量是对不上的
今天在抓帧某个角色的 卧蚕(眼部底下部分) 模型时候就出现这个问题
从下面的 IBV 可以看到1091,1092 之间的索引跨度很大,是有问题的
23901 的索引直接就超出了 VBV 顶点总数的数量
从下面的数据分析, Primitive 总数才 364 个三角形
VBV position 数量才 10313,但是 IBV 的索引已经远超这个范围了,这个很明显是 GPA 的数据显示上的 BUG
所以部分模型的导出可能遇到:
Fail setting triangles. Some indices are referencing out of bounds vertices. IndexCount: 2046, VertexCount: 10314
Fail setting triangles. The number of supplied triangle indices must be a multiple of 3.
must be a multiple of 3 的解决办法
在我的工具中,是这么来处理的:
因为是在 set triangles 的时候发生的错误
那么我们只要将 indices 的长度保持在 缓存元素数量可以整除3即可:indices.Count % 3 == 0
// jave.lin : push padding or remove padding
// jave.lin : 这么处理可以避免 https://blog.csdn.net/linjf520/article/details/127066726 文章中提及的 BUG:
// - Fail setting triangles. Some indices are referencing out of bounds vertices. IndexCount: xxx, VertexCount: xxx
// - Fail setting triangles. The number of supplied triangle indices must be a multiple of 3.
if (indices.Count > 0 && indices.Count % 3 != 0)
{
var loopCount = 0;
var lastOneVal = indices[indices.Count - 1];
var lastOneIsZeroVal = lastOneVal == 0;
while (indices.Count > 0 && indices.Count % 3 != 0)
{
if (loopCount > 10)
break;
loopCount++;
if (lastOneIsZeroVal)
{
// jave.lin : remove padding 如果尾部是 0 索引,我们可以用删除 padding 的方式
indices.RemoveAt(indices.Count - 1);
}
else
{
// jave.lin : push padding, 否则我们使用 push 最后一个顶点作为 padding 的方式
indices.Add(lastOneVal);
}
}
}
indices out of bounds vertices - 暂无解决方法
比如:Failed setting triangles. Some indices are referencing out of bounds vertices. IndexCount: 2049, VertexCount: 281
这个问题是 GPA 的 BUG
暂时无解,连 index 的数值都错了,我们是不可能知道正确的数值的,只能猜:比如,可能是 数据类型溢出?
就是新版本的 fbx 模型,可能使用了 index buffer 的索引值的压缩技术,可能在 GPA 中没有对应实现解析,那么肯定会有报错的
这个 index 索引值的压缩技术大概是这么个思路:
- 如果
index < byte.MaxValue
,那么 index 使用byte
类型解析 - 如果
index < ushort.MaxValue
,那么 使用ushort
类型解析 - 如果
index < uint.MaxValue
,那么 使用uint
类型解析
indices out of bounds vertices - 尝试解决的方法
可以尝试将后续开始 indices 是很大的数值统统删除,保留前面的数据,95% 是OK的,就可以导出来了
Invalid worldAABB.Object is too large or too far away from the origin - 解决方法
看起来是 AABB 太大导致,很有可能是坐标数值出问题
我直接 google : Invalid worldAABB.Object is too large or too far away from the origin
找到一篇: Assertion failed: Invalid worldAABB. Object is too large or too far away from the origin
然后我添加了检测 local position 大于 1000 的都打印出来 (一般来说说不会整这么大的 local pos)
发现输出在: 12506 的 idx position 数值非常大
其实也可以打开 position.csv 使用正则搜索: \b\d+,\d{2}
,一样可以搜索到 大于 2 位以上的数值
如果向定位数值位数,之久修改,比如,超过 百位可以修改为: \b\d+,\d{3}
超过千位: \b\d+,\d{4}
,以此类推,修改后面的 \d{n}
里面的 n
数值即可
OK,如下图,我们找到了异常的数据
我们要思考一下,为何是正常的呢?
很明显,这些是优化 VBV 的数据复用了一些 内存块
然后 GPA 里面没有 resize 这个大小,或是显示的时候,没丢对应的大小进去
导致 GPA 显示一丢后续无用的数据
那我们到底要怎么处理这些无用的数据呢?
怎么判断他是无用的呢?
方法就是: 根据 indices 的数量来定范围即可
比如,indices 里面最大的有效索引值,我们需要先知道,假如叫: var indices_max_val = indices.Max() + 1;
只要遍历顶点属性数据 (vertex attribute) 的时候 (position, color, uv等数据的时候) 的时候
如果这次的遍历次数 大于或等于 indices_max_val
我们就停止,不再处理
但是注意,indices 里面同样有可能有多余的数据,因此我们要看一下索引跨度很大的情况,删除后续无用的数据
我将之前的截图 挪到这再瞄一下,如下图
这样一般就不会出现: Invalid worldAABB.Object is too large or too far away from the origin
的问题
但如果你的模型本身 local pos 就很大的数值,那你应该考虑制作方法是否除了问题
因为一般模型的 local pos 不会很大,否则会有精度问题
如果实在是有这类需求,可以考虑,模型切块,分块加载 (streaming load)
比如,大世界里面的 超大地形的切块,或是 地表模型的切块
另一个问题: 有些TEXCOORD[n].y 是不需要翻转的,因为这些数据不是采样用的
如果我们直接对所有的 TEXCOORD[n].y 都判断是 dx 的就 1- y 的处理的话,那么对于一些模型中,将部分数据存于 TEXCOORD[n].y 的话,就会出错
因此我们额外有添加了其他的选项来确定要不要翻转这些数据
另一个问题:导出的 FBX 中,UV 无法保存 vector3 或是 vector4 的数据 (已有解决方法)
具体参考另一篇: Unity - 导出的FBX模型,无法将 vector4 保存在 uv 中(使用 Unity Mesh 保存即可)
为何方便导出,我将工具扩展了,可以导出 FBX,也可以选择导出 Unity Mesh
对于一些带有自定义的 TEXCOORD[n] 的数据,建议使用 Unity Mesh 导出
对于 *.obj 数据同样有不能保存 vector3, vector4 的问题
选项如下图
增加了 readable 的选项便于导出后就自动设置
对于导出 FBX,unity mesh ,此选项同样都有作用
关于 must be a multiple of 3; indices out of bounds vertices; AABB too large 的终极解决方法
这些这些问题的原因:我这个工具 并没有处理 buffer bounds 也就是没有处理 buffer 的 start offset 和 length
就是对这个 buffer 的数据读取没有做很好 起始idx 和 读取的长度控制,导致读取了 其他的范围的数据,导致无法正确转换
要处理这个问题,可以参考这篇文章: GPA提取模型数据
后续有时间,我会再给这个工具做一次迭代:增加对 indices 的有效长度的输入。
比方说,下面提取《CFDG》里面的一个角色模型的某一部分网格
EVENT ID
找到 24 个 Event ID
找到对应想要的网格
打开网格fx - 关注: Topology 和 Primitive count,决定 indices buffer 的有效长度,关注 slot 信息,决定使用到的 buffer 是哪个
topology 决定 index buffer 元素的拓补结构
primitive count 决定 图元数据量,比如上面是 triangle list (三角形列表),那就是每个元素就是有三个索引对应的顶点组成的三角形,那么意味着:总的顶点索引数量 = 图元数量 * 3
显示不完整,我们将 snipaste 截图 pin 到一块
可以看到,这个网格,有 6 个 slot 输入槽,每个 slot 对应使用到的 buffer 都有显示
比如 slot0
- buffer : B:191
- format R32G32B32A32_FLOAT
- offset : 0
每个 slot 都会对应的信息
Checked - Full API Log
打开这个开关后,就会多出很多 这次 dc 相关的 图形 API 调用函数
关注 IASetIndexBuffer - 关注的是 format (格式)
如下图,可以看到 这个网格的 indices buffer 的情况,
- PIndexBuffer - 使用的是 419 PIndexBuffer 指针的数据,也就是:
(PIndexBuffer*)(419)
- Format : 使用的是
DXGI_FORMAT_R16_UNIT
格式,也就是 也就是 无符号的 16 bit 的 int 类似,也就是ushort
无符号整型 - Offset : 这个
(PIndexBuffer*)(419)
指针数据中,byte 从 0 开始读取
这里我们重点关注 format,就是每个 index buffer 里面的元素都是 ushort
格式的
关注 IASetVertexBuffers - 关注的是 ppVertexBuffers, pStrides, pOffset 决定每个 buffer 的 offset 和 stride (每个元素的 对应格式个数)
下面是正常截图,但是 IASetVertexBuffers
的数据太多,屏幕高度不够截图
因此使用 Snipaste 工具将一些关注的数据截图 pin 到一起显示
注意每一个 buffer 指针的格式
比如:
- ppVertexBuffers 是一个
(ID3D11Buffer**)
的格式 - pStrides 是
(UINT*)
- pOffsets 是
(UINT*)
如何截图这块信息
上面截图可以看到,红绿蓝 三种框选
每种颜色都是一起关联使用的
比如,红色框选这么理解,其他以此类推
buffer
:191
,这个可以理解为一个指针地址stride
:12
,可以理解为,每个元素用到(UINT)
对应的数量offset
:1345200
,可以理解为,从这个191
buffer 里面偏移(UNIT*)
指针元素的1345200
个元素后在读取后续数据的意思
这样确定 indices 数量,然后其他顶点数据读取时,安装上面的 buffer, stride, offset 就可以精准读取了
Project
- github 工程: javelinlin/GPA_CSV2MESH_TOOL_pure_version
- GPA_CSV2MESH_TOOL_opt_sss_吕蒙_白起_马云禄_再次优化_projectsetting_shadowsize_法线RG解析
- GPA_CSV2MESH_TOOL_甘宁_吕蒙_马云禄_孙尚香_雕像_V2.rar
- GPA_CSV2MESH_TOOL_pure_version.rar
References
- GPA-Mesh - hub友 C++写的一个,没用过,也不知道怎么用
- INTEL GPA mesh ripper tutorial + tool (x32,x64,DX9,10,11) - 2014 年的时候国外友人写了 GPA Hook 来抓自己想要的资源