Форум программистов, компьютерный форум, киберфорум
Наши страницы

Мешалка и Змей 2 (Blender & Python)

Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 2.

Мешалка и Змей 2 (Blender & Python)

Запись от Ух ты! размещена 12.11.2012 в 14:40

Мешалка и Змей 2 : Экспорт морф анимации (Blender & Python)
И так на сей раз речь пойдет о том как бы нам не воспользоваться всеми благами человеческой мысли, а как говаривал дедушка ..нин - сходить другим путем! И написать свой собственный простейший экспортер морф анимации. Для начала надо понять что такое морф, что есть не морф, что есть анимация, что есть статика, что есть небо, что есть звезды, какое место отведено человеку... во вселенной... тьфу.
Сразу скажу про человека, что бы не томить в долгом ожидании очередного микро-откровения - человеку тут ни%ига делать не надь, он нервно курит в сторонке пока скриптец препарирует его анимированную модельку xD
И так - во цу дихтар? т.е. морф. Анимация = Кадр1 Кадр2 Кадр3... и т.д. Кадр1 - это сетка объекта(Mesh) в некоторый момент времени Action(t) некоторого действия Armatura(Action). Armatura - это скелет персонажа. Предыдущее предложение должно было вызвать у вас ассоциативную - иерархическую - галлюцинацию, разве нет?( Ну тогда еще раз...
И так объект это - персонаж игры, умеет прыгать, стрелять, умирать... показывать средний палец руки и т.д. и т.п. как вы думаете что это? Как ни странно, но это действия экшены(Actions). Будь скажем наш персонаж газо-образным смог бы он проделать все те действия?) Разумеется нет. поскольку нужна оснастка, скелет, которым и будут управлять действия. Однако, иерархия принципиально ортогональная это у скелета есть действия, а не наоборот( Ну да не суть. Главное тут то что Арматура в составе себя имеет набор действий персонажа которые вы кропотливо на анимировали в Blendere. По крайней мере такая иерархия наблюдается в меню у блендера, но в реальности как обычно бывает все гораздо жестче. Нырнем еще глубже. Теперь рассмотрим экшены, они в себе содержат такую полезную инфу как - с какого и по какой кадр они длятся, как называются что не удивительно и.. и кучу всего другого. Но основная их нагрузка и предназначение это хранить ключи(записи) трансформации костей, эти ключи хранятся только в тех местах(по времени) где вы трогали косточки, и нажимали I чтобы записать ключевые трансформации анимации. Допустим у вас на 0-ом кадре действия(поднять руку) персонаж стоял с опушенной рукой. Вы выбираете кости(в роse mode) нажимаете I сохраняете как -> LocScaleRot, тут же создается столбик ключей и далее вы перемещаетесь на 10-й кадр поднимаете руку и вновь выделяете все кости и сохраняете I -> LocScaleRot так мы получим короткую анимашку поднимающейся руки с 0-ого по 10-й кадр. Пройдя все эти круги ада мы грубо говоря наблюдаем 11-ть разных сеток. Как известно анимацию/фильмы для человеческого глаза обычно транслируют со скорость 24-25 кадра в минуту, а у нас только одиннадцать или еще меньше, откуда вы спросите возьмутся остальные? А остальные мы дорисуем на основе имеющихся 11-ти уже программе! Как? Возьмем первый и второй кадр. возьмем из первого кадра первую вершину и из второго первую. Работа вообще видеться только с симметричными вершинами в соседних кадрах, а иначе %игня получится. Далее, имея две вершины на руках из соседних кадров нам нужно предположим построить "смешанную вершину" во время 0.5t т.е это как бы половина первой и половина второй, как мы можем их смешать? - Легко, с помощью линейной интерполяции векторов, в общем виде она выглядит так
C++
1
2
3
4
vec3 mix( const vec3 &a, const vec3 &b, const float factor ) 
{
    return vec3(a + (b - a) * factor);
}
если скажем нам в метод void morf_animation_t :: render(const float time) было в качесве параметра отправлено время time = 9.2f нам прежде чем что-либо рисовать нужно сообразить, а какие же кадры нужно выделить в качестве опорных - а и b и насколько нужно их смешать. Вы наверное уже догадались, что смешивать будем 9й и 10й кадр, а степенью их смешивания будет коэфф = 0.2f выделить их из общего времени можно так
C++
1
2
    unsigned int i_frame = (unsigned int)time;
    float fmix_factor = time - (float)(i_frame);
Далее проверим, а не выходим ли мы за пределы данной анимации, ее данные я сохраняю в заголовке файла.
Заголовок
C++
1
2
3
4
5
6
struct morf_file_head_t 
{
    unsigned frame_size;
    unsigned vertex_count;
    unsigned frame_count;
};
Проверка
C++
1
2
3
4
5
    if (i_frame > (ani_head->frame_count - 1)) i_frame = ani_head->frame_count - 1;
    if (i_frame < 0) i_frame = 0;
 
    if (fmix_factor < 0.0f) fmix_factor = 0.0f;
    if (fmix_factor > 1.0f) fmix_factor = 1.0f;
После, можно сразу взяться за опорные кадры, выделить их из общего массива данных, где все понамешано и черт ногу сломит, а мы нет, потому что арифметику указателей знаем!)
C++
1
2
    morf_vertex_t* frame_a = (morf_vertex_t*)((i_frame * ani_head->frame_size) + ((char*)(vertexes)));
    morf_vertex_t* frame_b = (morf_vertex_t*)(((i_frame + 1) * ani_head->frame_size) + ((char*)(vertexes)));
