В поисках перспективных теней для roguelike



    Уважаемые Хабровчане, представляю вашему вниманию продолжение изысканий на тему поиска подходящих теней для 2D рогалика.

    Данный пост является сиквелом публикации, своеобразной работой над ошибками и дальнейшее развитие идеи.

    В своих комментариях, уважаемые критики, совершенно справедливо отметили, что в замкнутых пространствах тени получились угловатыми, и несколько не естественными. Было предложено несколько вариантов решения, мне понравилось предложение использовать ray casting для расчёта тени.



    Уточняю, я не работаю с видеокартой (пока не работаю), все результаты смоделированы на ЦПУ.

    В данной работе по рейкастингом понимается метод построения изображения посредством бросания лучей от наблюдателя в пространство до пересечения с препятствием (границами экрана) и подсвечиванием места их столкновения.

    Здесь мы будем использовать упрощённую разновидность рейкастинга, основанную на пересечении лучом тайловой сетки. Данный метод широко использовался в псевдотрёхмерных играх прошлого (например Wolfenstein_3D, почтение тем кто в теме), мы его адаптируем для двумерного пространства.



    Алгоритм, достаточно прост как для понимания, так и воплощения. Приведу собственную его реализацию:

    Pascal
    // i,j - координаты тайла, а - угол
    // X,Y - начальное смещение координат
    // r - максимальный радиус для расчёта
    
    // Инициализация направления
    if cos(a)<0 then
      begin di :=-1; ddi:= 0; end
    else
      begin di := 1; ddi:= 1; end;
    
    if sin(a)<0 then
      begin dj :=-1; ddj:= 0; end
    else
      begin dj := 1; ddj:= 1; end;
    
    // Предварительный расчёт первой точки по Х и Y
    x1 := (i+ddi) * tile_size;
    y1 := y+ (x1-x) * tan(a);
    Dx := len(x,y,x1,y1);
    
    y1 := (j+ddj) * tile_size;
    x1 := x+ (y1-y) * cotan(a);
    Dy := len(x,y,x1,y1);
    
    sum_lenX := 0;
    sum_lenY := 0;
    
    // Размер тайла по X и Y под углом a
    rX := abs(tile_size / cos(a));
    rY := abs(tile_size / sin(a));
    
    // выбираем точки пересечения
    repeat
      if sum_lenX+DX < sum_lenY+DY then
        begin  
          x1 := (i+ddi) * tile_size;
          y1 := y+ (x1-x) * tan(a);
          i  := i+di;
          // Проверяем тайл на наличие стены или границ экрана
          key  := is_wall(i,j); 
          sum_lenX := sum_lenX + DX;
          if DX<>rX then DX:=rX;
          // Если радиус больше нужного обрываем цикл
          if r<sum_lenX then Break;
        end
        else
        begin
          y1 := (j+ddj) * tile_size;
          x1 := x+ (y1-y) * cotan(a);
          j  := j+dj;
          // Проверяем тайл на наличие стены или границ экрана
          key  := is_wall(i,j);
          sum_lenY := sum_lenY + DY;
          if DY<>rY then DY:=rY;
          // Если радиус больше нужного обрываем цикл
          if r<sum_lenY then Break;
        end; 
    until (Пока не найдём стену или границу экрана); 
    
    // x1,y1 искомые координаты
    

    Так как луч пересекает ячейки по каждой оси на одинаковом расстоянии, то можно сэкономить на расчётах и только проверять нет ли стены в границах тайла. Нам нужно пересечение с препятствием и запоминать его координаты.

    В своей реализации я вынес всю тригонометрию и деления в отдельную таблицу для каждого угла, что значительно ускорило алгоритм.

    Запустив лучи во все стороны с нужным шагом получим примерно такую картину:



    Увеличив количество лучей до нескольких тысяч и получим искомый многогранник области видимости. Возможно, конечно, бросать лучи и для каждого пикселя изображения, как на 3D ускорителях, но без видеокарты тут уже не обойтись.



    Дальше начинается работа со слоями.

    Область видимости. Здесь и далее лучи немного проходят в глубь объектов. Такая игровая условность создаёт уникальный антураж, свойственный 2D Играм.



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



    Сведение всего вместе. Не хватает только жутких монстров и сокровищ… много сокровищ.



    Стены с изменяемой кривизной проникновения света мне не зашли, но возможно это на любителя.



    В процессе создания прототипа я перепробовал множество вариаций модели, некоторые из них лучше подходят для хоррора:



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

    Спасибо за внимание.

    Ссылка поиграться (exe для виндовс)

    Часть 1, Часть 3
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 16

      0
      Если нужен метод только для ограничения области видимости, то есть более простой вариант (если не ошибаюсь, он используется, например, в teleglitch): просто над препятствием рисуем чёрную вертикальную стену с сильной перспективой, которая просто загораживает лишнее.
        0
        Ранее не сталкивался с данной игрой, вы правы, отдаленно похоже, но эффект несколько иной.
          0

          В вашей прошлой статье ведь тот же принцип построения теней был?
          Кстати, как там полигоны теней отрисовывались: что-то быстрое типа такого?

            0
            Принцип был другой. Если упрощённо, в прошлой статье мы кидали слои теней на изображение, перекрывая его. А в данной работе, мы кидаем лучи света на маскирующий слой, осветляя его, создавая тем самым карту освещения.
              0

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

        +1
        Точечные источники освещения отбрасывают очень уж жёсткие тени. Для теней многоугольников можно упростить рейкастинг, например, как тут ncase.me/sight-and-light.

        Мягкие тени сделать сложнее, т.к. источник света не точка, а, например, отрезок. И вместо видно/не видно точку нужно считать под каким углом виден отрезок освещения из точки (получается как-бы обратный рейкастинг от точки к источнику/ам освещения). Хотя это и вычислительно сложно, можно заранее «запечь» карту освещения для статических источников.
          0
          Метод что указан по ссылке будет хорошо работать только когда объектов не много. Например: при разрешении Full HD — разрешение 1920×1080 и размере тайла 32х32, получим сетку 60х33 видимых тайла, итого верхняя граница 1980х4 ~ 8к лучей, без мягкой тени. Перспективно нужно пробовать.

          Я не сторонник мягких теней, их делать не сложнее, а вычислительно дороже. Нужно лишь выпустить пару лучей со сдвигом в стороны от испускающего свет объекта, создав как бы объёмный источник освещения.
            0
            Не соглашусь с рассчётом :). В сетке 60х33 будет (60+1)х(33+1) ~ 2к точек.
              0
              Один тайл мы отдадим на откуп обрамлению ).
              Про то, что у каждого тайла 4 угла вы забыли, или я не правильно понял метод?
              Хотя, карта должна состоять хотя бы наполовину из коридоров, а не только стен, так что 2-4к лучей вполне реально.
                +1
                Лучи идут к точкам.
                Одна и та-же точка может быть использована для нескольких тайлов. Например для одного тайла она верхняя левая, для другого она же нижняя правая. Нет смысла трассировать одну и ту-же по факту точку дважды.
                В плотном блоке 2х2 тайла действительно по 4 угла на тайл, но точек по факту 9, а не 16. Внутренняя точка общая для всех 4х тайлов, точки на серединах граней общие для 2х смежных блоков (сколько тайлов используют точку):
                1 2 1
                2 4 2
                1 2 1
                  0
                  Очень разумное решение, руки чешутся применить, по моим расчётам выигрыш на математике будет 2-3 раза, жаль, что на общем фоне расчётов это мизер.
                  Сейчас основная нагрузка идёт на тайловый движок и слияние слоёв.

                  Основная идея состоит в том, что хоть расчёты придется делать для всех тайлов экрана, но соединения стен можно просчитать заранее и кэшировать.
          0
          В лабиринте стоит докрашивать освещение до полного блока, если освещено больше 1/3 блока
            0
            Реализовать не проблема, но в чём эффект такого подхода?
              0
              Полагаю чтобы игрок чётко понимал — согласно игры он видит содержимое тайла или нет. В DoomRL, например, от этого зависит можно ли выстрелить в противника. Если механика игры это предусматривает, то логично как-то однозначно трактовать видимость в спорных ситуациях. Субъективно для игрока по факту противник виден, а выстрелить по нему по непонятным причинам нельзя. Дополнительная подсветка в таком случае устранила бы неоднозначность видим/не видим можно/нельзя попасть.
                0
                Как сказал человек выше — чтобы было однозначнее. Ну и глазу приятнее, раз карта квадратная, значит и тени должны быть квадратными
              0
              Как-то писал систему 2D освещения на рейтресинге в шейдерах. Работало довольно быстро, но со своими минусами. Ещё из особенностей была поддержка любых форм источников света, задавались они через обычные спрайты. Сейчас, к сожалению, забросил проект и он не заработает на последних версиях Unity3D.

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

              Самое читаемое