Maya:将普通摄影机转换为“camera and aim”组方法咨询
核心原理
Camera & Aim组的本质是拆分摄影机的位置控制与朝向控制:Aim节点负责定义摄影机的看向方向,摄影机作为子节点依附于Aim节点,通过局部空间偏移保持与Aim节点的相对位置关系。转换时需将原摄影机的每帧世界变换,分解为Aim节点的全局变换 + 摄影机在Aim节点局部空间的变换。
矩阵计算方法(核心公式)
假设使用右手坐标系(如Maya、3ds Max),摄影机看向方向为**-Z轴**,上方向为**+Y轴**。
1. 提取原摄影机的帧数据
对每一帧,提取原摄影机的4x4世界变换矩阵 $\mathbf{M}_{cam}$,分解为:
- 世界位置向量:$\mathbf{P}{cam} = (M{cam}[3][0], M_{cam}[3][1], M_{cam}[3][2])$
- 世界空间朝向向量:$\mathbf{D}{cam} = (-M{cam}[2][0], -M_{cam}[2][1], -M_{cam}[2][2])$(摄影机的-Z轴,即看向方向,需归一化)
- 旋转矩阵:$\mathbf{R}{cam}$($\mathbf{M}{cam}$的前3x3子矩阵,代表摄影机的世界旋转)
2. 计算Aim节点的变换
Aim节点的旋转需与原摄影机保持一致(确保朝向匹配),位置可自定义:
- 动态目标点方案:将Aim节点放在原摄影机前方固定距离处,位置为:
$$\mathbf{P}{aim} = \mathbf{P}{cam} + \mathbf{D}_{cam} \times L$$
其中 $L$ 是你设定的Aim节点与摄影机的距离(如10单位)。 - Aim节点的世界矩阵 $\mathbf{M}{aim}$:
$$
\mathbf{M}{aim} = \begin{bmatrix}
\mathbf{R}{cam}[0][0] & \mathbf{R}{cam}[0][1] & \mathbf{R}{cam}[0][2] & \mathbf{P}{aim}[0] \
\mathbf{R}{cam}[1][0] & \mathbf{R}{cam}[1][1] & \mathbf{R}{cam}[1][2] & \mathbf{P}{aim}[1] \
\mathbf{R}{cam}[2][0] & \mathbf{R}{cam}[2][1] & \mathbf{R}{cam}[2][2] & \mathbf{P}{aim}[2] \
0 & 0 & 0 & 1
\end{bmatrix}
$$
3. 计算摄影机的局部变换
摄影机在Aim节点局部空间的矩阵 $\mathbf{M}{local}$ 由原矩阵与Aim矩阵的逆矩阵相乘得到:
$$\mathbf{M}{local} = \mathbf{M}{aim}^{-1} \times \mathbf{M}{cam}$$
由于 $\mathbf{M}{aim}$ 是刚体变换,其逆矩阵可简化为:
$$\mathbf{M}{aim}^{-1} = \begin{bmatrix}
\mathbf{R}{cam}^T & -\mathbf{R}{cam}^T \times \mathbf{P}{aim} \
0 & 1
\end{bmatrix}$$
最终 $\mathbf{M}{local}$ 是纯平移矩阵,平移量为:
$$\mathbf{P}{local} = \mathbf{R}{cam}^T \times (\mathbf{P}{cam} - \mathbf{P}{aim})$$
即摄影机在Aim节点局部空间的位置。
API实现思路(以Maya Python API为例)
import maya.OpenMaya as om import maya.cmds as cmds def convert_to_camera_aim(cam_name, aim_distance=10): # 获取原摄影机的DAG路径 sel_list = om.MSelectionList() sel_list.add(cam_name) cam_dag = om.MDagPath() sel_list.getDagPath(0, cam_dag) # 创建Aim节点和子摄影机 aim_node = cmds.createNode("transform", name=f"{cam_name}_aim") child_cam = cmds.duplicate(cam_name, name=f"{cam_name}_child")[0] cmds.parent(child_cam, aim_node) # 获取原摄影机的动画帧范围 start_frame = cmds.playbackOptions(q=True, min=True) end_frame = cmds.playbackOptions(q=True, max=True) # 遍历每一帧设置变换 for frame in range(int(start_frame), int(end_frame)+1): cmds.currentTime(frame) # 提取原摄影机的世界矩阵 mfn_cam = om.MFnTransform(cam_dag) world_matrix = mfn_cam.transformationMatrix().asMatrix() # 提取位置和朝向 cam_pos = om.MPoint(world_matrix(3,0), world_matrix(3,1), world_matrix(3,2)) forward = om.MVector(-world_matrix(2,0), -world_matrix(2,1), -world_matrix(2,2)).normal() # 设置Aim节点位置和旋转 aim_pos = cam_pos + forward * aim_distance cmds.setAttr(f"{aim_node}.translate", aim_pos.x, aim_pos.y, aim_pos.z) # 复制原摄影机的旋转 cam_rot = cmds.getAttr(f"{cam_name}.rotate")[0] cmds.setAttr(f"{aim_node}.rotate", cam_rot[0], cam_rot[1], cam_rot[2]) # 计算子摄影机的局部位置 local_pos = om.MVector(cam_pos - aim_pos) * world_matrix.inverse().rotation() cmds.setAttr(f"{child_cam}.translate", local_pos.x, local_pos.y, local_pos.z) # 设置关键帧 cmds.setKeyframe(f"{aim_node}.translate", f"{aim_node}.rotate", time=frame) cmds.setKeyframe(f"{child_cam}.translate", time=frame) # 隐藏原摄影机 cmds.setAttr(f"{cam_name}.visibility", False) # 使用示例 convert_to_camera_aim("persp", aim_distance=15)
替代方案:使用软件内置约束
如果不想手动计算矩阵,可直接用3D软件的内置约束快速实现:
- 创建Aim节点,将原摄影机父化到Aim节点下。
- 给原摄影机添加Aim约束,目标选择Aim节点。
- 调整约束的轴向(匹配摄影机的朝向轴),设置偏移距离。
- 烘焙约束的动画到Aim节点和摄影机的关键帧,删除约束。
注意事项
- 坐标系适配:不同软件的坐标系(如Blender使用左手坐标系,摄影机看向+Z轴)需调整向量方向。
- 动画平滑:转换后可使用软件的曲线平滑工具优化关键帧,避免动画卡顿。
- 动态目标点:若需Aim节点看向固定点,只需将 $\mathbf{P}_{aim}$ 设为固定坐标即可。
内容的提问来源于stack exchange,提问作者Ben.Zh

