Скелетная анимация в играх. Формат Half-Life SMD.
главная страница статьи файлы о сайте ссылки
Скелетная анимация в играх. Формат Half-Life SMD.

Георгий Мошкин
tmtlib@narod.ru

Обратите внимание - самая новая версия загрузчика моделей Half-Life smd: текущая версия загрузчика SMD.

Здесь я хотел бы рассказать немного о своём опыте по применению скелетной анимации в играх. Начну с того, что я никогда не занимался созданием 3d моделей людей. Но когда я впервые увидел программу MilkShape3D, то мне очень понравилось, что я достаточно легко могу создавать новые движения трёхмерных героев. Помимо этого я обратил внимание на маленький размер получающихся файлов: модель была в отдельном большом файле, а разные движения (бег, ходьба и т.п) - в нескольких файлах. Я говорю о файлах с расширением smd, с которыми может работать MilkShape3D.


Скриншот из программы MilkShape 3D.

"Скелетная анимация" казалась мне чем-то сложным. А так как я обажаю Pascal, то мне было ещё сложнее. Ведь исходники Half Life Model viewer были написаны на C++, и на первый взгляд непонятно было как всё это хозяйство приспособить к TMT Pascal или Delphi. Но несмотря на эти трудности в 2002 году на свет вышли две моих программы: Half-Life smd viewer for TMT Pascal и Half-Life smd viewer for Delphi. В дальнейшем в свет вышла ещё "продвинутая" версия под TMT Pascal (c поддержкой animation blend и attach объектов). По мере транслирования функций из C++ в Pascal я осознал, что всё намного проще. Просто вначале я пытался написать свою программу для моделирования скелетной анимации, в то время как нужно было просто сделать проигрыватель и пользоваться моделями в формате Half-Life smd или MilkShape (там всё очень похоже).

На сегодняшний день ситуация обстоит несколько иначе. В современных играх уже недостаточно просто проигрывать анимацию, но нужно ешё и реализовывать взаимодействие скелета с окружающим миром (например, Rag Doll). К тому же, некоторые анимации движения можно создавать "на лету". Но и просто скелетная анимация без всяких наворотов вроде совместного влияния костей на одну и ту же часть меша выглядит весьма неплохо.


Одна из проекций в MilkShape 3D.

Ну а теперь, после этого длительного вступления, скажу очень кратко о проигрывании скелетной анимации. Обратите внимание, что принцип действия понять очень просто. И лучше всего это сделать на примере файлов Half Life SMD. Скелет там описывается так:

nodes
0 "Bip01" -1
1 "Bip01 Pelvis" 0
2 "Bip01 L Thigh" 1
3 "Bip01 L Calf" 2
... и т.д., а потом:
41 "Bip01 R Finger41" 40
42 "Bip01 R Finger42" 41
43 "Bip01 Head" 10
44 "Bone05" 43
end

Вначале написано "nodes". Можно переводить это дословно, но лучше считать, что "nodes" - это суставы. Хотя, правильнее называть их узлами. Посмотрим на нулевую строчку (нулевой сустав). Читать нужно так:

  • сустав 0 имеет название "Bip01" и соединён костью с суставом -1.
  • сустав 1 имеет название "Bip01 Pelvis" и соединён костью с суставом 0.
  • сустав 2 имеет название "Bip01 L Thing" и соединён костью с суставом 1.
  • сустав 3 имеет название "Bip01 L Calf" и соединён костью с суставом 2.
  • и т.д.

Но этих данных для создания скелета недостаточно. Мы не знаем длины костей и их относительного положения. Для этого в файлах SMD есть следующий блок данных:

