BVH 文件解析和 FK 过程

本文将对 BVH 文件进行讲解,用 Python 代码用递归下降的方式解析。解析完成后,通过前向运动学(Forward Kinematics)方法进行计算,并使用 panda3d 库进行播放。

BVH 文件介绍

BVH 是一种通用的人体特征动画文件格式,基于人体关节(Joint)的树状结构进行存储。

BVH 文件分为 Hierarchy 和 Motion 两部分, Hierarchy部分是描述虚拟角色的树形结构,Motion 部分是记录每一帧虚拟角色运动的姿态。下面是一个标准的 BVH 文件。

HIERARCHY
ROOT RootJoint
{
    OFFSET   0.000000   0.000000   0.000000
    CHANNELS 6 Xposition Yposition Zposition Xrotation Yrotation Zrotation
    JOINT lHip
    {
        OFFSET   0.100000  -0.051395   0.000000
        CHANNELS 3 Xrotation Yrotation Zrotation
        JOINT lKnee
        {
            OFFSET   0.000000  -0.410000   0.000000
            CHANNELS 3 Xrotation Yrotation Zrotation
            JOINT lAnkle
            {
                OFFSET   0.000000  -0.390000   0.000000
                CHANNELS 3 Xrotation Yrotation Zrotation
                JOINT lToeJoint
                {
                    OFFSET   0.000000  -0.050000   0.130000
                    CHANNELS 3 Xrotation Yrotation Zrotation
                    End Site
                    {
                        OFFSET   0.010000   0.002000   0.060000
                    }
                }
            }
        }
    }
    JOINT pelvis_lowerback
    {
        OFFSET   0.000000   0.093605   0.000000
        CHANNELS 3 Xrotation Yrotation Zrotation
        JOINT lowerback_torso
        {
            OFFSET   0.000000   0.100000   0.000000
            CHANNELS 3 Xrotation Yrotation Zrotation
            JOINT lTorso_Clavicle
            {
                OFFSET   0.001000   0.157500   0.000000
                CHANNELS 3 Xrotation Yrotation Zrotation
                JOINT lShoulder
                {
                    OFFSET   0.117647   0.000000   0.000000
                    CHANNELS 3 Xrotation Yrotation Zrotation
                    JOINT lElbow
                    {
                        OFFSET   0.245000   0.000000   0.000000
                        CHANNELS 3 Xrotation Yrotation Zrotation
                        JOINT lWrist
                        {
                            OFFSET   0.240000   0.000000   0.000000
                            CHANNELS 3 Xrotation Yrotation Zrotation
                            End Site
                            {
                                OFFSET   0.116353  -0.002500   0.000000
                            }
                        }
                    }
                }
            }
            JOINT rTorso_Clavicle
            {
                OFFSET  -0.001000   0.157500   0.000000
                CHANNELS 3 Xrotation Yrotation Zrotation
                JOINT rShoulder
                {
                    OFFSET  -0.117647   0.000000   0.000000
                    CHANNELS 3 Xrotation Yrotation Zrotation
                    JOINT rElbow
                    {
                        OFFSET  -0.245000   0.000000   0.000000
                        CHANNELS 3 Xrotation Yrotation Zrotation
                        JOINT rWrist
                        {
                            OFFSET  -0.240000   0.000000   0.000000
                            CHANNELS 3 Xrotation Yrotation Zrotation
                            End Site
                            {
                                OFFSET  -0.116353  -0.002500   0.000000
                            }
                        }
                    }
                }
            }
            JOINT torso_head
            {
                OFFSET   0.000000   0.282350   0.000000
                CHANNELS 3 Xrotation Yrotation Zrotation
                End Site
                {
                    OFFSET   0.000000   0.192650   0.000000
                }
            }
        }
    }
    JOINT rHip
    {
        OFFSET  -0.100000  -0.051395   0.000000
        CHANNELS 3 Xrotation Yrotation Zrotation
        JOINT rKnee
        {
            OFFSET   0.000000  -0.410000   0.000000
            CHANNELS 3 Xrotation Yrotation Zrotation
            JOINT rAnkle
            {
                OFFSET   0.000000  -0.390000   0.000000
                CHANNELS 3 Xrotation Yrotation Zrotation
                JOINT rToeJoint
                {
                    OFFSET   0.000000  -0.050000   0.130000
                    CHANNELS 3 Xrotation Yrotation Zrotation
                    End Site
                    {
                        OFFSET  -0.010000   0.002000   0.060000
                    }
                }
            }
        }
    }
}
MOTION
Frames: 2
Frame Time:   0.016667
-0.001735   0.855388   0.315499   2.008551   7.606260  -0.798294  11.216058  -3.286777  -1.592436  13.521250  -1.153514  -4.213484 -17.754157  -3.216621   9.232892  -7.948705   0.211932  -1.528529   2.220789  -0.981058  -1.133630   2.071938  -6.311876   2.083844   2.020309  -0.533885 -19.342332  -5.129554 -37.575293 -50.190804   0.198025 -24.741038   4.442069   0.442380   2.547494   4.858004   1.951773  -5.809334  21.100535  23.710456  30.003467  53.240376   0.414981  10.414544   1.952633   3.576914  -9.482057   6.918939   1.457480  -0.035296   0.111891 -27.722826  -1.655032   2.430426  -2.964232  -5.507982   1.444119   2.239212  -3.180259  -0.892285  -0.008100  -0.007000   0.024400
-0.003810   0.853981   0.337002   2.017405   7.825929  -1.809751  11.713970  -2.355625   0.062023  15.198954  -1.861308  -4.389417 -17.189762  -3.614663   9.244711  -9.397213   0.262158  -1.565413   2.647300  -1.021514   0.131973   1.458470  -6.632789   1.868957   1.928817  -0.148344 -19.543616  -3.937845 -37.139413 -49.957499   0.204371 -24.672734   4.317351   0.916151   2.440320   4.849158   2.068848  -5.518149  21.184327  23.795785  30.805519  52.865110   0.417817  10.118764   1.952408   3.104611  -9.774695   7.021717   1.448893  -0.004226   0.069402 -27.594885  -2.728812   3.499348  -1.670271  -5.527619   1.835016   6.676684  -3.330738  -4.015991  -0.008600  -0.007000   0.026400