Ну и самое вкусное на последок - возьмем и перемешаем.
C++
1
2
3
4
5
6
7
8
    for(int i = 0; i < ani_head->vertex_count; i++) 
    {
                
        actual_frame[i].pos = mix(frame_a[i].pos, frame_b[i].pos, fmix_factor);
        actual_frame[i].normal = mix(frame_a[i].normal, frame_b[i].normal, fmix_factor);
        actual_frame[i].tex_coords = frame_a[i].tex_coords;
        
    };
Кстати, если заметили текстурные координаты смешивать смысла нет. Поэтому берем их из любого кадра.
Вообще, данный морфинг производиться как вы видите на CPU, видеокарте отсылается уже просчитанная сетка, но его без труда можно(и даже нужно) перенести на GPU.
В финале собственно тривиальная прорисовка.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
    glEnableClientState(GL_VERTEX_ARRAY); 
    glEnableClientState(GL_TEXTURE_COORD_ARRAY); 
    glEnableClientState(GL_NORMAL_ARRAY);
 
    glTexCoordPointer(2, GL_FLOAT, sizeof(morf_vertex_t), &actual_frame->tex_coords); 
    glNormalPointer(GL_FLOAT, sizeof(morf_vertex_t), &actual_frame->normal); 
    glVertexPointer(3, GL_FLOAT, sizeof(morf_vertex_t), &actual_frame->pos);
 
    glDrawArrays( GL_TRIANGLES, 0, ani_head->vertex_count);
 
    glDisableClientState(GL_NORMAL_ARRAY); 
    glDisableClientState(GL_TEXTURE_COORD_ARRAY); 
    glDisableClientState(GL_VERTEX_ARRAY);
Теперь собственно вернемся к экспорту данных из блендера.
И так у нас есть цельный анимированный персонаж. Данный вариант скрипта писался именно для цельного меша, без труда можно реализовать и для составного состоящего из разных объектов, нужно лишь провести пару косметических манипуляций, может я в дальнейшем их опишу, а пока имеем то что имеем.
И так.
Персонаж преобразован в треугольники.
Сделана текстурная развертка.
Произведена оснастка его скелетом.
Сделана простейшая анимация.
К сожалению своему, мне поздно пришла мысль о фейс-палме, после того как я сделал это беспорядочное мотание головой и руками. Ну да ладно, в след раз...
Да и перс не фонтан, в общем и целом - за не имением повара %!ут и кухарку.
Нажмите на изображение для увеличения
Название: body.jpg
Просмотров: 293
Размер:	199.7 Кб
ID:	1488
Сам скрипт, портянка.
Кликните здесь для просмотра всего текста
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# written by UxTi 
 
