Создание анаморфных искажений в Unity

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

image

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

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

cbba8dbf1ede476e968134d7e5559f3e.jpg

Для создания правдоподобного эффекта очень важной деталью является точка обзора. Если вы видели 3д арт на улице, то понимаете, что шаг-вправо, шаг-влево и иллюзия распадается.

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

Пройдёмся по тому, как в общем строится такое изображение. Изначально берётся желаемый результат и разбивается на сетку. Чем мельче сетка — тем точнее решение. В Unity я решил просто сгенерировать меш, у которого можно регулировать количество ячеек в сетке.

f590f1dcd7e94129848649b5baff3889.png

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

9445f1ce3ddc40bb88df1eec94cda8b8.jpg

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

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

Для того, чтобы сделать всё это как можно проще, я решил сделать самый простой способ решения. Сгенерировать меш правильно расставив на нём uv координаты, натянуть на него нужное изображение. Дальше с помощью проекции сгенерить новый меш с новыми вертексами и нормалями, но старыми uv координатами.

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

91f56c97b3984c61b9efd6e59a440506.jpg

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

Заголовок спойлера
 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;
    }
}


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

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

08cc984de70f46fca569e80818d5f3bf.jpg

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

Комментарии (1)

  • 1 февраля 2017 в 07:37

    0

    Есть один художник — Орос, Иштван. Мне нравится его работа «Скрытый портрет Жюля Верна».

© Habrahabr.ru