Додано підтримку трансформації вузла сцени кватерніону в двигун Irrlicht.

У цій статті я розповім про додавання підтримки кватерніонного перетворення в графічний механізм Irrlicht. Цей приклад може бути корисним для будь-яких інших ігрових движків з відкритим кодом. Докладніше про кватерніони

Автор: Сергій Тарабан

:ad:

Чому нам потрібна кватерніонна трансформація в Irrlicht.

Irrlicht – простий і дуже корисний ігровий движок. Але він все ще не підтримує перетворення обертання кватерніонів для ігрових об’єктів у сцені. Ви можете встановити трансформацію вузла сцени лише за кутами Ейлера. Це призводить до таких проблем, як:
  • Замок Gimbal.
  • Неналежне перетворення після імпортування об’єктів сцени з графічного програмного забезпечення, наприклад 3ds max, Maya, Blender тощо. li>
  • Реалізація готового до використання коду з інших механізмів або зразків з Інтернету.
Застосування до комп’ютерної графіки Gimbal lock означає, що матриця обертання не є незалежною, тому ви не можете використовувати їх для встановлення незалежної послідовності перетворення обертання. Це означає, що, наприклад, якщо ви обертаєте якийсь об’єкт навколо осі X, Y та Z послідовно, ви отримуєте інше перетворення, ніж ви очікували. Ця ситуація називається «втрата ступеня свободи з кутами Ейлера». В результаті ви отримали поворот навколо осі x тільки на кут, що дорівнює сумі кутів повороту X і Z. Ви можете побачити це в цих формулах: Доказ блокування підвісу Після обчислень ми отримали таку матрицю: Доказ блокування підвісу

Ще один неприємний сюрприз чекає на вас, якщо ви імпортуєте сцену з 3D графічного редактора, наприклад Autodesk 3ds Max. Він зберігає перетворення в кватерніонах, а в Irrlicht вам потрібно перетворити їх на кути Ейлера. Як наслідок, деякі об’єкти на сцені повертатимуться дуже дивно або навіть неправильно через блокування карданного механізму або через алгоритм перетворення на кути Ейлера, оскільки він може створювати різні кути Ейлера для одного кватерніона.

Щоб вирішити ці проблеми, нам потрібно використовувати кватерніони для перетворення положення вершини. У цьому випадку позиція вершини множиться на кватерніон, а не на матрицю. Кватерніони також можуть бути помножені один на одного, а також на матрицю. У цьому випадку результуючий кватерніон збереже всю послідовність поворотних перетворень і гарантуватиме їх незалежність. Тут я маю відзначити важливий момент. Кватерніон можна перетворити на матрицю обертання, але ця матриця не матиме властивостей кватерніону. Ця матриця обертання отримана з кутів Ейлера і призведе до блокування кардана. Тому не використовуйте його замість кватерніона.

 

Живий зразок замка Gimbal.

Розглянемо тестову сцену в 3ds max. Кожна гілка цього фальшивого дерева - однаковий конус, який має різну трансформацію обертання, масштаб і положення. Давайте спробуємо завантажити цю сцену в Irrlicht. Для максимального експорту я використовую власний сценарій, написаний на MAXScript. І для того, щоб завантажити в свій движок, я просто аналізую файл сцени, який містить перетворення для кожного об’єкта сцени.На скріншоті нижче: Стандартна сцена 3ds max: 3dsmax_test_object

і ось як це буде в Irrlicht без підтримки перетворення кватерніонів: test_object_irr_no_quaternions

Ось сцена в Irrlicht з підтримкою перетворення кватерніонів:

test_object_irr_quaternions_supported

Як ви бачите, сцена в Irrlicht відображається правильно.

Реалізація.

У бібліотеці Irrlicht irr::core::Quaternion є хороший клас кватерніонів, який підтримує всі необхідні операції з кватерніонами. Тому нам потрібно лише додати нові методи до інтерфейсу ISceneNode і додати реалізації для кожного з графічних драйверів. У цій статті я покажу, як додати підтримку кватерніонів для OGLESDriver (OpenGL ES 1.0) і OGLES20Driver (OpenGL ES 2.0). Для інших драйверів є все те саме. Основна залежність полягає в тому, як ви реалізували вершинну трансформацію - за допомогою фіксованого конвеєра або в шейдері. Я опишу ці два варіанти на прикладі цих двох драйверів.

Перше, що ми робимо, це додаємо приватне поле Quaternion RelativeRotationQ, методи setRotationQ() і getRotationQ() у класі ISceneNode. Він потрібен, щоб об’єкти можна було трансформувати за допомогою кватерніона.

core::quaternion RelativeRotationQ;

віртуальна порожнеча setRotationQ(const core::quaternion& rotation)
{
RelativeRotationQ = обертання;
}
virtual const core::quaternion& getRotationQ() const
{
return RelativeRotationQ;
}

 

 

Додайте метод setTransformQ для інтерфейсу IVideoDriver virtual void const setTransformQ( core::quaternion q ) = 0; І його реалізація в стандартному драйвері CNullDriver

