Анимированные эффекты для сайта на WebGL
- Stats: 1913 1
- Author: RUDEBOX
- Category: Технології
- Comments: Комментариев нет
Сегодня мы хотим показать вам, как создать некоторые реалистичные эффекты искажения с использованием частиц шейдеров в WebGL. Идея заключается в том, чтобы добавить тонкое анимированное искажение статического изображения и какой-нибудь текст. Обратите внимание, что в данном эффекте используются пиксельные шейдера, работа которых заключается в запуске функций для каждого пикселя в видимой области. Для того чтобы реализовать задуманное, нам необходимо задать позиции для пиксельных шейдеров на нашем макете.
Следует отметить, что технология достаточно сложная и может поддерживаться не всеми браузерами. Давайте посмотрим на несколько аспектов в демонстрации, чтобы понимать, о чем мы хотим вам рассказать в сегодняшнем уроке.
Шаг 1. Мираж
Суть этого эффекта является в создании эффекта искажения. Прежде всего, давайте посмотрим на то, как мы можем сделать, для начала возьмём простое изображение, а затем немного будем его искажать.
1 2 3 4 5 6 7 8 9 |
varying vec2 position; uniform sampler2D texture; void main(){ // задаем цвет для пикселя для его позиции vec4 color=texture2D(texture,position); gl_FragColor=color; } |
Данный прием нам необходим для получения цвета пикселя на текстуре к которой он относиться в пространстве и позиционировании.
Теперь вместо того , чтобы просто получать пиксель от текущей позиции , хоть мы можем применить некоторые значения для класса position, мы будем использовать следующее правило для получения цвета с соседней позиции для пикселя:
1 2 |
float distortion=position.y*0.2; vec4 color=texture2D(texture,vec2(position.x+distortion,position.y)); |
В результате мы получаем следующее изображение:
Теперь, чтобы получить простое но интересное искажение, мы вычисляем позицию, основанную на синусоиде.
Здесь мы добавим к позиции (х) значение синусоида рассчитанного на основе позиции (у).
1 2 3 4 |
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)); |
Для того, чтобы оживить изображение, нам необходимо сделать следующее: Создать слой, который будет увеличивать каждый кадр, и использовать это значение с применением синусоидальной функции. Для создания значения для каждого кадра, мы будем использовать функцию JSrequestAnimationFrame:
1 2 3 |
(function draw(){ requestAnimationFrame(draw); }()) |
При создании данного эффекта мы должны иметь в виду, а именно: частоту обновления. Иногда число кадров в секунду, является непоследовательным и непредсказуемым. Ваше устройство может зависнуть на мгновение или медленно работать. Таким образом,мы сделали разработали метод, чтобы компенсировать и проверить, сколько времени прошло с момента последнего кадра и рассчитать когда нам необходимо выводить следующий кадр для приятного просмотра:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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)); |
Так что теперь мы можем послать необходимое время нашим шейдером для каждого кадра и использовать его для анимации по синусоиду.
1 2 3 4 5 6 7 8 |
(function draw(elapsed){ ... var location=gl.getUniformLocation(program,"time"); gl.uniform1f(location,time); ... }) |
Обратите внимание на то, что искажение применяется ко всему изображению. Один из способов сделать это только на определенных областях это использование другой текстуры в качестве подложки, так называемой карты:
Обратите внимание, что края размыты — это необходимо чтобы ослабить визуальный эффект и сохранить от искажающих элементов элементы, которые мы не хотим отображать. Затем, можно умножить величину искажения по яркости текущего пикселя карты.
1 2 |
float map=texture2D(map,position).r; vec4 color=texture2D(texture,vec2(position.x+distortion*map, position.y)); |
Шаг 2. Глубина
Глубина и эффект параллакса работают так же, чтобы получить значение для цвета с нескольких позиций (вспомните шаг первый), основанной на карте из нескольких значений. В этом случае нам потребуются значения для мыши по позициям (х) и (у).
1 2 3 4 |
document.addEventListener('mousemove',function(event){ var location=gl.getUniformLocation(program,"mouse"); gl.uniform2f(location,event.clientX/canvas.width,event.clientY/canvas.height); }) |
1 2 3 |
vec2 parallax=mouse*0.005; vec2 distortedPosition=vec2(position.x+distortion*map, position.y); vec4 color=texture2D(texture,distortedPosition+parallax); |
Теперь нам просто нужно создать карты глубины цвета. Это, будет немного другая цветовая карта, чем та, которую мы использовали раньше. Таким образом, вместо загрузки двух текстур, по одному для каждой карты, мы можем добавить обе карты на том же самом файле изображения, каждый в отдельном канале — то есть, один в красном канале и один в зеленом канале. Таким образом, мы можем сэкономить время загрузки и не загружать оперативную память.
В коде, мы их относим к соответствующим каналам:
1 2 3 4 5 6 7 |
vec4 maps=texture2D(mapsTexture,pos);</p> float depthMap=maps.r; float distortionMap=maps.g; ... vec2 distortedPosition=vec2(position.x+distortion*distortionMap, position.y); vec4 color=texture2D(texture,distortedPosition+parallax*depthMap); |
Имейте в виду, что это быстрый способ сделать эффект глубины. В априори, он должен быть тонким, иначе артефакты на изображении будут очень очевидным.
Шаг 3. Наполнение
Мы не можем реализовать содержимое HTML к canvas объекту — ни в WebGL , ни в ее 2D. Создание текста в canvas это сложно, и при загрузке растрового изображения ,текст читается достаточно плохо, и у него есть свои проблемы: ограниченное разрешение, размер файла.
Решение состоит в том, чтобы использовать SVG — мы можем сделать внешне загружаемым файлам SVG для canvas объекта, а затем использовать его в canvas в качестве текстуры. SVG файлы проще в обслуживании, легкие по сравнению с растровыми изображениями.
Это быстрый способ загрузить SVG и применить его к canvas:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 контейнера:
1 2 3 4 5 6 |
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); |
В результате мы получим достаточно привлекательное содержание нашего контейнера:
И это наш конечный результат. Мы объяснили данный эффект на первом примере и подробно объяснили, но есть намного больше возможностей, в том числе эффектов искажения для воды, вы можете увидеть их в нашей демонстрации.
Отправить ответ