Как стать автором
Обновить
89.59
Skillfactory
Онлайн-школа IT-профессий

Шейдеры, голограммы и утечка света на чистом CSS

Время на прочтение7 мин
Количество просмотров3.8K
Автор оригинала: Robb Owen

К старту курса по Fullstack-разработке на Python рассказываем, как на чистом современном CSS имитировать шейдеры аккуратным наложением слоёв и эффектов. За подробностями и демонстрациями приглашаем под кат.


Может, я немного преуменьшаю, но WebGL — классная штука. Пять минут на одном из множества сайтов о наградах за дизайн — и вы увидите, как сайты один за другим опираются на canvas. Инструменты типа threejs привносят в браузер шейдеры GLSL и 3D, которые выводят визуальные эффекты на новый уровень.

Это заставляет задуматься о том, зачем позволять JS получать всё удовольствие. Широкую поддержку получило свойство CSS mix-blend-mode — и теперь у нас есть многие распространённые методы шейдинга. Подобранные изображения и тщательное наложение слоёв помогут создать удивительные эффекты без необходимости в JS.

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

О, этот блеск. Давайте разбираться
О, этот блеск. Давайте разбираться

Что такое "шейдер" CSS?

Шейдеры WebGL — это сложные сценарии, которые определяют отрисовку каждого пикселя. Уровня управления WebGL в CSS нет, поэтому на простейшем уровне "шейдер" — это изображение со слоями фона поверх него. При тщательном обращении с градиентами, масками, вложенностью и свойством mix-blend-mode можно управлять взаимодействием слоёв и изображения в самом низу. Хотя я немного вольно обошёлся с названием.

В примере выше применяется несколько вложенных div:

<div class="shader">
  <img src="tower.jpg" alt="Asakusa at dusk">
  <div class="shader__layer specular">
    <div class="shader__layer mask"></div>
  </div>
</div>

Чтобы выровнять слои относительно изображения в основании, определим положение вложенного содержимого:

.shader {
    position: relative;
    overflow: hidden;
    backface-visibility: hidden; /* to force GPU performance. More on that later */
  }

  .shader__layer {
    background: black;
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-size: 100%;
  }

С основой мы разобрались, теперь посмотрим на первый слой эффекта — освещение.

Симуляция зеркальности

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