Hierarchy 部分

Hierarchy 描述了骨骼的树形结构,比如 rKnee 是一个关节(Joint):

JOINT rKnee
{
	OFFSET   0.000000  -0.410000   0.000000
	CHANNELS 3 Xrotation Yrotation Zrotation
	JOINT rAnkle
	{
   		...
	}
}
  • OFFSET - 当前结点相对于父结点的相对位置;
  • CHANNELS - 表示欧拉角的旋转顺序;
  • JOINT - 表示子节点,可能有多个。

RootJoint 是一个根节点(Root):

ROOT RootJoint
{
    OFFSET   0.000000   0.000000   0.000000
    CHANNELS 6 Xposition Yposition Zposition Xrotation Yrotation Zrotation
    JOINT lHip
    {
    	...
    }
}

ROOT 与 JOINT 的不同之处在于 CHANNELS 属性有6个维度,前三维是该骨骼对应的 X, Y, Z 三个轴的顺序。一般来说,根节点的 OFFSET 为 (0, 0, 0) 。

END Site 是骨骼的末端,即骨骼树的叶子结点。

End Site
{
	OFFSET  -0.010000   0.002000   0.060000
}

显然,只需要 OFFSET 即可表示。

Motion 部分

Motion 部分有以下信息:

  1. Frames 表示接下来动画中帧的数量;

  2. Frame Time 表示帧率,即每帧持续时间;

  3. 接下来每一行代表一帧中的运动数据。这些数据, 是按照前面 CHANNEL 定义顺序出现的. 按照上面 BVH 结构的定义, 首先是根关节的平移量:Xposition, Yposition, Zposition, 接下来是根关节的旋转量:Xrotation, Yrotation, Zrotation ,然后是各个关节的旋转量。

