前言
这个需求是好久个月前就已经实现了的,这里补充一下文章,记录实现的过程。
需求是这样的,描边需要用额外的法线信息进行处理,但是当前角色的法线已经有软硬的用途。
因此需要将额外的圆滑过的法线信息写入到 切线 中,引擎的 shader 读取切线进行描边绘制。
最开始要做这个需求,我想到的是利用 OpenMaya 的 API 将法线写入到切线就完事了。
然而我大意了,我发现 Maya 的 MFnMesh 只有获取切线信息的没有修改的功能 链接
在网上也搜了一遍,似乎也没有很好的解决方案。
不过我的前导师有给我留了一手,虽然他的脚本是对接 Unity 的,但是思路是完全可以借鉴的。
他的实现方式是将 FBX 保存为 ASCII 模式,然后用正则表达式将模型的法线信息替换到切线信息上。
这个方案用纯 Python 即可实现,方便高效。
只是我觉得用 FBX SDK 的话或许效率会更高,也能直接处理 Binary 的文件。
FBX SDK 使用
关于 FBX SDK 是什么,如何使用,我在之前写的文章有所介绍 链接
FBX Python SDK 里面有支持 Py2.7 和 Py3.3 的 pyd 脚本,并且里面还有代码案例,可以参考学习。
Py3.7 版本有需要可以在这篇文章末尾拿到 链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import fbx import FbxCommon
def clear_uv(output_file): """删除第一张 UV""" manager, scene = FbxCommon.InitializeSdkObjects() result = FbxCommon.LoadScene(manager, scene, output_file) if not result: return nodes = scene.GetRootNode() mesh = nodes.GetChild(0).GetMesh() uv = mesh.GetElementUV(0) mesh.RemoveElementUV(uv) FbxCommon.SaveScene(manager, scene, output_file)
|
上面是我自己写的一个简单的示例代码,可以清理 FBX 里面的第一份 UV 信息。
使用上和 C++ API保持一致,返回的数据结构可以查 C++ 文档
我自己写的或者收集的东西放到这个仓库下了 链接
FBX SDK 将模型法线写入到切线
思路: 输出两个 FBX 一个是正常输出,一个将模型的法线平滑再输出。
输出完成之后将平滑法线的模型 写入到 切线 上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| import os import tempfile
import pymel.core as pm import pymel.core.nodetypes as nt from maya import mel
import FbxCommon from fbx import FbxDocumentInfo,FbxNodeAttribute
SOFT_FBX_PATH = os.path.join(tempfile.gettempdir(), "soft_normal.fbx")
class TangentHandler(object):
info = {}
@classmethod def _export_soft_fbx(cls): """导出 平滑 法线的 FBX""" meshes = cls._soft_mesh() pm.select(meshes, r=True) mel.eval('FBXExport -f "%s" -s' % SOFT_FBX_PATH.replace("\\", "/")) pm.delete(meshes)
@classmethod def _soft_mesh(cls): """平滑法线""" new_meshes = [] for mesh in pm.ls(sl=1, dag=1): if hasattr(mesh, "getShape") and isinstance(mesh.getShape(), nt.Mesh): outline_mesh = mesh + "_outline" if not pm.objExists(outline_mesh): outline_mesh = pm.duplicate(mesh, n=outline_mesh)[0] pm.polyNormalPerVertex(outline_mesh, unFreezeNormal=True) pm.polySoftEdge(outline_mesh, angle=0, constructionHistory=False) pm.polyAverageNormal(outline_mesh) new_meshes.append(outline_mesh) return new_meshes
@staticmethod def _read_fbx_attribute(path, attribute): """读取 FBX 属性""" manager, scene = FbxCommon.InitializeSdkObjects() result = FbxCommon.LoadScene(manager, scene, path) assert result, u"无法打开 FBX 文件 %s" % path
data = {} for i in range(scene.GetNodeCount()): node = scene.GetNode(i) mesh = node.GetNodeAttribute() if not mesh or mesh.GetAttributeType() != FbxNodeAttribute.eMesh: continue attr = getattr(mesh, "GetElement%s" % attribute.capitalize())() if not attr: continue data[node.GetName()] = attr.GetDirectArray()
return {"manager": manager, "scene": scene, "data": data}
@classmethod def set_tangent(cls, fbx_path, meshes): pm.select(meshes) cls._export_soft_fbx() origin_data = cls._read_fbx_attribute(fbx_path, "Tangent") target_data = cls._read_fbx_attribute(SOFT_FBX_PATH, "Normal") manager = origin_data["manager"] scene = origin_data["scene"] tangent_data = origin_data["data"] normal_data = target_data["data"]
for name, array in tangent_data.items(): normals = normal_data[name + "_outline"] array.Clear() for normal in normals: array.Add(normal)
FbxCommon.SaveScene(manager, scene, fbx_path, 0)
fbx_path = r"E:\MayaTecent\MayaScript\FBXSDK\box.fbx"
export_meshes = [mesh for mesh in pm.ls(ni=1,assemblies=1) if isinstance(mesh.getShape(),nt.Mesh)] TangentHandler.set_tangent(fbx_path, export_meshes)
|
总结
默认不修改引擎的情况下,导入的切线无法通过重新导入来更新。
并且切线信息的调用上,也会有些问题,unreal 会把切线当成正常的切线进行计算。