Для своего движка (Indie engine), понадобилось использовать ms3d модели со скелетной анимацией, причем что бы это все работало на мобильных устройствах. Мучился с этим я долго, я не понимал вообще ничего, и наконец-то получилось. Я хочу поделиться опытом.
Стоит запомнить несколько правил.
- Создайте отдельный класс скелета
- Класс скелета хранит структуры костей
- Кость знает про своего родителя, имеет позицию и поворот в локальной системе координат, так же кость знает про ключевые кадры анимации
- Кадр знает про СМЕЩЕНИЕ позиции и поворота (это важно), и время кадра
- вершина должна знать про порядковый номер кости из скелета (несколько костей и вес для каждой)
пример структуры вершины:
struct Vertex
{
Vertex(float x = 0, float y = 0, float z = 0, float nx = 0, float ny = 0, float nz = 1, float u = 0, float v = 0,
int joint1 = 0, float weight1 = 0, int joint2 = 0, float weight2 = 0, int joint3 = 0, float weight3 = 0, int joint4 = 0, float weight4 = 0)
: x(x), y(y), z(z), nx(nx), ny(ny), nz(nz), u(u), v(v)
{
if (joint1 < 0) joint1 = 0;
if (joint2 < 0) joint2 = 0;
if (joint3 < 0) joint3 = 0;
if (joint4 < 0) joint4 = 0;
joint[0] = joint1;
weight[0] = joint1 >=0 ? weight1 : 0;
joint[1] = joint2;
weight[1] = joint2 >=0 ? weight2 : 0;
joint[2] = joint3;
weight[2] = joint3 >=0 ? weight3 : 0;
joint[3] = joint4;
weight[3] = joint4 >=0 ? weight4 : 0;
}
float x, y, z;
float nx, ny, nz;
float u, v;
float joint[4];
float weight[4];
};
Со структурой разобрались. Можно заполнять скелет данными.
Важно учесть, что в конкретный момент времени положение кости будет считаться так: (положение родительской кости умноженное на (положение кости умноженное на смещение кадра)), положение родительской кости это рекурсия, тоже считается по этой же формуле разумеется.
В этой формуле заключается самая магия. Затем перед тем как отдать в шейдер матрицы костей, нам каждую матрицу кости, которая вычислялась по первой формуле нужно умножить на инвертную матрицу положения кости - это матрица считается по формуле (родительское положение умноженное на локальное), тоже через рекурсию.
Ребята кому не понятно и кто тоже страдает от скелетной анимации я постараюсь помочь чем смогу. Не буду отвечать на вопросы как заполнить шейдер матрицами костей, только по теме. Тема скелетная анимация а не учимся программировать шейдеры
Еще важно точно знать, что вы заполняете скелет правильными данными, иначе как я потратите дополнительно несколько дней на выявление проблемы.
Ах да забыл про шейдер. Приведу его код:
const std::string texturedAnimShaderVs = ""
"attribute vec4 position;\n"
"attribute vec2 texcoord;\n"
"attribute vec4 jointId;\n"
"attribute vec4 weight;\n"
"uniform mat4 mvp;\n"
"uniform mat4 joints[64];\n"
"varying vec2 vTexcoord;\n"
"void main(){\n"
" vec4 newVertex;\n"
" mat4 newMat = joints[int(jointId.x)] * weight.x;\n"
" newMat += joints[int(jointId.y)] * weight.y;\n"
" newMat += joints[int(jointId.z)] * weight.z;\n"
" newMat += joints[int(jointId.w)] * weight.w;\n"
" newVertex = newMat * position;\n"
" gl_Position = mvp * newVertex;\n"
" vTexcoord = texcoord;\n"
"}\n";
const std::string texturedAnimShaderFs = ""
"uniform sampler2D texture0;\n"
"varying vec2 vTexcoord;\n"
"void main(){\n"
" gl_FragColor = texture2D(texture0, vTexcoord);\n"
"}\n";