time 14
0 -0.000128 0.001317 39.204914 0.000000 0.000000 -1.570885
1 -0.245774 0.000000 0.000000 -1.570795 -1.570451 0.000000
2 -0.000006 0.000008 4.427393 3.141292 -0.011240 3.068913
3 18.465940 0.000000 0.000000 0.000000 0.000000 -0.242123
... пропущено несколько десятков строк
41 1.012810 0.000001 0.000000 0.000000 0.000000 0.052360
42 1.012810 -0.000001 0.000000 0.000000 0.000000 0.052360
43 2.110540 0.000000 0.000000 0.000000 0.000000 -0.083184
44 0.891927 2.373051 -0.014527 -3.141592 -0.000001 1.413797

time 15 - это номер кадра.

Читать нужно так:

  • сустав 0 перемещён на (-0.000128, 0.001317, 39.204914) и повёрнут на (0.000000, 0.000000, -1.570885)
  • и т.д.

В файлах с анимацией следует много таких блоков с промежуточными положениями для суставов. (time 0, потом строки 0-44, time 1 и ещё строки 0-44 и т.д). Вот так описывается весь скелет, включая косточки пальцев. И только один раз описывается структура nodes.


Видно, что на ктсьях рук суставов предостаточно.

Теперь о том, как это всё работает: пусть мы хотим просто вывести анимацию этого скелета: суставы - точками (GL_POINT), а кости - линиями (GL_LINE). У каждого сустава есть своя система координат. И мы знаем какой сустав к какому прикреплён. А только что было написано "сустав 0 перемещён на ...". Относительно чего перемещён? Относительно того сустава, к которому он прикреплён костью. Начнём выводить нулевой сустав. Он прикреплён костью к "минус первому" суставу. А такого сустава нет!! Всё дело в том, что "-1" суставом считается основная система координат. Все манипуляции с системой координат нулевого сустава делаются относительно точки (0,0,0). Длина костей как раз и задаётся тем, насколько рассматриваемый сустав перемещается относительно того, к которому он прикреплён. Рассмотрим более детально структуру скелета:

nodes
0 "Bip01" -1
1 "Bip01 Pelvis" 0
2 "Bip01 L Thigh" 1
3 "Bip01 L Calf" 2
4 "Bip01 L Foot" 3
5 "Bip01 R Thigh" 1
6 "Bip01 R Calf" 5
7 "Bip01 R Foot" 6
8 "Bip01 Spine" 1
9 "Bip01 Spine1" 8
10 "Bip01 Neck" 9
11 "Bip01 L Clavicle" 10
12 "Bip01 L UpperArm" 11
13 "Bip01 L Forearm" 12
14 "Bip01 L Hand" 13
15 "Bip01 L Finger0" 14
16 "Bip01 L Finger01" 15
17 "Bip01 L Finger02" 16
18 "Bip01 L Finger1" 14
19 "Bip01 L Finger11" 18
20 "Bip01 L Finger12" 19
21 "Bip01 L Finger2" 14
22 "Bip01 L Finger21" 21
23 "Bip01 L Finger22" 22
24 "Bip01 L Finger4" 14
25 "Bip01 L Finger41" 24
26 "Bip01 L Finger42" 25
27 "Bip01 R Clavicle" 10
28 "Bip01 R UpperArm" 27
29 "Bip01 R Forearm" 28
30 "Bip01 R Hand" 29
31 "Bip01 R Finger0" 30
32 "Bip01 R Finger01" 31
33 "Bip01 R Finger02" 32
34 "Bip01 R Finger1" 30
35 "Bip01 R Finger11" 34
36 "Bip01 R Finger12" 35
37 "Bip01 R Finger2" 30
38 "Bip01 R Finger21" 37
39 "Bip01 R Finger22" 38
40 "Bip01 R Finger4" 30
41 "Bip01 R Finger41" 40
42 "Bip01 R Finger42" 41
43 "Bip01 Head" 10
44 "Bone05" 43
end

В конечном итоге, все суставы скелета поворачиваются в пространстве относительно начала координат (0,0,0). Но чтобы получить положение кончика пальца, прийдётся учесть повороты и перемещения нескольких костей, через которые этот палец соединён с суставом [0 "Bip01" -1]. Посмотрим, как получить начало систему координат для [40 "Bip01 R Finger4" 30]: для этого нужно учесть следующую цепочку поворотов и перемещений: 40 - 30 - 29 -28 - 27 - 10 - 9 - 8 - 1 - 0. А нулевой сустав соединён с -1.

