Скелетная анимация (Часть 1 – шейдер)

В данной статье я опишу свое понимание скелетной анимации, которая используется во всех современных 3D движках для анимирования персонажей, игрового окружения и т.п.
Начну описание с наиболее осязаемой части – вертексного шейдера, ведь весь путь расчетов, сколь сложным он не был, заканчивается передачей подготовленных данных на отображение в вертексный шейдер.

Скелетная анимация после обсчета на CPU попадает в вертексный шейдер.
Напомню формулу вертекса без скелетной анимации:
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vertex;
Для тех кто не понимает как возникла эта формула, может почитать мою статью описывающую принцип работы с матрицами для отображения 3D контента в контексте OpenGL.
Для остальных – формула реализации скелетной анимации:
” vec4 animatedVertex = bone0matrix * vertex * bone0weight +”
“bone1matrix * vertex * bone1weight +”
“bone2matrix * vertex * bone2weight +”
“bone3matrix * vertex * bone3weight;\n”
” gl_Position = projectionMatrix * viewMatrix * modelMatrix * animatedVertex;\n”

Тоесть конечную матрицу трансформации кости умножаем на вертекс и на вес этой матрицы относительно вертекса. Каждый вертекс может быть анимирован 4-мя костями, сила воздействия регулируется параметром веса кости, сумма воздействий должна равняться единице.
Что делать если на вертекс воздействуют меньше 4-х костей? Нужно поделить вес между ними, воздействие остальных сделать равным нулю.
Математически умножение веса на матрицу называется “Умножение матрицы на скаляр”. Умножение на скаляр позволяет суммировать воздействие матриц на итоговый вертекс.

Сами матрицы трансформации костей передаются массивом. Причем массив содержит матрицы для всей модели в целом, а не для каждого меша по отдельности.

А вот для каждого вертекса отдельно передается следующая информация:
– Индекс матрицы которая воздействует на вертекс
– Вес матрицы которая воздействует на вертекс
Передается не одна кость, обычно используется воздействие 4 костей на вертекс.
Также сумма весов 4-х костей должна всегда быть равна единице.
Далее рассмотрим как это выглядит в шейдере.
Массив матриц:
“uniform mat4 bonesMatrices[kMaxBones];”

Информация о воздействии 4 костей на каждый вертекс:
“attribute vec2 bone0info;”
“attribute vec2 bone1info;”
“attribute vec2 bone2info;”
“attribute vec2 bone3info;”

vec2 – в координате X храним индекс кости (и переводим в int в шейдере), в координате Y вес воздействия кости на вертекс. Почему приходится передавать эти данные в двухмерном векторе? Потому что GLSL не поддерживает передачу читаемых C структур с корректными полями в шейдер.

Ниже приведу пример получения необходимой информации из вектора, для дальнейшей подстановки в формулу animatedVertex:

“int bone0Index = int(bone0info.x);”
“float bone0weight = bone0info.y;”
“mat4 bone0matrix = bonesMatrices[bone0Index];”

“int bone1Index = int(bone1info.x);”
“float bone1weight = bone1info.y;”
“mat4 bone1matrix = bonesMatrices[bone1Index];”

“int bone2Index = int(bone2info.x);”
“float bone2weight = bone2info.y;”
“mat4 bone2matrix = bonesMatrices[bone2Index];”

“int bone3Index = int(bone3info.x);”
“float bone3weight = bone3info.y;”
“mat4 bone3matrix = bonesMatrices[bone3Index];”

Теперь структура вертекса, заполняющаяся на CPU, должна выглядеть так:
x, y, z, u, v, bone0index, bone0weight, bone1index, bone1weight, bone2index, bone2weight, bone3index, bone3weight

Структура вертексного буфера заполняется один раз во время загрузки модели, а вот матрицы трансформации передаются из CPU в шейдер при каждом кадре рендеринга.

В остальных частях я опишу принцип обсчета анимации на CPU, перед передачей в вертексный шейдер, опишу дерево нод костей, проход по иерархии анимация-модель-ноды-мэш, интерполяции матриц.

Источники

http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

Исходный код

https://gitlab.com/demensdeum/skeletal-animation