Створення анаморфных спотворень у Unity

Всім привіт! Зараз я працюю в VRTech, і в рамках роботи я натрапив на цікаву задачу про яку хочеться розповісти. Завдання полягало в тому, щоб отримати анаморфной відображення картинки. Я спробую розповісти, що таке анаморфные спотворення, як розрахувати найпростіший випадок лінійного відображення такого спотворення на площину, а так само запропоную своє рішення реалізоване за допомогою Unity.

image

Нещодавно на роботі мені запропонували цікаву задачку, порахувати на листочку, як перетворити картинку для створення анаморфного спотворення. Мені подобається математика, з цієї причини я зацікавився, так як ніколи про них не чув. Що ж таке анаморфные спотворення?

Загалом оптичне анаморфирование — це спосіб спотворення зображення, який передбачає різний масштаб перетворення в двох взаємно перпендикулярних напрямках. В цілому воно використовується для того, щоб прибрати перспективні спотворення, але так само стало досить популярним інструментом в мистецтві.



Для створення правдоподібного ефекту дуже важливою деталлю є точка огляду. Якщо ви бачили 3д арт на вулиці, то розумієте, що крок вправо, крок вліво і ілюзія розпадається.

Почитавши статті по тому, як робиться таке перетворення і зрозумівши, що в цілому нічого складного в цьому немає, я, як програміст, подумав, що напевно десь на просторах інтернету вже є готова бібліотека. Причому в ідеалі така бібліотека повинна надавати функціонал, щоб параметрично ставити точку огляду, передбачувану висоту зображення та інші плюшки. Я знайшов кілька рішень, але жодне з них не працювало. Тому довелося писати своє.

Пройдемося по тому, як загалом будується таке зображення. Спочатку береться бажаний результат і розбивається на сітку. Чим дрібніше сітка — тим точніше рішення. У Unity я вирішив просто згенерувати меш, у якого можна регулювати кількість комірок в сітці.



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



Вибір інструменту упав на Unity, так як я його добре знаю, а так само всі статті, які я знайшов на просторах інтернетів були написані популярним, а не математичною мовою, і мені хотілося без зайвих рухів перевірити коректність роботи алгоритму (не роздруковуючи на принтері рішення, і думаючи, що ж тут не так). У Unity таке реалізувати дуже просто, так як результат можна подивитися через камеру в 3d просторі. До того ж є реалізований інструментарій для роботи з 3д, векторами і т. п.

За списком фіч рішення повинне було на вхід отримувати зображення, точку огляду користувача, висоту (у метрах) результату з точки зору користувача, а так само точність рішення.

Для того, щоб зробити все це якомога простіше, я вирішив зробити найпростіший спосіб вирішення. Згенерувати меш правильно розставивши на ньому uv координати, натягнути на нього потрібне зображення. Далі з допомогою проекції згенерувати новий меш з новими вертексами та нормалями, але старими uv координатами.

Перша задача вирішується досить просто, так як меш володіє простою структурою, і необхідно було створити простеньку сітку і правильно пронумерувати трикутники. Меш в даному випадку є площиною, на яку картинтка натягується цілком, так що правильна розстановка uv координат так само не становить праці.
Котика для тестів ви вже бачили, але покажу його без «світла», так він красивіше.



Меш згенерований, далі необхідно було порахувати нові вертексы і нові нормалі. Я вирішив визначати нові вертексы за допомогою визначення точки перетину прямої, проведеної через дві точки і площини, заданої трьома точками. Метод визначення точки перетину між площиною і прямою вимагає визначення нормалі до площини, так що нові нормалі були пораховані.

Заголовок спойлера
private Vector3 GetIntersectionOfLineAndPlane(
Vector3 linePoint1, 
Vector3 linePoint2,
Vector3 planePoint1,
Vector3 planePoint2, 
Vector3 planePoint3,
ref Vector3 planeNormal)
{
Vector3 result = new Vector3();

planeNormal = Vector3.Cross(planePoint2 - planePoint1, planePoint3 - planePoint1);
planeNormal.Normalize();
Debug.Log(planeNormal.ToString());
var distance = Vector3.Dot(planeNormal, planePoint1 - linePoint1);
var w = linePoint2 - linePoint1;
var e = Vector3.Dot(planeNormal, w);

if(e != 0)
{
result = new Vector3(
linePoint1.x + w.x * distance / e,
linePoint1.y + w.y * distance / e,
linePoint1.z + w.z * distance / e);
}
else
{
result = Vector3.one * (-505);
}
return result;
}
}


Все, рішення готово! В цілому задача досить проста, але в той же час цікава. Думаю на цьому ефекті можна навіть зробити досить забавну гру і зламати мізки гравцям. У відео можна подивитися результат роботи. (Вихідна картинка на відео дуже темна, так як я не налаштовував світло)



Трохи пізніше я може допилю функціонал, щоб можна було проектувати на кілька площин, а так само на поверхні, що відбивають, і домагатися ефекту зразок цього.



Безпосередньо з кодом рішення, а так само стежити за його можливим подальшим розвитком можна тут.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.