.specular {
  background-image: linear-gradient(180deg, black 20%, #3c5e6d 35%, #f4310e, #f58308 80%, black);
}

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

К счастью, винтажное свойство CSS уровня 1 может помочь: background-attachment: fixed означает, что при прокрутке страницы градиент останется привязанным к области просмотра браузера. Это не только привносит в шейдер столь необходимое движение, но и означает, что мы сможем очень грубо имитировать изменение угла обзора без всякого JavaScript.

.specular {
  background-attachment: fixed;
  background-image: linear-gradient(180deg, black 20%, #3c5e6d 35%, #f4310e, #f58308 80%, black);
}

Прекрасно! А теперь применим освещение к основному изображению.

Знай режимы смешивания

Название mix-blend-mode подразумевает смешивание цветов каждого пикселя в одном слое с пикселем под ним. Как и в GLSL, в CSS есть длинный список вариантов. Создать подходящий эффект — значит знать сочетание цветов, которое даст именно его. Но что на самом деле делают режимы смешивания? До кропотливой работы над шейдером кратко рассмотрим режимы, которые будем применять.

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

Посмотрим на смешивание с умножением — multiply. режим берёт цвет каждого пикселя текущего слоя и умножает на цвет пикселя прямо под ним. Это означает, что тёмные цвета текущего слоя затеняют цвета нижнего слоя.

Значение screen берёт инверсии двух пикселей и перед инвертированием результата умножает их. Это может показаться сложным, но представить операцию можно как противоположность умножения: цвета темнее становятся прозрачными, а на нижнем слое видны только цвета светлее.

Режимы color-dodge и color-burn — как multiply и screen в овердрайве. Оба режима в математическом смысле делят цвет пикселя на основном слое на цвет в текущем слое.

Значение color-dodge это означает, что средние тона и блики размываются, а тёмные тона не затрагиваются вообще; color-burn усиливает тени и средние тона ближе к тёмным, а тона светлее не затрагиваются.

Ниже левый пример — со свойством color-dodge, а правый — с color-burn.

Композиция слоёв

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

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

Вам может быть интересно, как это возможно на CSS, если режим смешивания влияет только на пиксели слоя прямо под ним, а установить можно лишь один режим. Здесь наступает звёздный час структуры HTML:

<div class="shader">
  <img src="tower.jpg" alt="Asakusa at dusk">
  <div class="shader__layer specular">
    <div class="shader__layer mask"></div>
  </div>
</div>

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

Пробуем. С подходящим тёмным background-image установим mix-blend-mode: multiply слою .mask и отбросим части градиента, где просветов быть не должно.

.mask {
  mix-blend-mode: multiply;
  background-image: url(/tower_spec.jpg);
}

.specular {
  background-attachment: fixed;
  background-image: linear-gradient(180deg, black 20%, #3c5e6d 35%, #f4310e, #f58308 80%, black);
}

У нас есть карта бликов и можно применить последний эффект освещения к базовому изображению. Использовать нужно режим наложения, игнорирующий чёрные и тёмные тона. Это означает, что для слоя .specular мы должны установить blend-mode: screen или mix-blend-mode: color-dodge. Работать будет любой, но хочется, чтобы блики превратились в приятный солнечный свет, поэтому воспользуемся color-dodge.

И посмотрим:

.specular {
  mix-blend-mode: color-dodge;
  background-attachment: fixed;
  background-image: linear-gradient(180deg, black 20%, #3c5e6d 35%, #f4310e, #f58308 80%, black);
}

Финальный шейдер

Эффект закончен! Вот код:

<div class="shader">
  <img src="tower.jpg" alt="Asakusa at dusk">
  <div class="shader__layer specular">
    <div class="shader__layer mask"></div>
  </div>
</div>

<style>
  .shader {
    position: relative;
    overflow: hidden;
    backface-visibility: hidden; /* to force GPU performance */
  }

  .shader__layer {
    background: black;
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    background-size: 100%;
    background-position: center;
  }

  .specular {
    mix-blend-mode: color-dodge;
    background-attachment: fixed;
    background-image: linear-gradient(180deg, black 20%, #3c5e6d 35%, #f4310e, #f58308 80%, black);
  }

  .mask {
    mix-blend-mode: multiply;
    background-image: url(/tower_spec.jpg);
  }
</style>

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

Идём дальше

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

Северное сияние

Здесь повторение градиента фона и уменьшение background-size-y приводит к ускоренному перемещению светового эффекта по экрану. При маскировке с помощью карты бликов это создаёт иллюзию ряби северного сияния по основному изображению. Размытые блики color-dodge создают не тот эффект, поэтому поменяем слой .specular на mix-blend-mode: screen, чтобы сохранить чёткость сияния.

Утечка света

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

Голограмма

Расслоение внутри маски открывает ещё больше перспектив. Что будет, если добавить ещё слой с background-attachment: fixed?

В последнем примере слой .mask имеет фоновое изображение SVG и ещё один чёрно-белый градиент под углом, противоположным зеркальному градиенту слоя .specular. Установка значения color-burn для вложенного слоя маски приводит к искажению определения SVG — получается приятная двухсторонняя голограмма. CSS — это потрясающе.

Итоги

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

Небольшая настройка — и мне удалось немного повысить производительность через backface-visibility: hidden, но первым импульсом стал хак — принудительный рендеринг на GPU с надёжной трансформацией transform: translateZ(0);. К сожалению, эта трансформация выявила особенность, о которой тоже нужно знать.

Из-за зависимости от background-attachment: fixed трансформации CSS на шейдере могут вызвать странные побочные эффекты. В Chrome это в целом работает, но, в зависимости от трансформаций, градиенты могут показаться смещёнными. Firefox же просто игнорирует фиксированное расположение — и ваши градиенты полностью статичны. Я уверен, что есть способы обойти это, но они, наверное, будут искушать ваш дух воспользоваться JavaScript.

Конечно, мы не можем добиться уровня GLSL, но для эффектов попроще этот подход — отличная альтернатива добавлению зависимостей JS в проект. Сколь бы прекрасными ни были эффекты, я думаю, сегодня это в значительной степени случайность: не нужно этого делать только потому, что вы можете.

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

А мы поможем прокачать ваши навыки или с самого начала освоить профессию, актуальную в любое время:

Выбрать другую востребованную профессию.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+4
Комментарии2

Публикации

Информация

Сайт
www.skillfactory.ru
Дата регистрации
Дата основания
Численность
501–1 000 человек
Местоположение
Россия
Представитель
Skillfactory School