【Python FBX SDK】法线平滑工具

前言

三年没在博客写文了,快要查无此人了。一晃就三年过去了,三年前还是刚刚毕业的菜鸡,三年后的现在,还是菜哈哈。
之前一直在做客户端系统(直白说就是UI拼图仔)和SDK相关的工作,兜兜转转一圈回来后发现,果然还是TA更适合我哈哈。

好了废话不多说,进入今天的正题。最近恰好在做Back Facing描边的相关工作,理所当然的遇到了模型硬边描边断裂的问题,网上一搜恰好看到了这篇很好的文章,真是瞌睡来了遇到枕头。

https://blog.csdn.net/oldside1/article/details/106456379

不过原文作者是用C++写的,网搜也搜了一圈发现好像还没有Python版本的(FBX SDK明明也有Python版本的说)。
没办法,只能自己动手用Python写一遍。

思路

代码实现思路很简单,找到Mesh中控制点的所有法线,把这些法线按特定的算法平均一下就可以。算法有好几种,直接平均,
按面积加权平均,按夹角加权平均。这里偷懒就直接加起来平均吧…

顺带一提,原文作者用了两个for循环嵌套去查找Mesh的控制点,在模型顶点数量少的时候还好,但当我用一个2.7w面数,8w顶点的模型去测试的时候,时间复杂度O( n 2 n^2 n2)的算法,光查找控制点就要执行64亿次,直接两眼一黑。没办法,只能再优化一下算法,把所有数据以控制点为key,法线列表为value,用字典存起来,这样我们只需O(n)的复杂度就能完成控制点和法线数据的查找。

代码

获取所有控制点及其法线

 # 控制顶点字典        <int, List<FbxVector4>>  <顶点索引,法线列表>
 vertCtrlPoint = {}
 # 控制顶点平均法线字典  <int, FbxVector4>       <顶点索引, 平均法线>
 vertAvgNormal = {}

 # 先取到所有控制点的法线
 for j in range(polygonVetexCount):
     #print("正在读取", j, "/", polygonVetexCount)
     rCurVertexIndex = polygonVertices[j]
     rNormal: FbxVector4 = vertNormalArray[j]
     if rCurVertexIndex not in vertCtrlPoint:
         normals = [rNormal]
         vertCtrlPoint[rCurVertexIndex] = normals
     else:
         if rNormal not in vertCtrlPoint[rCurVertexIndex]:
             vertCtrlPoint[rCurVertexIndex].append(rNormal)

得到了控制点所有的法线数据,就能够计算平均法线了,这里可以换成其他的平均算法。

# 平均法线值
for key, value in vertCtrlPoint.items():
    weightNormal = FbxVector4(0, 0, 0, 0)
    for k in range(len(value)):
        weightNormal += value[k]
    weightNormal.Normalize()
    vertAvgNormal[key] = weightNormal

其余就和之前的代码一样了,把法线转换到切线空间,并映射到颜色通道里去(因为取值范围不同,向量[-1, 1] ->顶点色[0, 1])

# 构建TBN,转换到切线空间下
normal = vertNormalArray[m]
tangent = vertTangentArray[m]
binormal = vertBinormalArray[m]
tempVector = FbxVector4(tangent.DotProduct(smoothNormal),
                        binormal.DotProduct(smoothNormal),
                        normal.DotProduct(smoothNormal),
                        0)

smoothNormal = tempVector
smoothNormal.Normalize()

# 映射到顶点色中
color = FbxColor()
color.mRed = smoothNormal[0] * 0.5 + 0.5
color.mGreen = smoothNormal[1] * 0.5 + 0.5
color.mBlue = smoothNormal[2] * 0.5 + 0.5
color.mAlpha = vertColorArray[vertColorIndex].mAlpha

补充

贴一个创建顶点色通道的代码,自己要去开Maya再加顶点色再导出来确实挺麻烦的

def AddVertColor(self, node:FbxNode):
	if node.GetChildCount() > 0:
	    for i in range(node.GetChildCount()):
	        mesh = node.GetChild(i).GetMesh()
	        if mesh is not None :
	            layer = mesh.GetLayer(0)
	            vertexColor = FbxLayerElementVertexColor.Create(mesh, "")
	            vertexColor.SetMappingMode(FbxLayerElement.eByPolygonVertex)
	            vertexColor.SetReferenceMode(FbxLayerElement.eIndexToDirect)
	            vertexColorArray = vertexColor.GetDirectArray()
	            vertexIndexArray = vertexColor.GetIndexArray()
	            for j in range(mesh.GetPolygonVertexCount()):
	                vertexColorArray.Add(FbxColor(1, 1, 1, 1))
	                vertexIndexArray.Add(j)
	            layer.SetVertexColors(vertexColor)
	        self.AddVertColor(node.GetChild(i))

还有一件事,GetXXX()方法的开销很大,建议缓存起来

# 缓存数据,减少调用GetXXX()产生的开销
polygonVetexCount = mesh.GetPolygonVertexCount()
polygonVertices = mesh.GetPolygonVertices()
vertNormalArray = vertNormals.GetDirectArray()
vertTangentArray = vertTangents.GetDirectArray()
vertBinormalArray = vertBinormals.GetDirectArray()
vertColorArray = vertColor.GetDirectArray()
vertColorIndexArray = vertColor.GetIndexArray()

没缓存前处理8w顶点的模型耗时大概需要3分钟,缓存后…
在这里插入图片描述
快的令我有点诧异,居然耗时一秒都不到…

小结

本文只提供了Python版本及优化后的实现思路,涉及具体的原理建议去开头的文章里去看看,这里就不在过多赘述了。
该脚本可以整合加入到Maya工作流当中,实现静默处理,十分便利,不过现在手上还有动画相关的内容要处理,就先到此为止吧。

最后

还有一件事,我把源码放到GitHub上了,同时里面附带了Python3.7版本的FBX SDK库,有需要的小伙伴可以自行下载哦。
链接:AvgNormal-Python-FBX-SDK

就这样,12点了该睡觉了。
咱们下次见QwQ ,晚安好梦。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值