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

image

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

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



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

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

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



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



Выбор инструмента пал на Unity, так как я его отлично знаю, а так же все статьи, которые я находил на просторах интернетов были написаны популярным, а не математическим языком, и мне хотелось без лишних телодвижений проверить корректность работы алгоритма (не распечатывая на принтере решение, и думая, что же тут не так). В Unity такое реализовать очень просто, так как результат можно посмотреть через камеру в 3д пространстве. К тому же имеется реализованный инструментарий для работы с 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;
    }
}


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



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



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

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


  1. Salavat
    01.02.2017 07:37
    -1

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


  1. Igor_Sib
    01.02.2017 09:18

    Несколько лет назад делал подобные преобразования ( в 3д максе) и распечатывал картинки, потом клал на стол для получения подобного эффекта. На самом деле там просто проекцию на плоскость надо отбросить.

    А вообще наверно если сделать приложение — то художники которые рисуют подобные картинки на улицах наверно заинтересуются. Ну и кто просто ради веселья захочет разок распечатать картинку.


    1. sergarcada
      01.02.2017 10:38
      +1

      Как тут не вспомнить анекдот про Вовочку "… Это все фигня. Он в ванне крокодила нарисовал, так я через нарисованную дверь выпрыгнул."


    1. oisee
      01.02.2017 10:48
      +2

      На улицах (и в зданиях) никакое приложение не нужно — достаточно взять обычный проектор, поставить туда, откуда должно производиться наблюдение и спроецировать изображение туда, где оно должно быть.
      Затем обрисовать контуры на поверхности.

      Можно дома с помощью проектора для диафильмов такое сделать.


      1. sergarcada
        01.02.2017 10:59

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


  1. BasmanovDaniil
    01.02.2017 14:48

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


    1. DyadichenkoGA
      01.02.2017 15:54

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

      Но скажем с отражающими поверхностями такого решения достичь этими средствами уже не получится (либо это будет сложнее). Хотя в целом сейчас для обработки отражения нужно определять, какая поверхность отражает, и проецировать по отражённому лучу на неотражающую поверхность. Что опять таки, не такая сложная доработка.