简体   繁体   English

在Android上通过OpenGL ES显示Blender 3D角色的方法

[英]The way to display blender 3D character by OpenGL ES on Android

I'd like to create my personal light-weight Android 3D engine to show 3D animation 我想创建个人的轻量级Android 3D引擎来显示3D动画
configured by Blender. 由Blender配置。

I have successfully loaded the Blender-created 3D models by OpenGL ES on Android. 我已经通过OpenGL ES在Android上成功加载了Blender创建的3D模型。 Now, I'd like to further step into animate the 3D models - character animated by Blender by OpenGL ES on Android. 现在,我想进一步制作3D模型的动画-由Blender通过Android上的OpenGL ES动画化的角色。

Below are my steps: 以下是我的步骤:
[1]Use Blender 2.69 to create a 3D character and animate it in distinct frame, totally [1]使用Blender 2.69创建3D角色并在不同的帧中对其进行动画处理
250 frames, where the 3D character is applied onto with a metarig armature object and 250帧,其中将3D角色与metarig电枢对象一起应用于
guarantee that each vertex contains at least one vertex group and dedicated weighting. 确保每个顶点至少包含一个顶点组和专用权重。
[2]Modify the ./2.69/scripts/addons/io_scene_obj/export.py and add the necessary [2]修改./2.69/scripts/addons/io_scene_obj/export.py并添加必要的
Blender python defined function to export the whole bone system of the armature. Blender python定义的功能可导出电枢的整个骨骼系统。
Actually I export the 3D character in .obj format and add my personal defined tag 实际上,我以.obj格式导出3D角色并添加了我的个人定义标签
for the bone system, I name the tag - bonelib. 对于骨骼系统,我将其命名为-bonelib。 Store it in file *.bones 将其存储在文件* .bones中
[3]Modify the ./2.69/scripts/addons/io_scene_obj/export.py so that it can export the [3]修改./2.69/scripts/addons/io_scene_obj/export.py,以便可以导出
animation in the unit of frame where each frame contains the transformation - transition, 以帧为单位的动画,其中每个帧都包含转换-过渡,
rotation, scale of each distinct bone in the armature. 旋转,电枢中每个不同骨骼的比例。 I add my personal defined tag 我添加我的个人定义标签
for the animation frames - framelib. 用于动画帧-framelib。 Store it in file *.fms. 将其存储在文件* .fms中。
[4]I write a java application to translate *.obj, *.bones, *.fms to hex binary format [4]我编写了一个Java应用程序,将* .obj,*。bones,*。fms转换为十六进制二进制格式
*.bin, *.frames files and can successfully load the 3D model and restore every detail * .bin,*。frames文件,并且可以成功加载3D模型并还原每个细节
pertaining to construct the 3D model by OpenGL ES in Android. 关于在Android中通过OpenGL ES构造3D模型。
[5]I add some function to calculate the correct transformation matrix of each bone and [5]我添加了一些函数来计算每个骨骼的正确变换矩阵,然后
apply it onto each vertex with weighting value where the vertex group it is belong to. 将其应用于具有权重值的每个顶点上它所属的顶点组。

After so much effort, I failed to display the correct animation of my 3D character, it 经过如此多的努力,我无法正确显示3D角色的动画,
is distorted and could not be told what exactly the 3D model it is. 扭曲,无法得知3D模型到底是什么。

I list my code in two parts: 我将代码分为两部分:
[Part-1]Blender python to export 3D model, bone system, animation data [Part-1] Blender python导出3D模型,骨骼系统,动画数据

To export armature, I define two below functions: 为了导出电枢,我定义了以下两个功能:

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()

To export from the graph editor, I define below functions: 要从图形编辑器导出,我定义以下函数:

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()

With all above I think that I have correctly export all my bones in the armature 综上所述,我认为我已经正确地将所有骨骼输出到了电枢中
from Blender coordinate system to OpenGL coordinate system. 从Blender坐标系到OpenGL坐标系。 Can anyone point where 谁能指出
exactly possibly the code segment I made mistake? 完全可能是我弄错了代码段?

[Part-2]Android APK to figure out the correct OpenGL coordinate [Part-2] Android APK找出正确的OpenGL坐标

This is the function I calculate the transformation data of the distinct bine: 这是我计算不同主体的转换数据的函数:

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;
    }

Below I list the caller code snippet to build the transformation data: 在下面,我列出了用于构建转换数据的调用者代码段:
FrameLibInfoWavefrontObjectToBinary framelibInfo = new FrameLibInfoWavefrontObjectToBinary(m_WavefrontObject); 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;

[Question]I could not display the 3D animation I applied by Blender. [问题]我无法显示Blender应用的3D动画。 I don't know where 我不知道在哪
goes wrong. 出错。 For the coordinate system transformation issue, I think that I have fully 对于坐标系转换问题,我认为我已经充分
implemented in the python function. 在python函数中实现。 To calculate the correct transformation data for 为计算正确的转换数据
each bone in distinct frame, I have also restored in the same order the matrix_local 每个骨骼在不同的框架中,我也以相同的顺序还原了matrix_local
of a bone object and the matrix_world of the armature object are exported. 骨骼对象的“骨架”和骨架对象的matrix_world被导出。
The algorithm is inspired from below http://blenderartists.org/forum/showthread.php?209221-calculate-bone-location-rotation-from-fcurve-animation-data 该算法的灵感来自以下http://blenderartists.org/forum/showthread.php?209221-calculate-bone-location-rotation-from-fcurve-animation-data数据

I have spend almost one month to evaluate this python script with the rigging applied in 我花了将近一个月的时间来评估该python脚本的应用
my 3D character and found that the calculated final coordinate of bone fully matches 我的3D角色,发现计算出的骨骼最终坐标完全匹配
with the Blender native build-in bone coordinate. 使用Blender原生内置骨骼坐标。

But, why can't I display the animation I applied by Blender in my 3D character in Android 但是,为什么我不能在Android 3D角色中显示Blender应用的动画
OpenGL ES??? OpenGL ES ???

Why create your own game engine when there are several really good, free and open source implementations of this on the web that work well on Android. 当网络上有许多非常好的,免费和开源的实现在Android上运行良好时,为什么要创建自己的游戏引擎。 I highly recommend you take a good look at the jMonkeyEngine SDK or the LWJGL for Java or the PowerVR SDK or Assimp for C++. 我强烈建议您仔细阅读jMonkeyEngine SDK或Java的LWJGL或PowerVR SDK或C ++的Assimp。 I use the PowerVR plugin for Blender to export to their POD and Collada formats, which works really well. 我使用用于Blender的PowerVR插件导出到其POD和Collada格式,这确实很好用。

Some of these open source solutions would probably give you an idea of what is missing in your code. 这些开放源代码解决方案中的一些可能会使您了解代码中缺少的内容。 There is a complete list of game engines for Android here . 有游戏引擎为Android的完整列表在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM