Подтвердить что ты не робот

Применение весов к матрицам и вершинам (вращение кости)

Я вращаю кости скелета внутри сетки для низкой поли 3D-фигуры. На вершинном шейдере это применяется так.
GLSL:

    vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight;
    vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight);
    gl_Position =  vert1+vert2;

bone_matrix[index1] - матрица одной кости, а bone_matrix[index2] - матрица другого. weight обозначает vertex_in членство в этих костях. Проблема заключается в том, что чем ближе вес к .5, тем больше диаметр локтя сжимается при приложении вращения. Я протестировал его около 10 000 вершинного цилиндра (с градиентом весов). Результат был похож на изгиб садового шланга.

Я получил свой метод взвешивания из этих источников. Фактически это единственный способ найти:
http://www.opengl.org/wiki/Skeletal_Animation
http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html
http://blenderecia.orgfree.com/blender/skinning_proposal.pdf

initial_ugly_good

Левый - это то, как начинается форма, а середина - это то, как приведенное выше уравнение вращает ее, а правая - моя цель. Средние точки взвешены 0.5. Это только ухудшается, тем больше он изогнут, на 180 градусов он имеет нулевой диаметр.

  • Я попытался собрать матрицу на шейдере, чтобы я мог применять весы к вращению вместо результирующих вершин. Он выглядит идеально, как тот, что изображен справа, но требует сборки матрицы для каждой вершины (дорогой).
  • Я изучил кватернионы, но glsl не поддерживает их (исправьте меня, если я ошибаюсь), и они запутывают. Это то, что мне нужно сделать?
  • Я рассмотрел наличие трех костей на сустав и добавил "колено" между каждой костью. Это не устранит проблему, а уменьшит ее.
  • Я рассматриваю проецирование вершины на свое первоначальное расстояние от оси после поворота. Это провалится на 180 градусов, но будет (относительно) дешевым.

Итак, рассматривая варианты или другие варианты, которые я, возможно, не рассматривал, Как другие избегают этого эффекта сжатия?

EDIT: Я получил SLERP для работы с использованием кватернионов, но я решил не использовать его, поскольку GLSL не поддерживает его. Я не мог заставить геометрический SLERP работать, как описано Томом. Я получил NLERP, работающий на первые 90 градусов, поэтому я добавил дополнительную "кость" между каждым суставом. Поэтому, чтобы сгибать предплечье 40 градусов, я сгибаю локоть и предплечье на 20 градусов каждый. Это устраняет эффект защемления за счет удвоения количества костей, которое не является идеальным решением.

4b9b3361

Ответ 1

Проблема

Причина того, что ваше видение иллюстрируется рисунком в Levans answer. Однако, чтобы понять, что происходит, рассмотрите, что происходит при выполнении кода:

Если первая точка vert1 имеет координаты (p, 0), координаты vert2 будут (p cos(α), p sin(α)), где α - это угол между двумя костями (это всегда возможно с учетом соответствующего преобразования координат). Добавляя их вместе с использованием соответствующих весов w и 1-w, мы получаем следующие координаты:

x = w p + (1-w) p cos(α)
y = (1-w) p sin(α)

Длина этого вектора:

length^2 = x^2 + y^2
         = (w p + (1-w) p cos(α))^2 + (1-w)^2 p^2 sin(α)^2
         = p^2 [w^2 + 2 w (1-w) cos(α) + (1-w)^2 cos(α)^2 + (1-w)^2 sin(α)^2]
         = p^2 [w^2 + (1-w)^2 + 2 w (1-w) cos(α)]

В качестве примера, когда w = 1/2 это упрощает:

length^2 = p^2 (1/2 + 1/2 cos(α)) = p^2 cos(α/2)^2

И length = p |cos(α/2)|, тогда как длина исходных векторов p (см. graph). Длина нового вектора становится меньше, это уменьшающий эффект, который вы воспринимаете. Причиной этого является то, что мы фактически интерполируем две вершины вдоль прямой. Если мы хотим сохранить ту же длину p, нам действительно нужно интерполировать по окружности вокруг центра вращения. Один из возможных подходов - перенормировать полученный вектор, сохраняя ширину в суставе.

Это означает, что мы должны разделить полученные координаты вершин на |cos(α/2)| (или более общий результат для произвольных весов). Разумеется, это имеет побочный эффект: деление на ноль, когда угол составляет ровно 180 ° (по той же причине ширина в суставе равна нулю с вашей техникой).

Я не эксперт по скелетной анимации, но мне кажется, что исходное решение, как вы его описали, является приблизительным для работы с небольшими костяными углами (где эффект сокращения минимален).

Альтернативные подходы

Другой подход - интерполировать ваши вращения вместо ваших вершин. См., Например, slikp wiki page и этот документ.

SLERP

Метод slerp аналогичен описанной выше технике в том смысле, что он также сохраняет ширину в суставе, однако он интерполируется непосредственно по круговой траектории вокруг сустава. Общая формула:

gl_Position = [sin((1-w)α)*vert1 + sin(wα)*vert2]/sin(α)

Учитывая точки сверху vert1 = (p, 0) и vert2 = (p cos(α), p sin(α)), применяя формулу SLERP, получаем result = (x, y) с:

x = p [sin((1-w)α) + sin(wα) cos(α)]/sin(α)
y = p sin(wα) sin(α)/sin(α) = p sin(wα)

Вычисление косинуса cos θ угла между vert1 и result дает:

cos(θ) = vert1*result/(|vert1| |result|) = vert1*result/p^2
       = p^2 [sin(wα) + sin((1-w)α) cos(α)]/sin(α)/p^2
       = [sin(α) cos((1-w)α) - cos(α) sin((1-w)α) + sin((1-w)α) cos(α)]/sin(α)
       = cos((1-w)α)

Угол между vert2 и result равен:

cos(φ) = vert2*result/p^2
       = [sin(wα) cos(α) + sin((1-w)α) cos(α)^2 + sin((1-w)α) sin(α)^2]/sin(α)
       = [sin(wα) cos(α) + sin((1-w)α) cos(α)]/sin(α)
       = [sin(wα) cos(α) + sin(α) cos(wα) - cos(α) sin(wα)]/sin(α)
       = cos(wα)

Это означает, что θ/φ = (1-w)/w, который выражает тот факт, что SLERP интерполируется с постоянной радиальной скоростью. При работе с 3D-матрицами вращения мы можем выразить преобразование вращения vert1 в vert2 как M = inverse(A)*B = transpose(A)*B, чтобы мы могли выразить угол поворота α как:

cos(α) = (tr(M) - 1)/2 = (tr(transpose(A)*B) - 1)/2
       = (A[0][0]*B[0][0] + A[0][1]*B[1][0] + A[0][2]*B[2][0] + 
          A[1][0]*B[0][1] + A[1][1]*B[1][1] + A[1][2]*B[2][1] + 
          A[2][0]*B[0][2] + A[2][1]*B[1][2] + A[2][2]*B[2][2] - 1)/2

Quarterion LERP

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

Если вы действительно хотите избежать этих проблем, вы всегда можете разделить свои сетки на суставе и повернуть их отдельно.

Ответ 2

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

Прежде всего, позвольте мне поставить эту маленькую схему, таким образом, мы будем уверены, что все мы говорим об одном и том же:

enter image description here

Синие и зеленые фигуры - это оригинальные кости, полностью вращающиеся с помощью bone_matrix[index1] или bone_matrix[index2]. Красная точка - это центр вращения, оранжевая фигура - это то, что вы хотите, а черный - то, что у вас есть.

Итак, вы считаете, что строите как средневзвешенное значение синих и зеленых, на этом рисунке мы видим (благодаря серым линиям), почему он так сжимается.

Вам нужно как-то компенсировать это сокращение, я бы предложил уменьшить точки из вашего центра вращения, нам нужно масштабирование значения 2 на стыке между костями и значение 1 на концах.

Пусть scale_matrix - предварительно вычисленная матрица: масштабирование амплитуды 2 с центром в центре вращения (красная точка).

В итоге вы получите этот шейдер:

vec4 vert1 = (bone_matrix[index1]*vertex_in)*weight;
vec4 vert2 = (bone_matrix[index2]*vertex_in)*(1-weight);
vec4 inter =  vert1+vert2;
vec4 scaled1 = inter*(1-2*min(weight, 1-weight));
vec4 scaled2 = (scale_matrix*inter)*(2*min(weight, 1-weight));
gl_Position =  scaled1+scaled2;

Боюсь, я не могу проверить это прямо сейчас (я не очень много знаю о GLSL), но я думаю, что вы сможете адаптировать его к своему делу, если что-то не подходит.

Ответ 3

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

enter image description here

Весы показаны зеленым/чистым. Тем не менее, это требует немного обманывания костей, поэтому, когда вы наклоняетесь вправо, вы используете правые кости и устанавливаете центр вращения вправо, а когда слева - левые кости и центр вращения слева.