import time
import sys
import os
import bpy
import struct
 
 
time_start = time.time()
 
ObjectList = bpy.context.selected_objects
 
for SceneObject in ObjectList:
    if SceneObject.type in {'MESH'}:
        Mesh = SceneObject.to_mesh(bpy.context.scene, True, 'PREVIEW')
            
        print ("object name %s" % SceneObject.name)
        print ("polygons count %d" % len(Mesh.polygons))
        print ("vertex count %d" % len(Mesh.vertices))
        
        GotTexCoords = len(Mesh.uv_layers)
        print ("UVLayers count %d" % (GotTexCoords))
        
        for act in bpy.data.actions:
        #
            print ('action name: %s' % act.name)
            print ('action time from %f to %f' % (act.frame_range[0],act.frame_range[1]) )
            print("bone count in this action: %i" % len(act.groups))
        
        
        max_frame = int(act.frame_range[1] - act.frame_range[0]) + 1
 
        vertex_size = 32
        #razmer structuri
        
        frame_size = len(Mesh.polygons) * 3 * vertex_size 
        #razmer kadra animacii v bytes        
 
        print ("one frame size %d bytes..." % frame_size)
        print ("animation size %d bytes..." % (frame_size * max_frame))
        
        file = open("model.abm", 'wb')
        file.write( struct.pack('<I', int(frame_size) ))
        file.write( struct.pack('<I', int( len(Mesh.polygons) * 3)))
        file.write( struct.pack('<I', int( max_frame)))
        
        bpy.context.scene.frame_set(0)
        bpy.context.scene.update()
         
 
        #print ("Startig exporting animation %s..." % act.name)
        
        for f in range(int(act.frame_range[0]), int(act.frame_range[1])+1):
                print ("exporting frame %d..." % f)
                #curscene.frame_set(f)
                bpy.context.scene.frame_set(f)
                bpy.context.scene.update()
                
                Mesh = SceneObject.to_mesh(bpy.context.scene, True, 'PREVIEW')
        
                for i, polygon in enumerate(Mesh.polygons):
                    nCountOfVertexesInPolygon = len(polygon.vertices)
                    for j in range(nCountOfVertexesInPolygon):
                        
                        vertex = Mesh.vertices[polygon.vertices[j]]
                                       
                        #file.write( 'v %f %f %f\n' % (vertex.co[0], vertex.co[1], vertex.co[2]))
                        #file.write( 'n %f %f %f\n' % (vertex.normal[0], vertex.normal[1],vertex.normal[2]))
                        
                        file.write( struct.pack( '<fff', vertex.co[0], vertex.co[1], vertex.co[2]))
                        file.write( struct.pack( '<fff', vertex.normal[0], vertex.normal[1],vertex.normal[2]))
                        
                        if GotTexCoords:
                            TexCoords = Mesh.uv_layers.active.data[polygon.loop_indices[j]].uv
                            #file.write( 't %f %f\n' % (TexCoords[0], TexCoords[1]))
                            file.write( struct.pack( '<ff', TexCoords[0], TexCoords[1]))
                        else:
                            #file.write( 't %f %f\n' % (0.0, 0.0))      
                            file.write( struct.pack( '<ff', 0.0, 0.0))
            
        file.close()
 
        bpy.context.scene.frame_set(0)
        bpy.context.scene.update()  
# do something...
 
print("My Script Finished: %.4f sec" % (time.time() - time_start))

В вкратце(если получится) основные моменты.
Кликните здесь для просмотра всего текста
for act in bpy.data.actions:
#
print ('action name: %s' % act.name)
print ('action time from %f to %f' % (act.frame_range[0],act.frame_range[1]) )
print("bone count in this action: %i" % len(act.groups))