Frames: 2
Frame Time:   0.016667
-0.001735   0.855388   0.315499   2.008551   7.606260  -0.798294  11.216058  -3.286777  -1.592436  13.521250  -1.153514  -4.213484 -17.754157  -3.216621   9.232892  -7.948705   0.211932  -1.528529   2.220789  -0.981058  -1.133630   2.071938  -6.311876   2.083844   2.020309  -0.533885 -19.342332  -5.129554 -37.575293 -50.190804   0.198025 -24.741038   4.442069   0.442380   2.547494   4.858004   1.951773  -5.809334  21.100535  23.710456  30.003467  53.240376   0.414981  10.414544   1.952633   3.576914  -9.482057   6.918939   1.457480  -0.035296   0.111891 -27.722826  -1.655032   2.430426  -2.964232  -5.507982   1.444119   2.239212  -3.180259  -0.892285  -0.008100  -0.007000   0.024400
...

总而言之,每个 CHANNEL 按照顺序对应 Motion 中的每个数据。

BVH 文件解析

根据上述说明,对于 Hierarchy 部分,我们建立的骨骼树应该包括以下三种结点:

class root(object):
    def __init__(self, parent, name, offset, channel):
        self.parent = parent
        self.name = name
        self.offset = offset
        self.channel = channel	# 6
        self.children = []
        
class joint(object):
    def __init__(self, parent, name, offset, channel):
        self.parent = parent
        self.name = name
        self.offset = offset
        self.channel = channel	# 3
        self.children = []
        
class end(object):
    def __init__(self, parent, name, offset, channel):
        self.parent = parent
        self.name = name
        self.offset = offset

但是实际上用类的方式来储存和访问各个结点情况过于冗余了:因为骨骼树结构很简单,而且自上而下可以给每个关节都赋予一个编号,用数组记录每个关节对应的 name, parent, offset 以及 channel 的情况即可表达所有的骨骼树信息。

为了解析 Hierarchy 部分,我们先定义一个 hierarchy_parser 类并预处理得到 HIERARCHY 部分:

class hierarchy_parser(object):

    def __init__(self, bvh_file_path):
        self.lines = get_hierarchy_lines(bvh_file_path)
        self.line_number = 0

        self.root_position_channel = []
        self.joint_rotation_channels = []

        self.joint_names = []
        self.joint_parents = []
        self.joint_offsets = []

    def get_hierarchy_lines(bvh_file_path):
        hierarchy_lines = []
        for line in open(bvh_file_path, 'r'):
            line = line.strip()
            if line.startwith('MOTION'):
                break
            else:
                hierarchy_lines.append(line)

        return hierarchy_lines

然后用递归下降的思想,分别编写三种类的解析函数:

class hierarchy_parser(object):    
    # ...
    
    def parse_offset(self, line):
        return [float(x) for x in line.split()[1:]]
    
    def parse_channels(self, line):
        return [x for x in line.split()[2:]]

    def parse_root(self, parent=-1):
        self.joint_parents.append(parent)

        self.joint_names.append(self.lines[self.line_number].split()[1])
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find root offset')
        self.line_number += 1

        if self.lines[self.line_number].startswith('CHANNELS'):
            channels = self.parse_channels(self.lines[self.line_number])
            if self.lines[self.line_number].split()[1] == '3':
                self.joint_rotation_channels.append((channels[0], channels[1], channels[2]))
            elif self.lines[self.line_number].split()[1] == '6':
                self.root_position_channels.append((channels[0], channels[1], channels[2]))
                self.joint_rotation_channels.append((channels[3], channels[4], channels[5]))
        else:
            print('cannot find root channels')
        self.line_number += 1

        while self.lines[self.line_number].startswith('JOINT'):
            self.parse_joint(0)
        self.line_number += 1

    def parse_joint(self, parent):
        self.joint_parents.append(parent)

        index = len(self.joint_names)
        self.joint_names.append(self.lines[self.line_number].split()[1])
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find joint offset')
        self.line_number += 1

        if self.lines[self.line_number].startswith('CHANNELS'):
            channels = self.parse_channels(self.lines[self.line_number])
            if self.lines[self.line_number].split()[1] == '3':
                self.joint_rotation_channels.append((channels[0], channels[1], channels[2]))
        else:
            print('cannot find joint channels')
        self.line_number += 1

        while self.lines[self.line_number].startswith('JOINT') or \
                self.lines[self.line_number].startswith('End'):
            if self.lines[self.line_number].startswith('JOINT'):
                self.parse_joint(index)
            elif self.lines[self.line_number].startswith('End'):
                self.parse_end(index)
        self.line_number += 1

    def parse_end(self, parent):
        self.joint_parents.append(parent)

        self.joint_names.append(self.joint_names[parent] + '_end')
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find joint offset')
        self.line_number += 2

最后提供一个解析的入口:

class hierarchy_parser(object):    
    # ...
    
    def analyze(self):
        if not self.lines[self.line_number].startswith('HIERARCHY'):
            print('cannot find hierarchy')
        self.line_number += 1

        if self.lines[self.line_number].startswith('ROOT'):
            self.parse_root()
        
        return self.joint_names, self.joint_parents, self.joint_offsets

前向运动学(Forward Kinematics)

对于骨骼树,要想确定每个关节在每一帧的位置,应该从 Root 结点开始,向下遍历计算每个关节的旋转,从而得到每个关节的位置。

因此,我们可以通过对树进行解析的方式得到每一帧下每个关节的全局旋转和全局坐标。由于我的 BVH 文件所有 CHANNEL 的顺序都是 (X, Y, Z) ,因此并未处理其它情况,如果有特殊情况需要注意。

import numpy as np
from scipy.spatial.transform import Rotation as R

def forward_kinematics(joint_name, joint_parent, joint_offset, motion_data, frame_id):
    m = len(joint_name)
    joint_positions = np.zeros((m, 3), dtype=np.float64)
    joint_orientations = np.zeros((m, 4), dtype=np.float64)
    channels = motion_data[frame_id]
    rotations = np.zeros((m, 3), dtype=np.float64)
    cnt = 1
    for i in range(m):
        if '_end' not in joint_name[i]:
            for j in range(3):
                rotations[i][j] = channels[cnt * 3 + j]
            cnt += 1
    for i in range(m):
        parent = joint_parent[i]
        if parent == -1:
            for j in range(3):
                joint_positions[0][j] = channels[j]
            joint_orientations[0] = R.from_euler('XYZ', [rotations[0][0], \
            	rotations[0][1], rotations[0][2]], degrees=True).as_quat()
        else:
            if '_end' in joint_name[i]:
                joint_orientations[i] = np.array([0, 0, 0, 1])
                joint_positions[i] = joint_positions[parent] + \
                	R.from_quat(joint_orientations[parent]).as_matrix() @ joint_offset[i]
            else:
                rotation = R.from_euler('XYZ', [rotations[i][0], \
                	rotations[i][1], rotations[i][2]], degrees=True)
                joint_orientations[i] = (R.from_quat(joint_orientations[parent]) * rotation).as_quat()
                joint_positions[i] = joint_positions[parent] + \
                	R.from_quat(joint_orientations[parent]).as_matrix() @ joint_offset[i]

    return joint_positions, joint_orientations

计算中涉及一些四元数(quaternion)的相关知识,可以通过 四元数和旋转 稍作了解。

动画播放

这部分内容参考了 GAMES105 课程。首先安装依赖库,pip install panda3d

然后直接将仓库中的 viewer.pyGroundScene.eggcharacter_model.pywalk60.bvh 和文件放在同一文件夹下,调用运行即可。

Hibiki33/BVHPlayer

完整的代码如下:

from viewer import SimpleViewer
import numpy as np
from scipy.spatial.transform import Rotation as R

