Пособие по созданию фрагментных шейдеров в WebGL для создания эффекта искажения анимированных эффектов горячего воздуха на изображениях и тексте
Сегодня мы хотим показать вам, как создать реалистичные эффекты искажения горячего воздуха, используя фрагментарные шейдеры в WebGL. Идея состоит в том, чтобы добавить тонкое анимированное искажение к статическому изображению и некоторому тексту.
Внимание: демоверсии используют некоторые современные веб-технологии, которые могут не работать в старых браузерах. Обратите внимание, что текущий ZIP-файл - это скомпилированная сборка этого проекта. Работает только на сервере!Для полноценного описания, мы выберем одну из демонстраций, чтобы дать вам обзор того, как работает анимация WebGL. Со временем мы будем выкладывать больше примеров с использованием WebGL. Сейчас же мы разберем конкретный пример из нашего демо. Для более глубокого изучения перейдите на страницу WebGL Fundamentals или Learning WebGL. Если хорошо изучить данную технологию можно делать даже игры, или найти более применение этим знаниям! Здесь очень много примеров!
Давайте рассмотрим несколько аспектов демонстрации, чтобы понять, как реализован данный пример.
Искажение
Ядром этого эффекта является "искажение теплового тумана". Для начала давайте посмотрим как мы можем нарисовать этот образ и потом присвоить ему искажение. Там мы можем получить цвет пикселя в текстуре подложки, т.к сам эффект накладывается поверх картинки и все исходные данные цвета берутся с самой картинки
varying vec2 position; uniform sampler2D texture; void main(){ // Получаем цвет пикселя какртинки в текущей позиции vec4 color=texture2D(texture,position); gl_FragColor=color; }

Так же мы можем задать смещение координат, откуда следует взять цвет пикселя подложки.
float distortion=position.y*0.2; vec4 color=texture2D(texture,vec2(position.x+distortion,position.y));

Теперь мы получаем волновое искажение основываясь на синусоидальной волне
Здесь мы добавим в положение X синусовую кривую, основанную на Y-позиции.
// Возможно отрегулировать величины на свое усмотрение, меняя скорость размер и частоту эффекта float frequency=100.0; float amplitude=0.003; float distortion=sin(position.y*frequency)*amplitude; vec4 color=texture2D(texture,vec2(position.x+distortion, position.y));

Чтобы оживить эффект, мы можем сделать следующее: необходимо задать шейдеру значение для зацикливания эффекта. Оно увеличивает каждый фрейм, и зациклит это значение в синусной функции.
Чтобы задать значение для каждого фрейма, мы можем использовать функцию requestAnimationFrame:
(function draw(){ requestAnimationFrame(draw); }())
Теперь давайте подумаем об оптимизации. Ведь, как и в играх на производительность может повлиять аппаратная часть устройства. Эффект может зависнуть или наоборот ускориться, как если бы запустили старую игру на современном устройстве. Зададим необходимые параметры, которые будут одинаковы вне зависимости от устройства.
var fps=60; // частота кадров в секунду var frameDuration=1000/fps; // за сколько милисекунд должна происходить отрисовка объекта var time=0; // значение времени, которое будет отправлено шейдерам var lastTime=0; // время отрисовки последнего кадра (function draw(elapsed){ var delta=elapsed-lastTime; lastTime=elapsed; var step=delta/frameDuration; time+=step; ball.x += 20*step; requestAnimationFrame(draw); }(0));
Итак, теперь мы можем отправить значение времени в наш шейдер для каждого кадра и использовать его для анимации синусоидальной волны.
Пишем в JS файле(function draw(elapsed){ var location=gl.getUniformLocation(program,"time"); gl.uniform1f(location,time); })Пишем в шейдере
float speed=0.03; float distortion=sin(position.y*frequency+time*speed)*amplitude; vec4 color=texture2D(texture,vec2(position.x+distortion, position.y));В результате получаем
Обратите внимание, что искажение применяется ко всему изображению. Один из способов сделать это только в определенных областях - использовать другую текстуру в качестве карты и рисовать области ярче или темнее пропорционально тому, насколько сильным или слабым мы хотим искажение.
Обратите внимание, что края размыты - это ослабляет эффект и не искажает то, что мы не хотим. Затем мы можем умножить величину искажения на яркость текущего пикселя карты.
float map=texture2D(map,position).r; vec4 color=texture2D(texture,vec2(position.x+distortion*map, position.y));
Эффект глубины, или 3D эффект
Эффект глубины + параллакс работает примерно так же - получаем значение цвета из другой позиции, основанной на карте и некоторых значениях. В этом случае значения представляют собой координаты X и Y.
... document.addEventListener('mousemove',function(event){ var location=gl.getUniformLocation(program,"mouse"); gl.uniform2f(location,event.clientX/canvas.width,event.clientY/canvas.height); })
... vec2 parallax=mouse*0.005; vec2 distortedPosition=vec2(position.x+distortion*map, position.y); vec4 color=texture2D(texture,distortedPosition+parallax);
Теперь нам нужна карта глубины. В этот раз, это будет другая карта, чем та, которую мы использовали раньше. Таким образом, вместо загрузки двух текстур, по одному для каждой карты, мы можем добавить обе карты в один и тот же файл изображения, каждый в отдельном канале, то есть один в красном канале и один в зеленом канале. Таким образом, мы можем сэкономить время загрузки и системную память.
В коде мы ссылаемся на каждый из цветов по их соответствующим каналам. Нужно иметь хотя бы основы знаний дизайна что бы это понять.
... vec4 maps=texture2D(mapsTexture,pos); float depthMap=maps.r; float distortionMap=maps.g; ... vec2 distortedPosition=vec2(position.x+distortion*distortionMap, position.y); vec4 color=texture2D(texture,distortedPosition+parallax*depthMap);
Имейте в виду, что это быстрый и грязный способ сделать эффект глубины. Возможны появления артефактов.
Контент
Мы показали, что с изображением можно делать все что угодно, но что же делать с текстом?
Мы не можем поместить текст картинкой. Создание текста картинкой дальнейшем может доставить немало хлопот, и при загрузке растрового изображения содержащего текст, будет тянуть за собой следующие ограничения: ограниченное разрешение, дополнительный размер файла, сложнее адаптировать и т. Д.
Для прорисовки текста мы будем использовать SVG. Мы можем нарисовать абсолютно любой объект которому зададим свойства уже в css. Естественно, картинка нарисованная кодом будет загружаться моментально, наряду просто с изображением, которое находится в файле. Да и работать с таким изображением проще.
Это быстрый способ загрузить SVG и нарисовать его как маску:
var canvas=document.createElement('canvas'); loadSVG('file.svg',canvas); function loadSVG(file,canvas){ var svg=new Image(); svg.addEventListener('load',function(){ var ctx=canvas.getContext('2d'); canvas.width=svg.width; canvas.height=svg.height; ctx.drawImage(svg,0,0); }) svg.src=file; }
Теперь мы можем использовать эту маску так же, как и любую другую текстуру.
Трюк для облегчения позиционирования текстуры, которую мы только что создали в контейнере WebGL, состоит в том, чтобы создать и разместить маску точно так же, как любой другой элемент - то есть с использованием HTML и CSS - и задать ему окончательную позицию с помощью getBoundingClientRect, а затем отправить его на обработку в шейдер.
var title=document.querySelector('canvas'); var bounds=title.getBoundingClientRect(); var location=gl.getUniformLocation(program,"contentPosition"); gl.uniform2f(location,bounds.left,bounds.top); var location=gl.getUniformLocation(program,"contentSize"); gl.uniform2f(location,bounds.width,bounds.height);

И вот наш конечный результат! мы взяли его и подробно расписали само построение такой живой картинки. Есть много вариантов применения данной инструкции, как например - живая вода, на последнем примере. Надеемся данная инструкция не покажется слишком сложной и вы сможете расширить свои знания по данному направлению. Пробуйте и продвигайте свои идеи! Напишите в комментария что вы думаете об этом всем.
В своих примерах мы не показываем как подключать те или иные файлы, подразумевается, что вы знакомы с основами html
cat345
Хороший урок, хорошая технология, спасибо! Единственное у меня начинает комп сильно шуметь и охлаждать систему) видимо данная технология хорошенько так нагружает комп, на смартфоне лучше естественно отключать, ибо будет лагать.