Вообще, логика скрипта строится на том что в файл будет экспортировано одно действие некоторой длины, но тем неменее мы тут пробегаемы по всем, благо у меня оно одно) тут главное не создавать больше одной анимации в самом бленд файле, далее скрип будет доработан. Действие - это хронометраж, группы костей, флаг активности, имя и другая неведомая чушь. Для экспорта единственного действия, а оно обычно активно, поэтому пока в скрипте мы его не выбираем из тучи других, нам нужен его хронометраж - начальная позиция (act.frame_range[0]) и конечная (act.frame_range[1]).

Python
1
max_frame = int(act.frame_range[1] - act.frame_range[0]) + 1
Вычисляем макс кадр. ))

Python
1
2
3
4
        vertex_size = 32
        #razmer structuri
        
        frame_size = len(Mesh.polygons) * 3 * vertex_size
Эта магия чисел тут не спроста, вообще ниже мы пишем структуры вида vec3 + vec3+vec2 она весит 32 байта.
frame_size - тут размер сетки в некоторый момент времени в байтах, величина константная в пределах анимации одного перса.

Python
1
2
3
4
        file = open("model.abm", 'wb')
        file.write( struct.pack('<I', int(frame_size) ))
        file.write( struct.pack('<I', int( len(Mesh.polygons) * 3)))
        file.write( struct.pack('<I', int( max_frame)))
тут открывается файл для записи
и первым делом пишется заголовок, с минимальной инфой об анимации, что бы на стороне программы на с++ мы имели некоторое представление.

Python
1
2
3
4
5
6
7
for f in range(int(act.frame_range[0]), int(act.frame_range[1])+1):
                print ("exporting frame %d..." % f)
                #curscene.frame_set(f)
                bpy.context.scene.frame_set(f)
                bpy.context.scene.update()
                
                Mesh = SceneObject.to_mesh(bpy.context.scene, True, 'PREVIEW'))
Во временном промежутке анимации, начинаем делать следующее. Устанавливать текущий кадр в f - время, и обновлять внутренности данных блендера, чтобы кости встали на f кадр и деформировали сетку, далее на этом кадре мы берем текущее положение сетки, которая от кадра к кадру меняется. Брать мы ее будем каждый кадр.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
                for i, polygon in enumerate(Mesh.polygons):
                    nCountOfVertexesInPolygon = len(polygon.vertices)
                    for j in range(nCountOfVertexesInPolygon):
                        
                        vertex = Mesh.vertices[polygon.vertices[j]]
                                       
                        #file.write( 'v %f %f %f\n' % (vertex.co[0], vertex.co[1], vertex.co[2]))
                        #file.write( 'n %f %f %f\n' % (vertex.normal[0], vertex.normal[1],vertex.normal[2]))
                        
                        file.write( struct.pack( '<fff', vertex.co[0], vertex.co[1], vertex.co[2]))
                        file.write( struct.pack( '<fff', vertex.normal[0], vertex.normal[1],vertex.normal[2]))
                        
                        if GotTexCoords:
                            TexCoords = Mesh.uv_layers.active.data[polygon.loop_indices[j]].uv
                            #file.write( 't %f %f\n' % (TexCoords[0], TexCoords[1]))
                            file.write( struct.pack( '<ff', TexCoords[0], TexCoords[1]))
                        else:
                            #file.write( 't %f %f\n' % (0.0, 0.0))      
                            file.write( struct.pack( '<ff', 0.0, 0.0))
Идет бинарная запись плотно упакованных структур. В итоге после заголовка у нас лежит несколько сеток, читай кадров. А файл в целом это одно действие.
Ниже приложены некоторые ресурсы, класс для загрузки, и скрип для экспорта. Далее я надеюсь оба будут улучшаться и дополняться.
Делимся опытом. Отзывы и комментарии по теме приветствуются.
Нажмите на изображение для увеличения
Название: blend.jpg
Просмотров: 242
Размер:	153.6 Кб
ID:	1492
Вложения
Тип файла: rar desing_sources.rar (101.3 Кб, 105 просмотров)
Тип файла: rar vertex blend.rar (956.9 Кб, 151 просмотров)
Размещено в Без категории
Просмотров 892 Комментарии 0
Всего комментариев 0

Комментарии

 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru