Приступая к кастомным фильтрам на CSS (Custom Cascading Style Sheets Filters)
Вступление
Кастомные CSS фильтры (далее CCSSF)(ранее известные как CSS шейдеры) — это новая браузерная фича, накладывающая созданные руками визуальные эффекты на элементы HTML документа.
Кастомные фильтры являются частью Filter Effects 1.0 specification. Они дополняют другую часть этой спецификации, определяющую некоторые общие CSS фильтры, уже встроенные в браузеры (blur, sepia, contrast, grayscale).
Встроенные CSS фильтры
Встроенные CSS фильтры предлагают функционал, схожий с фильтрами в редакторах изображений — у вас сет предустановленных, повсеместно используемых эффектов, в которых вы можете только контролировать параметры.
Например, это изображение было сконвертировано в grayscale а затем был применён blur:
(можно нажать на картинки и убедиться самому, встроенные CSS фильтры должны работать в стабильной версии Chrome)
Кастомные CSS фильтры
Кастомные фильтры, с другой стороны, позволяют создавать полностью новые типы эффектов, в которых вы можете контролировать не только параметры самого эффекта, но еще и определять саму суть применяемого эффекта, используя шейдеры.
Например, эта карта, встроенная в документ, была преобразована в настоящую текстурированную сферу:
(можно нажать на картинки и убедиться самому, подробнее в пункте Как сделать так, чтобы CSS фильтры заработали)
Встроенные фильтры имеют доступ к функционалу, недоступному для кастомных. Они могут достичь большего, чем было задумано создателями браузеров изначально.
Самые заметные встроенные фильтры могут считывать пиксели с указанного DOM контента, что значит, что они могут сделать, например, blur (Смотрите в пункте Ограничения кастомных фильтров).
Что такое шейдеры?
Эффекты кастомных фильтров устанавливаются маленькими программами, назвываемыми шейдеры (shaders).
Шейдеры определяют 3D форму и вид графических элементов (шейдеры оперируют полигональными сетками).
Шейдеры работают напрямую на железе видеокарты. Они могут обрабатывать параллельно большое количество данных, что значит, что они могут быть очень быстрыми, но часто и лишними по сравнению с типичным циклом работы программ, крутящихся на CPU.
CCSSF используют GLSL как язык для написания шейдеров.
Если вы знакомы с шейдерами по программированию графики, задействующему шейдерные языки (OpenGL, OpenGL ES, WebGL, DirectX), то здесь всё похоже — шейдеры, использованные в кастомных CSS фильтрах работают так же.
Типы шейдеров
Шейдеры, используемые в CCSSF, бывают двух типов: вершинные шейдеры (Vertex Shader)и фрагментные шейдеры (Fragment Shader)(их еще называют пиксельные).
Вершинные шейдеры говорят, где что находится. Они позволяют двигать вершины сетки в 3D пространстве, деформируя и переставляя объекты.
Фрагментные шейдеры говорят как выглядят поверхности объектов. Они позволяют рисовать на объектах или изменять принадлежность существующих пикселей к внешности объектов.
В целом, чтобы иметь возможность создавать валидную GPU программу, вам нужны и те, и другие шейдеры. Тем не менее, для кастомных CSS фильтров нужен только один из этих видов, а для недостающего браузер использует default pass-through shader.
Как работают кастомные CSS фильтры?
Современные браузеры нынче сами реализовывают использование графического ускорения.
HTML страницы отрисовываются браузерами как коллекции текстурированных прямоугольников, соответствующих DOM элементам.
С кастомными CSS фильтрами вы цепляетесь за браузерный рендерный информационный поток, получая возможность менять форму и вид этих прямоугольников до того, как они отрисуются на экране.
Это похоже на то, как работают CSS 3D transforms, только заместо возможности играться с параметрами с уже заданным функционалом, вы можете запускать свой собственный код обработки DOM контента.
Контентовая сетка
Каждый DOM элемент с CCSSF будет преобразован в треугольную сетку с мозаикой, определённой юзером:
<img srс=«cs317217.userapi.com/v317217705/37a5/ghtCCVl9SeM.jpg» alt=«image»/>
Скрытый текст
Прим. переводчика — не нашёл способ вставить svg объект, поэтому сделал скриншот
По умолчанию, сетка содержит только два треугольника (необходимый минимум для создания прямоугольника).
Эта сетка получит текстуру, созданную для контента DOM элемента (то, что обычно и отрисовывается на экране), а потом к ней применятся ваши кастомные шейдеры.
Связность сеток
Сетка из треугольников может быть создана двумя путями (контролируемыми из CSS):
- прикреплённые треугольники
- откреплённые треугольники
Сетка с прикреплёнными треугольниками это один объединённый объект, где соседние треугольники имеют общие вершины. Если вы сдвинете вершину, все связанные с ней треугольники деформируются (как лист ткани). Этот вариант используется по умолчанию.
Сетка с откреплёнными треугольниками собрана из множества отдельных треугольников. Каждая вершина принадлежит только одному треугольнику. Вы можете поделить сетку на отдельные компоненты. Сетка может содержать дыры, или вообще быть полностью перемоделирована в вершинный шейдер.
Мозаика сетки и связность должны оставаться одними и теми же во всех CSS transitions.
Входные параметры шейдеров
Вершинные и фрагментовые шейдеры могут принимать на вход параметры трёх типов:
- формы (uniforms)
- атрибуты (attributes)
- отличия (varyings)
Формы являются параметрами с одним значением для всех вершин и пикселей сетки (например, цвет объекта).
Атрибуты являются индивидуальными параметрами вершин, каждая вершина сетки получает своё значение для каждого атрибута (например, позиция вершины).
Отличия являются параметрами, передаваемыми вершинами фрагментам. Они указываются для каждой вершины треугольника и их значения для точек внутри треугольника интерполирует GPU (например, освещение).
Filter Effects specification так же допускают другой тип входных данных: текстуры. Тем не менее, они еще не реализованы (и попытки использовать их тихо сломают шейдеры).
Браузеры предоставляют некоторые дефолтные встроенные параметры, созданные и инициализированные для все элементов, к которым применяются кастомные CSS фильтры.
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_meshCoord;
attribute vec3 a_triangleCoord;
Встроенные атрибуты позволяют идентифицировать и находить отдельные вершины и треугольники в сетке.
uniform mat4 u_projectionMatrix;
uniform vec2 u_textureSize;
uniform vec4 u_meshBox;
uniform vec2 u_tileSize;
uniform vec2 u_meshSize;
Встроенные формы предоставляют информацию про данные DOM элементов, общие для всей сетки.
varying vec2 v_texCoord;
Встроенные отличия предоставляют координаты текстуры в случае, если эффект использует дефолтные шейдеры.
Точные определения этих параметров можно найти в специализации Filter Effects (атрибуты, формы, отличия).
Предостережение: на данный момент, не все встроенные параметры из этой специализации реализованы.
Например, отсутствуют формы u_textureSize и u_meshSize. Вы можете решить эту проблему, самолично определив их в CSS.
Так же, отличие v_texCoord пока не работает, поэтому вам надо будет создать своё отличие для передачи координат текстуры из атрибута a_texCoord в вершинный шейдер.
Следить за прогрессом внедрения кастомных CSS фильтров можно в этом издании WebKit «master» issue.
В дополнение к встроенным параметрам вы так же можете описать свои формы для эффекта через CSS стили.
.shaded {
-webkit-filter: custom(url(distort.vs) mix(url(tint.fs) normal source-atop),
distortAmount 0.5, lightVector 1.0 1.0 0.0);
}
// Шейдер (вершинный или фрагментовый)
...
uniform float distortAmount;
uniform vec3 lightVector;
...
Всё это позволяет вам контролировать эффекты извне. В частности, значения для форм, описанные в CSS будут интерполированы при использовании CSS transitions.
Предостережение: количество доступных слотов под формы и отличия органичено и зависит от конкретных GPU (то же самое относится и к атрибутам, но для CSS фильтров их использовать не получится).
Здесь можно увидеть, сколько параметров шейдеров доступно на вашей системе для WebGL (указанные числа должны быть аналогичными для CCSSF; часть слотов закреплена за встроенными параметрами).
Как использовать кастомные CSS фильтры на моём сайте?
CCSSF применяются так же, как и прочие CSS стили, посредством style. Выглядит это так:
<style>
.shader {
-webkit-filter:
custom(url(shaders/crumple.vs) mix(url(shaders/crumple.fs) normal source-atop),
50 50, amount 0, strength 0.2, lightIntensity 1.05);
}
</style>
Или так, если вы используете только фрагментовый шейдер:
<style>
.shader {
-webkit-filter:
custom(none mix(url(shaders/tint.fs) normal source-atop), amount 0);
}
</style>
Аналогично использовать none заместо фрагментного шейдера не получится (пока). В такой ситуации решением является подстановка пустого фрагментного шейдера.
Вы даёте браузеру ссылку на исходники GLSL шейдеров и указываете их параметры через CSS стили. Браузер позаботится о том, чтобы скомпилировать шейдеры в бинарники и применить их к HTML контенту.
Когда у вас уже есть шейдеры, применить их к HTML легко — вы просто работаете с ними, как с обычными CSS стилями.
<div class="shader">
Можно использовать CSS transitions с кастомными CSS фильтрами, например, чтобы динамически менять эффект при наведениии на элемент страницы.
-webkit-transition: -webkit-filter ease-in-out 1s;
CCSSF параметры будут интерполироваться так же, как встроенные CSS свойства.
Как получить работающие CCSSF?
Предостережение: эта технология очень новая и может быть грубо реализована. Детали могут меняться, какие-то фичи могут не работать, а в имплементации могут быть баги.
Этот пост отражает состояние дел на сентябрь 2012 года. Если с тех пор что-то работает не так, как описано, полезно будет погуглить, чтобы быть в курсе точныого синтаксиса и поведения.
На данный момент, кастомные CSS фильтры работают в текущем билде Chrome Canary (точная версия на момент создания записи — 24.0.1278.0).
Скачать Chrome Canary можно здесь:
tools.google.com/dlpage/chromesxs
CCSSF должны работать в Windows и OSX (Я использую Windows 7).
Даже если ваша ОС и браузер поддерживают их, всё может упереться в старый или слабый GPU или в неподходящие к нему драйвера (Системные требования примерно равны указанным здесь — WebGL compatibility check)
Кастомные CSS фильтры не включены по умолчанию, поэтому Chrome надо запускать из консоли с таким ключом:
chrome --enable-css-shaders
Простой способ получить включенные CSS шейдеры в Chrome на Windows — создать ярлык на Chrome Canary и добавить параметр --enable-css-shaders в поле «target».
Альтернатива — сделать батник с указанной выше командой.
В новых версиях Chrome Canary в интерфейсе есть специальный флаг, в специальной вкладке «флаги» (Искать «Enable CSS Shaders»):
chrome://flags/
Как убедиться, что CCSSF работают?
Смотрите этот пример.
Если вы видите сферические планеты, поздравляю, они работают ;).
Если вы видите только прямоугольные картинки, значит, что-то пошло не так ;S.
Обратите внимание, что некоторые из примеров найдены рандомно в интернете и могут со временем сломаться. Например, эффекты, работающие в ранних кастомных билдах Chromium от Мая 2012 года, не работают в последних Chrome Canary.
Предостережение: сейчас реализация почему-то не стабильная. Может случиться так, что в один момент CCSSF работают, а в другой — уже ломаются, даже во время перезагрузки страницы или циклического применения transitions на одной и той же странице.
В таких случаях:
- перезагрузите страницу
- закройте таб и откройте страницу в новом табе
- перезапустите браузер
Шейдеры, сломавшиеся в одном табе, могут убить шейдеры во всех остальных табах (и шейдеры будут оставаться сломанными, даже если перезагружать страницы).
Во время первой загрузки страницы шейдеры могут сработать и на частично отрендеренные DOM элементы. В этом случае закройте таб и откройте страницу снова, reload не поможет.
Ограничения кастомных фильтров
Доступ к контенту
Шейдеры, используемые в CCSSF никак не могут читать пиксели текстур DOM контента и еще они не могут напрямую рисовать пиксели на экране.
Эти ограничения появились как реакция на timing attacks, когда разбойничий 3rd party shader, встроенный в сайт, мог быть использован для считывания контента сайта (в зависимости от источника цвета пикселя применялся разный код шейдера).
Единственный способ, которым вы можете взаимодействовать с пикселями DOM элемента — переходить к вашим подсчитанным цветам от оригинальных, используя следующие параметры встроенные фрагментных шейдеров css_ColorMatrix, css_MixColor
Предостережение: спецификация говорит, что вы так же можете использовать gl_FragColor для создания значений сплошных цветов.
Пока что это не работает.
WebGL решает эту проблему другим путём, через предостав доступа только к «чистому» контенту (созданному на том же сервере или напрямую разрешённому по cross-domain access для 3rd party серверов).
Отрицательная сторона шейдерного подхода в том, что он не совместим с множеством интересных видов приложений, а положительная — что CSS шейдеры можно применять к любом контенту.
Размер сетки
Еще одно ограничение, по крайней мере, в текущей реализации есть фиксированное количество треугольников на одну мозаичную сетку.
У вас не может быть сетки больше, чем на 20.000 с лишним треугольников. Поэтому рекоммендуется рендерить, используя индексированные треугольники, с 16-битными номерами, ограничивая количество вершин числом 65.536.
Как создавать собственные CSS эффекты?
Всё, что вам нужно — браузер и текстовый редактор. Шейдеры, использванные в CCSSF являются обычными текстовыми файлами.
Воркфлоу такой же, как при вёрстке HTML + CSS: делаете изменения, обновляете страницу в браузере и смотрите, получилось или нет.
Есть тулза от Adobe, называется CSS FilterLab, она позволяет проще редактировать параметры эффектов.
Предостережение: компиляция шейдера в бинарник относительно медленная операция. Если у вас много шейдеров, они могут заметно замедлить загрузку страницы.
Для WebGL в Chrome было реализовано кэширование уже использованных бинарников шейдеров, это может помочь.
Как выглядят шейдеры, используемые в кастомных CSS фильтрах?
Простой вершинный шейдер выглядит похожим на это:
precision mediump float;
attribute vec4 a_position;
uniform mat4 u_projectionMatrix;
void main() {
gl_Position = u_projectionMatrix * a_position;
}
Простой фрагментный шейдер похож на это:
precision mediump float;
void main() {
float r = 1.0;
float g = 1.0;
float b = 1.0;
float a = 1.0;
css_ColorMatrix = mat4( r, 0.0, 0.0, 0.0,
0.0, g, 0.0, 0.0,
0.0, 0.0, b, 0.0,
0.0, 0.0, 0.0, a );
}
Запись precision не имеет эффекта на десктопном OpenGL, но работает на мобильных устройствах, использующих OpenGL ES. Тем не менее, они должны быть указаны, чтобы текущая реализация заработала.
К HTML контенту шейдеры применяются так:
<style>
.shader {
-webkit-filter: custom(url(simple.vs) mix(url(simple.fs) normal source-atop), 1 1)
}
</style>
<body>
<div class="shader"> Hello world! </div>
<body>
Это минимальный CSS стиль, использующий и фрагментный и вершинный шейдеры, применённые на сетку с простой мозаикой с двумя прикреплёнными треугольниками (1 столбец x 1 ряд).
Как создать кастомный эффект?
Теперь посмотрим как создать немного упрощённую версию сферического эффекта, похожую на использованную здесь.
Мы начнём с ровной текстуры с DOM контентом (первая картинка). Мы хотим обернуть её в сферу и приложить тени, как если бы это был 3D объект, освещённый прямым светом (вторая картинка).
Смотрите здесь рабочий пример.
Деформация сетки
Все деформации сетки происходят в вершинных шейдерах.
Сначала нам надо найти способ как обернуть прямоугольную плоскость в форму сферы, а тень наложим потом.
Мы начнём с простой прямоугольной сетки, лежащей в 2D пространстве, с равномерно распределёнными позициями вершин. Нам нужно подобрать какой-то маппинг этих 2D позиций в 3D сферу.
Мы можем получить координаты вершин изначальной плоскости через предоставляемый браузером встроенный атрибут a_position. Нам нужно декларировать переменную, прежде, чем можно будет использовать её:
attribute vec4 a_position;
Мы запомним этот атрибут в локальной переменной, которую будем потом модифицировать (потому что атрибуты являются read-only):
vec4 position = a_position;
Распостранённой операцией в компютерной графика является текстурирование посредством a href=«en.wikipedia.org/wiki/UV_mapping»>UV mapping, когда прямоугольная картина оборачивается вокруг сетки, используя 2D координаты текстуры. Звучит очень похоже на то, что нам нужно.
Один из встроенных атрибутов сетки, предоставляемый браузером — двухкомпонентный вектор a_texCoord с текстурными координатами вершины.
attribute vec2 a_texCoord;
Текстурные координаты называются U и V, они находятся в промежутке 0… 1 для каждой оси и мапят вершины сетки по длине и высоте картинки.
Чтобы получить координаты X, Y, Z, соответствующие нашим U и V координатам, мы будем использовать трансформацию между сферической системой координат (её ещё называют полярной) и прямоугольной системой координат:
x = r * sin( θ ) * cos( φ )
y = r * sin( θ ) * sin( φ )
z = r * cos( θ )
Сферическая система координат использует следующие координаты:
- радиус r
- зенит (inclination) θ (промежуток [0… π])
- азимут (azimuth) φ (промежуток [0… 2π])
Позволим пользователю определять радиус через CSS форму:
uniform float sphereRadius;
И будем мапить U и V координаты в азимут и зенит:
vec3 computeSpherePosition( vec2 uv, float r ) {
vec3 p;
float fi = uv.x * PI * 2.0;
float th = uv.y * PI;
p.x = r * sin( th ) * cos( fi );
p.y = r * sin( th ) * sin( fi );
p.z = r * cos( th );
}
vec3 sphere = computeSpherePosition( a_texCoord, sphereRadius );
Теперь мы можем переходить между оригинальной плоской позицией и новой сферической позицией, которую мы только что посчитали, используя встроенную в GLSL функцию (линейной интерполяции между двумя значениями).
uniform float amount;
Мы будем использовать предоставленные пользователем параметры формы чтобы контролировать переход (параметр формы из CSS может быть интерполирован браузером, используя CSS transitions).
position.xyz = mix( position.xyz, sphere, amount );
Наконец, мы можем заменить вершины сетки, записывая в встроенную GLSL переменную, трансформирующую нашу посчитанную позицию по предоставленной браузером матричной форме gl_Position:
gl_Position = u_projectionMatrix * position;
Затенение поверхности
Подсчёт затенения делается объединением вершинных и фрагментных шейдеров.
Затенение: вершинные шейдеры
В вершинном шейдере мы будем подсчитывать освещение для каждой вершины и передавать это как отличие в фрагментный шейдер, где мы будем использовать его, чтобы изменить цвет DOM текстуры.
Для затенения мы будем использовать простую модель Ламбертовского рассеивания.
Свет отражения считается как скалярное произведение нормального вектора поверхности и нормализованного вектора направления света.
Нам понадобится позиция света и нормаль поверхности для каждой вершины.
Мы позволим пользователю определять позицию источника света как трёхкомпонентную векторную форму lightPosition, передоваемую через CSS:
uniform vec3 lightPosition;
Мы будем нормализовывать используая встроенную в GLSL функцию normalize:
vec3 lightPositionNormalized = normalize( lightPosition );
Далее нам надо посчитать нормаль для плоской и сферической поверхности.
Нормаль поверхности обычно считается на стороне CPU, так как вам нужно работать со связной сеткой и иметь доступ ко всем вершинам треугольников одновременно (шейдер видит за раз только одн вершину).
Тем не менее, здесь мы имеем дело с простыми геометрическими формами, поэтому мы можем посчитать нормали аналитически.
Для нормали плоскости мы немного схитрим: правильная нормаль плоскости должна быть направлена от монитора, перпендикулярно к плоскости XY.
Но такая нормаль даст нам неизменённый DOM элемент, местами немного затенённый, и это будет неестественно на фоне остальных элементов страницы, которые не имеют наложенных шейдеров.
Поэтому мы используем вектор, направленный к источнику света как нормаль поверхности.
vec3 planeNormal = lightPositionNormalized;
Это позволит недеформированному элементу всегда быть полностью освещённым.
Для нормали сферы мы будем использовать простую аналитическую формулу. Нормаль сферы на поверхности — это нормализованные векторы из центра сферы к её поверхности:
vec3 sphereNormal = normalize( position.xyz );
Чтобы получить адекватную нормаль для нашего переходного состояния, мы будем просто переходить от нормали плоскости к нормали сферы и нормализировать этот переходный вектор:
vec3 normal = normalize( mix( planeNormal, sphereNormal, amount ) );
Наконец, мы можем посчитать освещение соответственно Ламбертовской формуле, используя встроенную в GLSL функцию dot, и зафиксировать отрицательные занчения формулой max:
float light = max( dot( normal, lightPositionNormalized ), 0.0 );
Последним действием векторного шейдера мы передадим интенсивность света фрагментному шейдеру через отличие:
varying float v_light;
v_light = light;
Затенение: фрагментный шейдер
Фрагментный шейдер будет очень простой, всё тяжёлое было осилено в вершинном шейдере.
Возьмём интенсивность света из отличия:
varying float v_light;
И используем его, чтобы модулировать цветовые коэффиценты (прозрачность менять не будем):
float r, g, b;
r = g = b = v_light;
И используем их чтобы получить матрицу перехода css_ColorMatrix:
css_ColorMatrix = mat4( r, 0.0, 0.0, 0.0,
0.0, g, 0.0, 0.0,
0.0, 0.0, b, 0.0,
0.0, 0.0, 0.0, 1.0 );
В нашем случае, получится цвет, эквивалетный этому:
gl_FragColor = vec4( r, g, b, 1.0 ) * sourceColor;
Итоговый вершинный шейдер
Скачать
Скрытый текст
precision mediump float;
// Встроенные атрибуты
attribute vec4 a_position;
attribute vec2 a_texCoord;
// Встроенные формы
uniform mat4 u_projectionMatrix;
// Формы, переданные через CSS
uniform float amount;
uniform float sphereRadius;
uniform vec3 lightPosition;
// Отличия
varying float v_light;
// Константы
const float PI = 3.1415;
// Создание perspective matrix
vec3 computeSpherePosition( vec2 uv, float r ) {
vec3 p;
float fi = uv.x * PI * 2.0;
float th = uv.y * PI;
p.x = r * sin( th ) * cos( fi );
p.y = r * sin( th ) * sin( fi );
p.z = r * cos( th );
return p;
}
// Main
void main() {
vec4 position = a_position;
// Мапим плоскость в сферу, используя UV координаты
vec3 sphere = computeSpherePosition( a_texCoord, sphereRadius );
// Переход от плоскости к сфере
position.xyz = mix( position.xyz, sphere, amount );
// Зададим позицию вершины
gl_Position = u_projectionMatrix * position;
// Посчитаем свет
vec3 lightPositionNormalized = normalize( lightPosition );
vec3 planeNormal = lightPositionNormalized;
vec3 sphereNormal = normalize( position.xyz );
vec3 normal = normalize( mix( planeNormal, sphereNormal, amount ) );
float light = max( dot( normal, lightPositionNormalized ), 0.0 );
// Передадим в отличие
v_light = light;
}
// Встроенные атрибуты
attribute vec4 a_position;
attribute vec2 a_texCoord;
// Встроенные формы
uniform mat4 u_projectionMatrix;
// Формы, переданные через CSS
uniform float amount;
uniform float sphereRadius;
uniform vec3 lightPosition;
// Отличия
varying float v_light;
// Константы
const float PI = 3.1415;
// Создание perspective matrix
vec3 computeSpherePosition( vec2 uv, float r ) {
vec3 p;
float fi = uv.x * PI * 2.0;
float th = uv.y * PI;
p.x = r * sin( th ) * cos( fi );
p.y = r * sin( th ) * sin( fi );
p.z = r * cos( th );
return p;
}
// Main
void main() {
vec4 position = a_position;
// Мапим плоскость в сферу, используя UV координаты
vec3 sphere = computeSpherePosition( a_texCoord, sphereRadius );
// Переход от плоскости к сфере
position.xyz = mix( position.xyz, sphere, amount );
// Зададим позицию вершины
gl_Position = u_projectionMatrix * position;
// Посчитаем свет
vec3 lightPositionNormalized = normalize( lightPosition );
vec3 planeNormal = lightPositionNormalized;
vec3 sphereNormal = normalize( position.xyz );
vec3 normal = normalize( mix( planeNormal, sphereNormal, amount ) );
float light = max( dot( normal, lightPositionNormalized ), 0.0 );
// Передадим в отличие
v_light = light;
}
Итоговый фрагментный шейдер
Скачать
Скрытый текст
precision mediump float;
varying float v_light;
void main() {
float r, g, b;
r = g = b = v_light;
css_ColorMatrix = mat4( r, 0.0, 0.0, 0.0,
0.0, g, 0.0, 0.0,
0.0, 0.0, b, 0.0,
0.0, 0.0, 0.0, 1.0 );
}
varying float v_light;
void main() {
float r, g, b;
r = g = b = v_light;
css_ColorMatrix = mat4( r, 0.0, 0.0, 0.0,
0.0, g, 0.0, 0.0,
0.0, 0.0, b, 0.0,
0.0, 0.0, 0.0, 1.0 );
}
CSS стиль
Скачать
Скрытый текст
.shader {
-webkit-filter: custom(url(sphere.vs) mix(url(sphere.fs) normal source-atop),
16 32, amount 1, sphereRadius 0.35, lightPosition 0.0 0.0 1.0);
-webkit-transition: -webkit-filter ease-in-out 1s;
}
.shader:hover {
-webkit-filter: custom(url(sphere.vs) mix(url(sphere.fs) normal source-atop),
16 32, amount 0, sphereRadius 0.35, lightPosition 0.0 0.0 1.0);
}
-webkit-filter: custom(url(sphere.vs) mix(url(sphere.fs) normal source-atop),
16 32, amount 1, sphereRadius 0.35, lightPosition 0.0 0.0 1.0);
-webkit-transition: -webkit-filter ease-in-out 1s;
}
.shader:hover {
-webkit-filter: custom(url(sphere.vs) mix(url(sphere.fs) normal source-atop),
16 32, amount 0, sphereRadius 0.35, lightPosition 0.0 0.0 1.0);
}
Еще немного примеров кастомных эффектов
- Burn shader
- Dissolve shader
- Crumple shader
- Dots shader
- Flip shader (variant 1)
- Flip shader (variant 2)
- Sphere shader (planets)
- Sphere shader (map)
Отладка шейдеров
Отладка шейдеров, используемых в CCSSF — дело сложное. По крайней мере, сейчас нет никакого error лога в консоль Chrome (в отличие от WebGL, где можно получить результаты компиляции шейдера и, возможно, найти ошибку или опечатку).
На маке вы можете получить error output отображаемый в окне консоли, из которой вы запустили Chrome, но в Windows в консоль такого не пишется.
Возможное решение — использовать GL shader validation plugin для Sublime text editor, созданного недавно ребятами @aerotwist и @brendankenny.
Профилирование шейдеров
К сожалению, так же как и в WebGL нет никакого способа увидеть, что происходит на стороне GPU :S.
Всё же, есть пара хитростей, которые можно использовать, чтобы получить немного внутренней информации о работе шейдеров.
Например, если вы хотите узнать, привязано ли действие вашего эффекта привязанным к фрагментному шейдеру, попробуйте изменить размер DOM элемента, чтобы увидеть, стало ли работать лучше на уменьшенных элементах.
Пытайтесь избегать эффектов, применяемых к полноэкранным элементам. Это будет непроизводительно и может оказаться узким местом при построении страницы.
Если хотите увидеть, привязано ли действие вашего эффекта к вершинным шейдерам, попробуйте его на сетке с другой мозаикой.
Помните, что если применять эффект к множеству элементов с большими мозаиками на одной и той же странице, можно очень быстро нарастить общий счётчик треугольников.
Полезные ссылки
- Filter Effects specification
- GLSL specification
- Compositing and Blending specification
- CSS transitions specification
- CSS transforms specification
- Umbrella WebKit issue tracking CSS Shaders implementation progress
- Adobe custom CSS filter effects examples
- Introducing CSS shaders: Cinematic effects for the web
- WebGL quick reference card (включает полезный справочник GLSL)
- GLSL Sandbox
- CSS Shaders implemented with WebGL
Материал
- Jupiter close-up by Voyager 1
- Jupiter texture
- Earth from Google Maps
- Diagrams from Filter Effects spec
Скрытый текст
P.S. Перевёл по просьбе Keyten сам ничего вышеописанного не пробовал =)