Android上OpenGL ES显示blender 3D字符的方法
Posted
技术标签:
【中文标题】Android上OpenGL ES显示blender 3D字符的方法【英文标题】:The way to display blender 3D character by OpenGL ES on Android 【发布时间】:2014-08-07 19:52:43 【问题描述】:我想创建我个人的轻量级 android 3D 引擎来显示 3D 动画 由 Blender 配置。
我已在 Android 上成功加载了由 OpenGL ES 创建的 Blender 3D 模型。现在, 我想进一步为 3D 模型制作动画 - Blender 为角色制作动画 Android 上的 OpenGL ES。
以下是我的步骤: [1]使用 Blender 2.69 创建一个 3D 角色并在不同的帧中对其进行动画处理,完全 250 帧,其中 3D 角色使用 metarig 骨架对象和 保证每个顶点至少包含一个顶点组和专用权重。 [2]修改./2.69/scripts/addons/io_scene_obj/export.py并添加必要的 Blender python 定义的函数来导出骨架的整个骨骼系统。 实际上我以 .obj 格式导出 3D 字符并添加我个人定义的标签 对于骨骼系统,我将标签命名为 -bonelib。将其存储在文件 *.bones [3]修改./2.69/scripts/addons/io_scene_obj/export.py,使其可以导出 以帧为单位的动画,其中每一帧都包含变换 - 过渡, 旋转,骨架中每个不同骨骼的比例。我添加了我个人定义的标签 对于动画帧 - framelib。将其存储在文件 *.fms 中。 [4]我写了一个java应用程序把*.obj,*.bones,*.fms翻译成十六进制二进制格式 *.bin, *.frames 文件,可以成功加载3D模型,还原每一个细节 关于在Android中通过OpenGL ES构建3D模型。 [5]我添加了一些函数来计算每个骨骼的正确变换矩阵和 将其应用于具有权重值的每个顶点,它所属的顶点组。
经过这么多努力,我未能正确显示我的 3D 角色的动画,它 是扭曲的,无法告诉它究竟是什么 3D 模型。
我将我的代码分为两部分: [Part-1]Blender python导出3D模型、骨骼系统、动画数据
为了导出骨架,我定义了以下两个函数:
def matrix_to_string(m, dim=4):
s = ""
for i in range(0, dim):
for j in range(0, dim):
s += "%+.2f "%(m[i][j])
return(s)
def export_armature(path_dir, objects):
#Build up the dictionary for the armature parented by all mesh objects
ArmatureDict = vertex coordinate
for ob in objects:
b_export_armature = True
if ob.parent and (ob.parent_type == 'OBJECT' or ob.parent_type == 'ARMATURE' or ob.parent_type == 'BONE'):
if ob.parent_type == 'OBJECT':
if ob.parent.type != 'ARMATURE':
b_export_armature = False
if b_export_armature == True:
p = ArmatureDict.get(ob.parent.name)
if p is None:
ArmatureDict[ob.parent.name] = ob.parent
#print("Total %d armatures to be exported\n" %(len(ArmatureDict)))
for key in ArmatureDict.keys():
a_obj = ArmatureDict.get(key)
filename = a_obj.name + ".bones"
bonesfilepath = path_dir + '/' + filename
file = open(bonesfilepath, "w", encoding="utf8", newline="\n")
fw = file.write
#Write Header
fw('#Blender v%s BONS File: %s\n' %(bpy.app.version_string, filename))
fw('#Author: mjtsai1974\n')
fw('\n')
#Armature architecture portion
list_bones = a_obj.data.bones[:]
fw('armature %s\n' %(a_obj.name))
for bone in list_bones:
fw('bonechain %s' %(bone.name))
child_bones = bone.children
for child in child_bones:
fw(' %s' %(child.name))
fw('\n')
fw('\n')
#Armature and its bone chain restpose portion
mat_rot = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X')
m = mat_rot * a_obj.matrix_world
fw('restpose armature %s %s\n' %(a_obj.name, matrix_to_string(m, 4)))
for bone in list_bones:
m = mat_rot * bone.matrix_local
fw('restpose bone %s %s\n' %(bone.name, matrix_to_string(m, 4)))
file.close()
为了从图形编辑器中导出,我定义了以下函数:
def get_bone_action_location(action, bonename, frame=1):
loc = Vector()
if action == None:
return(loc)
data_path = 'pose.bones["%s"].location'%(bonename)
for fc in action.fcurves:
if fc.data_path == data_path:
loc[fc.array_index] = fc.evaluate(frame)
return(loc)
def get_bone_action_rotation(action, bonename, frame=1):
rot = Quaternion( (1, 0, 0, 0) ) #the default quat is not 0
if action == None:
return(rot)
data_path = 'pose.bones["%s"].rotation_quaternion'%(bonename)
for fc in action.fcurves:
if fc.data_path == data_path: # and frame > 0 and frame-1 <= len(fc.keyframe_points):
rot[fc.array_index] = fc.evaluate(frame)
return(rot)
def export_animation_by_armature(filepath, frames, objects):
#Build up the dictionary for the armature parented by all mesh objects
ArmatureDict =
mat_rot = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X')
for ob in objects:
b_export_armature = True
if ob.parent and (ob.parent_type == 'OBJECT' or ob.parent_type == 'ARMATURE' or ob.parent_type == 'BONE'):
if ob.parent_type == 'OBJECT':
if ob.parent.type != 'ARMATURE':
b_export_armature = False
if b_export_armature == True:
p = ArmatureDict.get(ob.parent.name)
if p is None:
ArmatureDict[ob.parent.name] = ob.parent
#print("Total %d armatures to be exported\n" %(len(ArmatureDict)))
path_dir = os.path.dirname(filepath)
for key in ArmatureDict.keys():
a_obj = ArmatureDict.get(key)
filename = a_obj.name + ".fms"
bonesfilepath = path_dir + '/' + filename
file = open(bonesfilepath, "w", encoding="utf8", newline="\n")
fw = file.write
#Write Header
fw('#Blender v%s FRAMES File: %s\n' %(bpy.app.version_string, filename))
fw('#Author: mjtsai1974\n')
fw('\n')
#Use armature pose bone chain for
list_posebones = a_obj.pose.bones[:]
fw('animator %s\n' %(a_obj.name))
for frame in frames:
bpy.context.scene.frame_set(frame)
action =a_obj.animation_data.action
#action = bpy.data.objects[a_obj.name].animation_data.action
if action == None:
print("Armature %s has no action" %(a_obj.name))
fw('frame %d\n' %(frame))
for bone in list_posebones:
m = get_bone_action_rotation(action, bone.name, frame) #read the fcurve-animation rotation
l = get_bone_action_location(action, bone.name, frame) #read the fcurve-animation location
#m = m.to_matrix().to_4x4()
#m = mat_rot * m
q = Quaternion((m.w, m.x, -m.z, m.y))
tl = mathutils.Matrix.Translation(l)
tr = mat_rot * tl
loc = tr.to_translation()
fw('bone %s t %+.2f %+.2f %+.2f\n' %(bone.name, loc[0], loc[1], loc[2]))
fw('bone %s r %+.2f %+.2f %+.2f %+.2f\n' %(bone.name, q.w, q.x, q.y, q.z))
fw('bone %s s %+.2f %+.2f %+.2f\n' %(bone.name, 1.0, 1.0, 1.0))
fw('\n')
file.close()
#[mjtsai@20140517]append the .frames information at the end of .obj file
file = open(filepath, "a+", encoding="utf8", newline="\n")
fw = file.write
for key in ArmatureDict.keys():
a_obj = ArmatureDict.get(key)
filename = a_obj.name + ".fms"
fw('\nframelib %s\n' %(filename))
file.close()
综上所述,我认为我已经正确导出了骨架中的所有骨骼 从 Blender 坐标系到 OpenGL 坐标系。谁能指点 完全可能是我犯错的代码段?
[Part-2]Android APK 找出正确的 OpenGL 坐标
这是我计算distinct bin的变换数据的函数:
public void buildTransformationDataByBoneNameAtFrame(String sz_Name, Armature ar_Obj, FrameLib fl_Obj, int idx_Frame)
float [] f_ary_Matrix_Inverted = new float[16];
float [] f_ary_Matrix_Total = new float[16];
float [] f_ary_Matrix_Self = new float[16];
float [] f_ary_Data_Matrix_Parent = null;
float [] f_ary_Data_Self = null;
float [] f_ary_Data_Parent = null;
AnimatorUnit au_Obj = fl_Obj.getAnimatorUnit();
ArrayList<String> ary_list_Strings = ar_Obj.buildFromRootToBoneByName(sz_Name);
FrameUnit fu_Obj = au_Obj.getFrameByIndex(idx_Frame);
if (ary_list_Strings == null)
LoggerConfig.Log(String.format("Failed in building array list for Bone[%s] ", sz_Name));
//new RuntimeException(String.format("Failed in building array list for Bone[%s] ", sz_Name));
return;
if (fu_Obj == null)
LoggerConfig.Log(String.format("FrameUnit - %d doesn't exist ", idx_Frame));
return;
String sz_LastBoneName = ary_list_Strings.get(ary_list_Strings.size() - 1);
//Suppose the very last one in the array list should be the same bone name to sz_Name
if (!sz_LastBoneName.equals(sz_Name))
LoggerConfig.Log(String.format("LAST_BONE_NAME[%s] != Bone[%s]", sz_LastBoneName, sz_Name));
return;
for (int Index = 0; Index < ary_list_Strings.size(); Index++)
String sz_ParentBoneName = "";
String sz_BoneName = ary_list_Strings.get(Index);
AnimationUnit amu_Obj = fu_Obj.getAnimationUnitByName(sz_BoneName);
AnimationUnit amu_ParentObj = null;
Restpose rp_Obj = ar_Obj.getRestposeByName(sz_BoneName);
Restpose rp_ParentObj = null;
if (Index != 0)
//This means that it is child bone
//Get parent bone
sz_ParentBoneName = ary_list_Strings.get(Index - 1);
rp_ParentObj = ar_Obj.getRestposeByName(sz_ParentBoneName);
amu_ParentObj = fu_Obj.getAnimationUnitByName(sz_ParentBoneName);
f_ary_Data_Matrix_Parent = amu_ParentObj.getTransformationData();
f_ary_Data_Parent = rp_ParentObj.getData(); //parent bone's local matrix
Matrix.invertM(f_ary_Matrix_Inverted, 0, f_ary_Data_Parent, 0); //parent bone's inverse local matrix
//Get child bone itself
f_ary_Data_Self = rp_Obj.getData(); //child bone's local matrix
//Multiply child bone's local matrix by parent bone's inverse local matrix
Matrix.multiplyMM(f_ary_Matrix_Self, 0, f_ary_Matrix_Inverted, 0, f_ary_Data_Self, 0);
//Multiply (child bone's local matrix by parent bone's inverse local matrix) by parent bone's transformation matrix
Matrix.multiplyMM(f_ary_Matrix_Total, 0, f_ary_Data_Matrix_Parent, 0, f_ary_Matrix_Self, 0);
amu_Obj.inflateTransformationData();
amu_Obj.finalizeTransformationData(f_ary_Matrix_Total);
else
//This means that it is root bone
f_ary_Data_Self = rp_Obj.getData();
amu_Obj.inflateTransformationData();
amu_Obj.finalizeTransformationData(f_ary_Data_Self);
//Before we return float array, free the arraylist just returned from ar_Obj.buildFromRootToBoneByName(sz_Name);
ary_list_Strings.clear();
//For garbage collection
ary_list_Strings = null;
f_ary_Matrix_Inverted = null;
f_ary_Matrix_Total = null;
f_ary_Matrix_Self = null;
下面我列出调用者代码 sn-p 来构建转换数据: FrameLibInfoWavefrontObjectToBinary framelibInfo = new FrameLibInfoWavefrontObjectToBinary(m_WavefrontObject);
i_ary_Statistics[0] = i_ary_Statistics[1] = 0;
framelibInfo.read(dis, i_ary_Statistics);
FrameLib fl_Obj = m_WavefrontObject.getFrameLib();
AnimatorUnit au_Obj = null;
BoneLib bl_Obj = null;
Armature ar_Obj = null;
ArrayList<BoneChain> bc_Objs = null;
BoneChain bc_Obj = null;
Bone b_Obj = null;
int count_Frames = 0;
String sz_BoneName = "";
if (fl_Obj != null)
au_Obj = fl_Obj.getAnimatorUnit();
count_Frames = au_Obj.getFrameCount();
if (count_Frames > 0)
bl_Obj = m_WavefrontObject.getBoneLibs().get(0); //By default, we have only one bonelib
ar_Obj = bl_Obj.getArmature();
bc_Objs = ar_Obj.getBoneChains();
for (int i_Frame = 0; i_Frame < count_Frames; i_Frame++)
for (int i_BC = 0; i_BC < bc_Objs.size(); i_BC++)
bc_Obj = bc_Objs.get(i_BC);
b_Obj = bc_Obj.getParentBone();
framelibInfo.buildTransformationDataByBoneNameAtFrame(b_Obj.getName(), ar_Obj, fl_Obj, i_Frame);
framelibInfo.dispose();
framelibInfo = null;
[问题]我无法显示 Blender 应用的 3D 动画。不知道在哪里 出错。对于坐标系变换的问题,我觉得我已经完全 在python函数中实现。为计算正确的转换数据 每个骨骼在不同的框架中,我也以相同的顺序恢复了 matrix_local 骨骼对象和骨架对象的矩阵世界被导出。 该算法的灵感来自下面http://blenderartists.org/forum/showthread.php?209221-calculate-bone-location-rotation-from-fcurve-animation-data
我已经花了将近一个月的时间来评估这个 Python 脚本并在其中应用了索具 我的 3D 角色,发现计算出的骨骼最终坐标完全匹配 使用 Blender 原生内置骨骼坐标。
但是,为什么我无法在 Android 中的 3D 角色中显示 Blender 应用的动画 OpenGL ES???
【问题讨论】:
【参考方案1】:当网络上有几个非常好的、免费和开源的实现在 Android 上运行良好时,为什么要创建自己的游戏引擎。我强烈建议您好好看看 jMonkeyEngine SDK 或用于 Java 的 LWJGL 或用于 C++ 的 PowerVR SDK 或 Assimp。我使用 Blender 的 PowerVR 插件将其导出为 POD 和 Collada 格式,效果非常好。
其中一些开源解决方案可能会让您了解代码中缺少的内容。有完整的安卓游戏引擎列表here。
【讨论】:
以上是关于Android上OpenGL ES显示blender 3D字符的方法的主要内容,如果未能解决你的问题,请参考以下文章
使用OpenGL ES 2.0在Android上创建Audio wave