class HierarchyParser(object):

    def __init__(self, bvh_file_path):
        self.lines = self.get_hierarchy_lines(bvh_file_path)
        self.line_number = 0

        self.root_position_channels = []
        self.joint_rotation_channels = []

        self.joint_names = []
        self.joint_parents = []
        self.joint_offsets = []

    def get_hierarchy_lines(self, bvh_file_path):
        hierarchy_lines = []
        for line in open(bvh_file_path, 'r'):
            line = line.strip()
            if line.startswith('MOTION'):
                break
            else:
                hierarchy_lines.append(line)

        return hierarchy_lines
    
    def parse_offset(self, line):
        return [float(x) for x in line.split()[1:]]
    
    def parse_channels(self, line):
        return [x for x in line.split()[2:]]

    def parse_root(self, parent=-1):
        self.joint_parents.append(parent)

        self.joint_names.append(self.lines[self.line_number].split()[1])
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find root offset')
        self.line_number += 1

        if self.lines[self.line_number].startswith('CHANNELS'):
            channels = self.parse_channels(self.lines[self.line_number])
            if self.lines[self.line_number].split()[1] == '3':
                self.joint_rotation_channels.append((channels[0], channels[1], channels[2]))
            elif self.lines[self.line_number].split()[1] == '6':
                self.root_position_channels.append((channels[0], channels[1], channels[2]))
                self.joint_rotation_channels.append((channels[3], channels[4], channels[5]))
        else:
            print('cannot find root channels')
        self.line_number += 1

        while self.lines[self.line_number].startswith('JOINT'):
            self.parse_joint(0)
        self.line_number += 1

    def parse_joint(self, parent):
        self.joint_parents.append(parent)

        index = len(self.joint_names)
        self.joint_names.append(self.lines[self.line_number].split()[1])
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find joint offset')
        self.line_number += 1

        if self.lines[self.line_number].startswith('CHANNELS'):
            channels = self.parse_channels(self.lines[self.line_number])
            if self.lines[self.line_number].split()[1] == '3':
                self.joint_rotation_channels.append((channels[0], channels[1], channels[2]))
        else:
            print('cannot find joint channels')
        self.line_number += 1

        while self.lines[self.line_number].startswith('JOINT') or \
                self.lines[self.line_number].startswith('End'):
            if self.lines[self.line_number].startswith('JOINT'):
                self.parse_joint(index)
            elif self.lines[self.line_number].startswith('End'):
                self.parse_end(index)
        self.line_number += 1

    def parse_end(self, parent):
        self.joint_parents.append(parent)

        self.joint_names.append(self.joint_names[parent] + '_end')
        self.line_number += 2

        if self.lines[self.line_number].startswith('OFFSET'):
            self.joint_offsets.append(self.parse_offset(self.lines[self.line_number]))
        else:
            print('cannot find joint offset')
        self.line_number += 2

    def analyze(self):
        if not self.lines[self.line_number].startswith('HIERARCHY'):
            print('cannot find hierarchy')
        self.line_number += 1

        if self.lines[self.line_number].startswith('ROOT'):
            self.parse_root()
    
        return self.joint_names, self.joint_parents, self.joint_offsets


def forward_kinematics(joint_name, joint_parent, joint_offset, motion_data, frame_id):
    m = len(joint_name)
    joint_positions = np.zeros((m, 3), dtype=np.float64)
    joint_orientations = np.zeros((m, 4), dtype=np.float64)
    channels = motion_data[frame_id]
    rotations = np.zeros((m, 3), dtype=np.float64)
    cnt = 1
    for i in range(m):
        if '_end' not in joint_name[i]:
            for j in range(3):
                rotations[i][j] = channels[cnt * 3 + j]
            cnt += 1
    for i in range(m):
        parent = joint_parent[i]
        if parent == -1:
            for j in range(3):
                joint_positions[0][j] = channels[j]
            joint_orientations[0] = R.from_euler('XYZ', [rotations[0][0], rotations[0][1], rotations[0][2]], degrees=True).as_quat()
        else:
            if '_end' in joint_name[i]:
                joint_orientations[i] = np.array([0, 0, 0, 1])
                joint_positions[i] = joint_positions[parent] + R.from_quat(joint_orientations[parent]).as_matrix() @ joint_offset[i]
            else:
                rotation = R.from_euler('XYZ', [rotations[i][0], rotations[i][1], rotations[i][2]], degrees=True)
                joint_orientations[i] = (R.from_quat(joint_orientations[parent]) * rotation).as_quat()
                joint_positions[i] = joint_positions[parent] + R.from_quat(joint_orientations[parent]).as_matrix() @ joint_offset[i]

    return joint_positions, joint_orientations


def load_motion_data(bvh_file_path):
    with open(bvh_file_path, 'r') as f:
        lines = f.readlines()
        for i in range(len(lines)):
            if lines[i].startswith('Frame Time'):
                break
        motion_data = []
        for line in lines[i+1:]:
            data = [float(x) for x in line.split()]
            if len(data) == 0:
                break
            motion_data.append(np.array(data).reshape(1,-1))
        motion_data = np.concatenate(motion_data, axis=0)
    return motion_data