Пусть мы хотим повернуть кисть руки:

После поворота она будет выглядеть так:

Хотя мы изменили углы поворота только у сустава, находящегося в основании руки, это затронуло и все пальцы.

Зелёным цветом выделена та часть скелета, на которую будет влиять поворот выделенного красным цветом сустава.

А теперь повернём сустав, который находится в плече:

После поворота этого сустава скелет будет выглядеть так:

Если посмотреть на скелет со стороны, то получится следующее:

Выводить скелет - это хорошо, но как же быть с mesh? Всё очень просто. В основном файле smd треугольники 3d модели описываются в следующем виде:

zombie_legs.bmp
43 2.088721 1.696477 69.392159 0.661725 0.749728 -0.005300 0.488281 0.917647
43 1.588720 1.784227 68.839081 0.473493 0.837586 -0.272496 0.492188 0.847059
43 0.068722 2.368554 69.559540 0.045202 0.989231 0.139204 0.574219 0.917647

Первое число - это номер того сустава (системы координат), в которой следует выводить этот треугольник. Следующие за номером 43 восемь чисел - это координаты вершины (x,y,z), нормаль (nx,ny,nz) и текстурные координаты (u,v). Таким образом, все три точки из треугольника с текстурой zombie_legs.bmp должны трансформироваться в соответствии с цепочкой трасформаций для узла 43 (сустава 43).

Плавность движений достигается созданием промежуточных кадров (эти кадры создаются вручную). Выше был показан блок данных для одного из кадров скелетной анимации:

time 14
0 -0.000128 0.001317 39.204914 0.000000 0.000000 -1.570885
1 -0.245774 0.000000 0.000000 -1.570795 -1.570451 0.000000
2 -0.000006 0.000008 4.427393 3.141292 -0.011240 3.068913
3 18.465940 0.000000 0.000000 0.000000 0.000000 -0.242123
... пропущено несколько десятков строк
41 1.012810 0.000001 0.000000 0.000000 0.000000 0.052360
42 1.012810 -0.000001 0.000000 0.000000 0.000000 0.052360
43 2.110540 0.000000 0.000000 0.000000 0.000000 -0.083184
44 0.891927 2.373051 -0.014527 -3.141592 -0.000001 1.413797

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

Программы для проигрывания анимации я писал достаточно давно, но всё-таки ещё помню некоторые ньюансы: во-первых, в главном smd файле находится нормальная модель и один кадр анимации ("time 0"). После загрузки я "сверачивал" 3d модель, произведя "инверсные" translation и rotation. Это было как бы подготовкой к последующему её "разворачиванию" при реальной анимации. Потом, при написании проигрывателя анимации в формате milkshape, я обнаружил небольшую разницу в хранении translation или rotation (уже точно не помню).

В конце приведу юниты для tmt pascal:
hlmvpascal.zip (19kb) - загрузка half life smd через конвертор smdconv.
milkshape.zip (8kb) - загрузка milkshape ms3d
Под delphi: --- - файлы smdview1 (этот мой под opengl) и smdview2 (кто-то переделал под DirectX). Также вы можете найти в интернете через google или yandex модификации моих smdview для delphi, но исходники весьма древние (ищите строчку smdview).

Использовать версию для tmtpascal так:
mdldec-ом "декомпилишь" mdl в кучу smd
потом текстовые smd конвертишь в мой бинарный формат:
(smdconv.pas) smdconv.exe r главнаямодель.smd вочтоконвертить.dat
(smdconv.pas) smdconv.exe s анимация.smd вочтоконвертить.dat
(smdconv.pas) smdconv.exe o модельсоднимjoint.smd вочтоконвертить.dat
Приделывать оружие и т.п. к руке с помощью attach, в скобках указываешь node для прикрепления.