Maya如何获取设置世界空间顶点法线,解决烘焙自定义枢轴法线错乱问题
Maya烘焙自定义枢轴时保持自定义法线不变的问题
我正在开发一款用于烘焙自定义枢轴的小型工具,本身功能并不特殊。但在使用中我发现,执行bakeCustomPivot命令时会破坏自定义法线,而在我的需求中,法线朝向必须全程保持不变。我最初想到的方案是在烘焙枢轴前保存顶点法线,待烘焙完成后恢复法线,但实际操作时遇到了问题:
调用
cmds.polyNormalPerVertex( q=1, normalX=1)查询到的法线值,在枢轴烘焙前后返回结果完全一致,这是因为法线朝向会和对象枢轴朝向同步变换,因此局部空间下的法线数值不会发生变化。我也曾尝试先计算枢轴烘焙前后的朝向差值,再将差值叠加到法线数值上,也没有获得预期效果。
经过梳理我打算更换实现思路:直接获取世界空间下的顶点法线数值,这样就不会受局部枢轴变换的影响,但目前我没有找到对应的实现方法。
你可以运行下方测试代码更清晰地复现问题:
import maya.cmds as cmds import maya.mel as mel # create test plane plane = cmds. polyPlane(axis=[0,0,1], subdivisionsHeight=2, subdivisionsWidth=2) cmds.select(plane[0]+'.vtx[*]') objVertices = cmds.ls(sl=1,fl=1) # Activate normals preview cmds.select(plane) mel.eval('ToggleVertexNormalDisplay') # Change plane normals to any cmds.polyNormalPerVertex(objVertices, normalXYZ=[0.3, 0.4, 1.0]) # get vtx normal vector for one vtx before bake cmds.select(objVertices[0]) oldVtxNormalX = cmds.polyNormalPerVertex( q=1, normalX=1) oldVtxNormalY = cmds.polyNormalPerVertex( q=1, normalY=1) oldVtxNormalZ = cmds.polyNormalPerVertex( q=1, normalZ=1) oldVtxValue = list(map(lambda x: x[0], [oldVtxNormalX] + [oldVtxNormalY] + [oldVtxNormalZ])) # Warning. Why values are differ from custom values? They are smaller print ( '{} --------- Old normal value '.format(oldVtxValue) ) # [0.26832816004753113, 0.3577708899974823, 0.8944271802902222] # Rotate pivot, bake it (Modify/Bake Pivot) cmds.select(plane) cmds.manipPivot(o=[45.0, 0.0, 0.0]) # apply pivot mel.eval('BakeCustomPivot') # get vtx normal vector for one vtx after bake cmds.select(objVertices[0]) newVtxNormalX = cmds.polyNormalPerVertex( q=1, normalX=1) newVtxNormalY = cmds.polyNormalPerVertex( q=1, normalY=1) newVtxNormalZ = cmds.polyNormalPerVertex( q=1, normalZ=1) newVtxValue = list(map(lambda x: x[0], [newVtxNormalX] + [newVtxNormalY] + [newVtxNormalZ])) print ( '{} --------- New normal value after bake. Same result '.format(newVtxValue) ) # [0.26832816004753113, 0.3577708899974823, 0.8944271802902222] # !!!! As you can see normal values still same despite of fact pivot have changed
解决方法
核心原理
polyNormalPerVertex默认返回的是局部空间下的法线值,所以枢轴变换后局部值不变但世界朝向已经偏移。要固定世界空间下的法线朝向,只需要做两步坐标空间转换:
- 烘焙枢轴前,将每个顶点的局部法线转换为世界空间法线并存储
- 烘焙枢轴完成后,将存储的世界空间法线转换为新的局部空间,再写回顶点
具体实现代码
你可以在原有逻辑的基础上加入空间转换的逻辑,示例代码如下:
import maya.cmds as cmds import maya.mel as mel import maya.api.OpenMaya as om def get_world_normals(mesh_name): sel = om.MSelectionList() sel.add(mesh_name) mesh_dag = sel.getDagPath(0) mesh_fn = om.MFnMesh(mesh_dag) # 直接获取世界空间下的所有顶点法线 normals = mesh_fn.getNormals(om.MSpace.kWorld) return [ (n.x, n.y, n.z) for n in normals ] def set_normals_from_world(mesh_name, world_normals): sel = om.MSelectionList() sel.add(mesh_name) mesh_dag = sel.getDagPath(0) mesh_fn = om.MFnMesh(mesh_dag) # 把世界空间法线转成当前局部空间再写回 local_normals = [] # 法线空间转换需要使用世界矩阵的逆转置矩阵 world_inverse_transpose = mesh_dag.inclusiveMatrixInverse().transpose() for n in world_normals: world_normal = om.MVector(n) local_normal = world_normal * world_inverse_transpose local_normal.normalize() local_normals.append(local_normal) mesh_fn.setNormals(local_normals, om.MSpace.kObject) # 原有业务逻辑 plane = cmds.polyPlane(axis=[0,0,1], subdivisionsHeight=2, subdivisionsWidth=2)[0] cmds.select(plane+'.vtx[*]') objVertices = cmds.ls(sl=1,fl=1) mel.eval('ToggleVertexNormalDisplay') cmds.polyNormalPerVertex(objVertices, normalXYZ=[0.3, 0.4, 1.0]) # 步骤1:烘焙前保存世界空间法线 pre_bake_world_normals = get_world_normals(plane) # 执行枢轴烘焙 cmds.select(plane) cmds.manipPivot(o=[45.0, 0.0, 0.0]) mel.eval('BakeCustomPivot') # 步骤2:烘焙后恢复世界空间法线 set_normals_from_world(plane, pre_bake_world_normals)
注意事项
- 这里用到了Maya的OpenMaya API来处理空间转换,比纯用cmds手动计算矩阵更高效准确
- 写入法线前做了归一化处理,避免法线长度异常导致的渲染问题
- 支持任意枢轴变换(平移、旋转、缩放)后的法线保持,不需要单独计算枢轴的变换差值
内容的提问来源于stack exchange,提问作者Andrew Salov
相关产品推荐
相关产品推荐