def animation(viewer, joint_names, joint_parents, joint_offsets, motion_data):
    frame_num = motion_data.shape[0]
    
    class UpdateHandle:
        def __init__(self):
            self.current_frame = 0
        def update_func(self, viewer_):
            joint_positions, joint_orientations = forward_kinematics(joint_names, \
                joint_parents, joint_offsets, motion_data, self.current_frame)
            viewer.show_pose(joint_names, joint_positions, joint_orientations)
            self.current_frame = (self.current_frame + 1) % frame_num

    handle = UpdateHandle()
    viewer.update_func = handle.update_func
    viewer.run()


def main():
    bvh_file_path = 'walk60.bvh'
    viewer = SimpleViewer()
    parser = HierarchyParser(bvh_file_path)
    joint_names, joint_parents, joint_offsets = parser.analyze()
    motion_data = load_motion_data(bvh_file_path)
    animation(viewer, joint_names, joint_parents, joint_offsets, motion_data)


if __name__ == "__main__":
    main()
  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Unity是一款流行的游戏引擎,支持多种文件格式的导入,其中包括bvh文件格式。bvh文件格式是一种用于记录人体骨骼动作的文件格式,通常用于动画制作。 在Unity中导入bvh文件非常简单。首先,在Unity中创建一个新项目,并导入需要的人体模型。其次,打开导入人物模型的编辑器,在编辑器中选择导入bvh文件的选项。接下来,选择需要导入的bvh文件,并按照提示完成导入过程。在导入过程中,Unity会自动将bvh文件中的骨骼动作信息应用到导入的人体模型上。 在导入完毕后,可以使用Unity的动画编辑器来查看和编辑导入的骨骼动作。除了默认的动画编辑器,还可以使用一些第三方插件来更好地控制和编辑动画。此外,如果觉得需要,还可以利用Unity"动作匹配器"和"动作融合器"的功能,来组合和混合多个动画。 总之,Unity支持bvh文件的导入,使得人物动画制作变得更加容易和高效。通过导入和编辑bvh文件,人物动画制作者可以创造出更加生动、精彩的人物动作,从而提高游戏的质量和用户体验。 ### 回答2: Unity是一款非常强大的游戏引擎,可以实现3D场景的构建和游戏物体的操作。而导入bvh文件也是其中的一个非常重要的功能,这可以非常方便地实现角色动作的导入和应用。 首先,在Unity的资源管理器中选择需要导入bvh文件的对象,在其属性面板中选择“导入”的选项。随后选择bvh文件,并进行导入操作。 接下来,在导入后的动画对象上,我们可以进行各种不同的操作,比如修改动画的播放速度、添加新的动画片段、或者对动画进行差值和编辑。 需要注意的是,在导入bvh文件时,Unity会尝试将其重新调整为匹配当前场景的大小和比例。因此,有时候可能需要手动对动画进行一些微调和编辑,以实现最佳效果。 总之,使用Unity导入bvh文件是一项非常重要且实用的功能,可以让我们更加轻松和高效地创建精美的动画效果。 \end{cn} ### 回答3: Unity是一款流行的游戏引擎,可以用于开发各种类型的游戏。导入BVH文件是Unity中常见的任务,这种文件格式通常用于描述人体运动数据。以下是关于如何在Unity中导入BVH文件的一些提示。 首先,Unity可以通过使用第三方插件来导入BVH文件。有许多免费或付费的插件可供选择,例如"BVH Importer"或"FinalIK"。在查找和选择插件之前,要确保已经下载并安装了最新的Unity版本。 安装插件后,需要准备BVH文件。可以使用3D建模软件如Maya、Blender或MotionBuilder来创建或修改此文件。通常需要确保文件符合常见的BVH标准格式,例如正确的帧速率、帧数量、层次结构和骨骼命名。 导入过程与导入其他文件类型相似: 打开Unity,创建一个新项目或打开现有项目,然后从文件选项中选择导入功能。选择正确的插件和BVH文件后,将需要进行一些设置,例如指定是否移动,旋转或缩放人物模型。 成功导入BVH文件后,Unity会生成一个包含BVH数据的动画片段。可以通过将这段动画片段附加到人物模型上,使模型运动引擎和动画数据配合工作。 总之,在Unity中导入BVH文件需要使用适当的插件,并通过创建和配置人物模型来准备文件。成功导入后,BVH数据可用于实现复杂的人体动作和其他交互式游戏元素。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值