Способ отображения 3D-персонажа блендера с помощью OpenGL ES на Android

Я хотел бы создать свой собственный легкий 3D-движок Android, чтобы показывать 3D-анимацию, настроенную с помощью Blender.

Я успешно загрузил 3D-модели, созданные Blender'ом с помощью OpenGL ES, на Android. Теперь я хотел бы перейти к анимации 3D-моделей - персонажа, анимированного Blender с помощью OpenGL ES на Android.

Ниже приведены мои шаги:
[1] Используйте Blender 2.69, чтобы создать 3D-персонажа и анимировать его в отдельном кадре, всего
250 кадров, где 3D-персонаж наложен на объект арматуры метариг и
гарантия что каждая вершина содержит по крайней мере одну группу вершин и специальный вес.
[2] Измените ./2.69/scripts/addons/io_scene_obj/export.py и добавьте необходимую функцию, определенную Python Blender для экспорта всей кости система арматуры.
На самом деле я экспортирую трехмерный персонаж в формат .obj и добавляю свой личный определенный тег
для костной системы, я называю этот тег - bonelib. Сохраните его в файле * .bones
[3] Измените ./2.69/scripts/addons/io_scene_obj/export.py, чтобы он мог экспортировать
анимацию в единицах кадра, где каждый кадр содержит преобразование - переход, вращение, масштаб каждой отдельной кости в каркасе. Я добавляю свой личный тег
для кадров анимации - framelib. Сохраните его в файле * .fms.
[4] Я пишу java-приложение для перевода * .obj, * .bones, * .fms в шестнадцатеричный двоичный формат
* .bin, * .frames файлы и могу успешно загрузить 3D-модель и восстановить каждую деталь, относящуюся к построению 3D-модели с помощью OpenGL ES в Android.
[5] Я добавляю некоторую функцию для вычисления правильной матрицы преобразования каждой кости и
применяю ее к каждой вершина со значением веса, к которой принадлежит группа вершин.

После стольких усилий мне не удалось отобразить правильную анимацию моего 3D-персонажа, он
искажен, и я не мог сказать, что это за 3D-модель.

Я перечисляю свой код в двух частях:
[Часть-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. Может ли кто-нибудь указать, где именно возможно сегмент кода, в котором я сделал ошибку?

[Часть 2] Android APK для определения правильной координаты OpenGL

Это функция, с помощью которой я вычисляю данные преобразования отдельного бина:

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

Ниже я перечисляю фрагмент кода вызывающего абонента для построения данных преобразования:
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;

[Вопрос] Мне не удалось отобразить 3D-анимацию, которую я применил с помощью Blender. Я не знаю, где что-то пошло не так. Что касается проблемы преобразования системы координат, я думаю, что я полностью
реализовал функцию python. Чтобы вычислить правильные данные преобразования для каждой кости в отдельном кадре, я также восстановил в том же порядке матрицу_local
объекта кости и матрицу_world объекта арматуры.
Алгоритм вдохновлен снизу http://blenderartists.org/forum/showthread.php?209221-calculate-bone-location-rotation-from-fcurve-animation-data

Я потратил почти месяц на оценку этого скрипта python с использованием оснастки, примененной в моем 3D-персонаже
, и обнаружил, что вычисленная конечная координата кости полностью совпадает с
с встроенной координатой кости Blender.

Но почему я не могу отобразить анимацию, которую я применил с помощью Blender, в моем 3D-персонаже в Android
OpenGL ES ???


person user3749411    schedule 17.06.2014    source источник


Ответы (1)


Зачем создавать собственный игровой движок, если в сети есть несколько действительно хороших, бесплатных реализаций с открытым исходным кодом, которые хорошо работают на Android. Я настоятельно рекомендую вам внимательно изучить jMonkeyEngine SDK, LWJGL для Java, PowerVR SDK или Assimp для C ++. Я использую плагин PowerVR для Blender для экспорта в их форматы POD и Collada, что действительно хорошо работает.

Некоторые из этих решений с открытым исходным кодом, вероятно, дадут вам представление о том, чего не хватает в вашем коде. Полный список игровых движков для Android здесь.

person ClayMontgomery    schedule 18.06.2014