const void CNullDriver::setTransformQ( core::quaternion q )
{
    RotationTransformQuat = rot;
}

 

І не забудьте додати поле core::quaternion RotationTransformQuat;

Потім додайте код нижче в усі реалізації інтерфейсу ISceneNode у функції render() перед викликом setTransform.

 driver->setTransformQ(RelativeRotationQ);

Наприклад, як у функції CMeshSceneNode::render() <код>…

driver->setTransformQ(RelativeRotationQ); //ось наш код
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);

Тепер перейдемо до коду реалізації драйвера.Для OpenGL ES 1.0 і OpenGL FFP (фіксований конвеєр) просто додайте код нижче у функцію setTransform:

case ETS_WORLD:
{
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(Матриці[ETS_VIEW].pointer());
    glMultMatrixf(Матриці[ETS_WORLD].pointer());
    //код підтримки обертання кватерніону
    кут плавання = 0,0f;
    core::vector3f вісь;
    RelativeRotationQ.toAngleAxis(кут, вісь);
    glRotatef(кут*ядро::RADTODEG, вісь.X, вісь.Y, вісь.Z);
}

З міркувань продуктивності я рекомендую вам подумати про переміщення кватерніонного перетворення в інше місце. Додавання кватерніонного перетворення для OpenGL ES 2.0 буде дещо складнішим. По-перше, нам потрібно змінити вершинний шейдер glsl COGLES2FixedPipeline.vsh для підтримки множення кватерніонів. Додайте новий рівномірний fvQuatRotation. Ми будемо використовувати цю змінну для передачі кватерніона в шейдер. uniform vec4 fvQuatRotation;

Тепер додамо функцію множення кватерніона на вектор:

Я напишу дві версії алгоритму множення кватерніонів для шейдера GLSL - одну без оптимізації, а другу - з оптимізацією. Неоптимізований варіант:

vec4 quatMultyQV( vec4 q, vec4 v )
{
    return vec4(quatMultyQQ(q, quatMultyQQ(vec4(v.xyz, 0.), getConjugate(q))).xyz, v.w);
}
vec4 quatMultyQQ( vec4 q, vec4 rq)
{
    return vec4( cross(q.xyz, rq.xyz) + q.w*rq.xyz + rq.w*q.xyz, q.w*rq.w - dot(q.xyz, rq.xyz) );
}
vec4 getConjugate( vec4 q )
{
повернути vec4(-q.x, -q.y, -q.z, q.w);
}

Оптимізований варіант: <код>

vec4 quatMultyQV( vec4 q, vec4 v ) //q-кватерніон, v – вектор для дії перетворення
{
vec3 x = q.xyz;
float w = q.w;
vec3 a = cross(x,v.xyz) + w*v.xyz;
return vec4((хрестик(a,-x) + крапка(x,v.xyz)*x + w*a), v.w);
}

 

Потім запишіть код вершинної трансформації:

// inVertexPosition - атрибут vec4
vec4 inRotVertexPosition = quatMultyQV(fvQuatRotation, inVertexPosition);
vec4 fvObjectPosition = matWorld * inRotVertexPosition;
gl_Position = matViewProjection * fvObjectPosition;

 

Тепер поверніться до коду двигуна Irrlicht. Написати код для з’єднання значення шейдера кватерніону з шейдером glsl у функції COGLES2FixedPipelineShader::OnRender()

s32 unifQuatRotationLocation = this->getUniformLocation("fvQuatRotation");
core::quaternion quat = Driver->getTransformQuat();
float vec[4] = {quat.X, quat.Y, quat.Z, quat.W};
glUniform4fv(unifQuatRotationLocation, 1, reinterpret_cast(&(vec)));

Тож кінець. Ми успішно впровадили шейдер обертання кватерніонів GLSL. І тепер обидва наші драйвери Irrlicht підтримують обертання кватерніонів. Тепер ми можемо використовувати новий метод setRotationQ() , щоб установити перетворення обертання об’єктів виключно за допомогою кватерніонів.І останнє. Майте на увазі, що різні графічні редактори можуть використовувати різну систему координат: правосторонню, лівосторонню тощо. Я надаю вам корисний код для перетворення кватерніона з лівостороннього XYZ у правий базис XZY. Я використовую цю функцію для перетворення кватерніона з 3ds max. Як бачите, перетворення кватерніонів дуже просте, вам просто потрібно інвертувати або замінити потрібну вісь.

кватерніон convertFromXYZToLeftHandedXZY( кватерніон qsrc )
{
    кватерніон q;
    float angle = 0;
    core::vector3df вісь;
    qsrc->toAngleAxis(кут, вісь);
    core::swap(axis.Z, axis.Y);
    вісь.Z = -вісь.Z;
    вісь.X = -вісь.X;
    вісь.Y = -вісь.Y;
    q.makeIdentity();
    q.fromAngleAxis(кут, вісь);
    повернення q;
}

Автор: Сергій Тарабан