Canvas / Создание эффекта (маски) миража для изображения

87
21 сентября 2019, 23:40

Подскажите, есть ли легкий способ создать эффекта миража(марева) на технологиях canvas / svg для изображения. В приведенном примере используется перемещение текстур, можно ли проще, к примеру для анимации эффекта огня?

Answer 1

Выбираем картинку с объемными объектами на дальнем плане, чтобы эффект миража лучше проявился.

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

Применяем к ней два фильтра feTurbulenceи feDisplacementMap Подробно о фильтрах, на русском, с многочисленными примерами можно почитать здесь

За основу создания эффекта миража в фильтре отвечают несколько параметров, но основной параметр это baseFrequency

Частота генерации шума, которая представляется в виде двух чисел, определяющих горизонтальное и вертикальное направления. Если задается одно число, то оно по умолчанию принимается для обоих направлений.

Вот этот параметр фильтра и будем анимировать с помощью скрипта Остальные параметры фильтра можете регулировать по своему вкусу.

scale="6" - увеличивает размер волн

seed="53" - отвечает за слоистость структуры

frames += 0.3 - увеличение прироста фраймов, увеличивает скорость волновых эффектов

var img = document.querySelector("#displacementFilter feTurbulence"); 
var frames = 0; 
var rad = Math.PI / 180; 
 
function AnimateBaseFrequency() { 
  //baseFrequency="0.01 .1" 
  bfx = 0.01; 
  bfy = 0.1; 
  frames += 0.8 
  bfx += 0.01 * Math.cos(frames * rad); 
  bfy += 0.01 * Math.sin(frames * rad); 
 
  bf = bfx.toString() + ' ' + bfy.toString(); 
  img.setAttributeNS(null, 'baseFrequency', bf); 
 
  window.requestAnimationFrame(AnimateBaseFrequency); 
} 
 
window.requestAnimationFrame(AnimateBaseFrequency);
.container { 
width:50%; 
height:50%; 
}
<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
        viewBox="0 0 673 455" preserveAspectRatio="none" >   
	    
 <filter id="displacementFilter"> 
    <feTurbulence type="turbulence" baseFrequency="0.1 .1" 
        numOctaves="1" result="turbulence" seed="53"/> 
    <feDisplacementMap in2="turbulence" in="SourceGraphic" 
        scale="6" xChannelSelector="R" yChannelSelector="B"/> 
  </filter>	    
 
<image id="blueMoon"  y="90" xlink:href="https://i.stack.imgur.com/ke2Ih.png" width="100%" height="100%" /> 
 
   <use xlink:href ="#blueMoon"   transform="translate(0, 0) scale(1 1) " filter="url(#displacementFilter)"/> 
</svg>	  
</div> 

Пламя

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

 var img = document.querySelector("#displacementFilter feTurbulence"); 
var frames = 0; 
var rad = Math.PI / 180; 
 
function AnimateBaseFrequency() { 
  //baseFrequency="0.01 .1" 
  bfx = 0.1; 
  bfy = 0.1; 
  frames += .4 
  bfx += 0.05 * Math.cos(frames * rad); 
  bfy += 0.1 * Math.sin(frames * rad); 
 
  bf = bfx.toString() + ' ' + bfy.toString(); 
  img.setAttributeNS(null, 'baseFrequency', bf); 
 
  window.requestAnimationFrame(AnimateBaseFrequency); 
} 
 
window.requestAnimationFrame(AnimateBaseFrequency);
.container { 
width:50%; 
height:50%; 
}
<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
        viewBox="0 0 670 526" preserveAspectRatio="none" >   
	    
 <filter id="displacementFilter"> 
    <feTurbulence type="turbulence" baseFrequency="0.01 0.1" 
        numOctaves="1" result="turbulence" seed="3"/> 
    <feDisplacementMap in2="turbulence" in="SourceGraphic" 
        scale="10" xChannelSelector="R" yChannelSelector="B"/> 
  </filter>	    
 
<image id="blueMoon"  y="90" xlink:href="https://i.stack.imgur.com/Pqhln.jpg"  width="80%" height="100%"  /> 
 
   <use xlink:href ="#blueMoon"    filter="url(#displacementFilter)"/> 
</svg>	  
</div> 

Огонь и вода

При обработке изображений необходимо уменьшить их размеры в любом растровом редакторе. Лучше в фотошопе, у которого есть недокументированная возможность уменьшать поэтапно на 10% размеры изображения без заметной потери качества. таким образом можно получить из изображения весом 2.2 Mb картинку весом 100kb В шапке svg файла необходимо выставить размер viewBox с атрибутами равными размеру используемой картинки. Например в этом примере - viewBox="0 0 670 526"

var img = document.querySelector("#displacementFilter feTurbulence"); 
var frames = 0; 
var rad = Math.PI / 180; 
 
function AnimateBaseFrequency() { 
  //baseFrequency="0.01 .1" 
  bfx = 0.1; 
  bfy = 0.1; 
  frames += .31 
  bfx += 0.1 * Math.cos(frames * rad); 
  bfy += 0.1 * Math.sin(frames * rad); 
 
  bf = bfx.toString() + ' ' + bfy.toString(); 
  img.setAttributeNS(null, 'baseFrequency', bf); 
 
  window.requestAnimationFrame(AnimateBaseFrequency); 
} 
 
window.requestAnimationFrame(AnimateBaseFrequency);
.container { 
width:50%; 
height:50%;
<div class="container"> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"  
    xmlns:xlink="http://www.w3.org/1999/xlink" 
        viewBox="0 0 680 409" preserveAspectRatio="none" >   
	    
 <filter id="displacementFilter"> 
    <feTurbulence type="turbulence" baseFrequency="0.1 .1" 
        numOctaves="1" result="turbulence" seed="53"/> 
    <feDisplacementMap in2="turbulence" in="SourceGraphic" 
        scale="5" xChannelSelector="R" yChannelSelector="B"/> 
  </filter>	    
 
<image id="blueMoon"  y="90" xlink:href="https://i.stack.imgur.com/7ptDK.png" width="100%" height="100%" /> 
 
   <use xlink:href ="#blueMoon"     filter="url(#displacementFilter)"/> 
</svg>	  
</div>

Answer 2

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

Алгоритм:

  // запоминаем текущую текстурную координату
vec2 p_d = uv; 
  // прибавляем dt (сколько прошло времени с прошлого кадра)
p_d.y += t * 0.1; 
  // берем для полученного значения текстурных координат сэмпл 2d шума 
  // (тут могла быть текстура вместо вызова математического метода, и это будет быстрее)
vec2 offset = vec2(noise(p_d * 22.));     
  // плавно уменьшаем это значения до 0 (0 сверху)
offset *= uv.y * 0.01; 
  // берем цвет оригинальным координатам + смещение
return sample(uv.xy + offset); 

let t = new Date().getTime(); 
let url = "https://webgl2fundamentals.org/webgl/resources/images/computer-history-museum/pos-z.jpg"; 
 
let filter = webglFilter(url, ` 
 
  uniform float t; 
 
  float rand(vec2 n) {  
    return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); 
  } 
 
  float noise(vec2 p){ 
    vec2 ip = floor(p); 
    vec2 u = fract(p); 
    u = u*u*(3.0-2.0*u); 
 
    float res = mix( 
      mix(rand(ip),rand(ip+vec2(1.0,0.0)),u.x), 
      mix(rand(ip+vec2(0.0,1.0)),rand(ip+vec2(1.0,1.0)),u.x),u.y); 
    return res*res; 
  } 
   
  vec4 frag (vec2 uv) { 
    vec2 p_d = uv; 
    p_d.y += t * 0.1; 
    vec2 offset = vec2(noise(p_d*22.)); 
    offset *= uv.y * 0.01;  
    return sample(uv.xy + offset); 
  } 
`); 
 
filter.ready = function() {  
  let c = filter.canvas; 
  document.body.append(c); 
  animate(); 
} 
 
function animate() { 
  filter.uniform('1f', 't', (new Date().getTime() - t)/1000) 
        .apply(); 
  requestAnimationFrame(animate); 
}
<script> 
function webglFilter(url, fragCode) { 
   
  let canvas = document.createElement('canvas'); 
  let pid, gl = canvas.getContext('webgl')  
        || canvas.getContext('experimental-webgl'); 
   
  let loader = new Image(); 
  loader.crossOrigin = "anonymous"; 
  loader.src = url; 
  loader.onload = function() {  
     
    canvas.width = loader.width; 
    canvas.height = loader.height; 
 
    pid = gl.createProgram(); 
 
    shader(` 
      attribute vec2 coords; 
        void main(void) { 
        gl_Position = vec4(coords.xy, 0.0, 1.0); 
      } 
    `, gl.VERTEX_SHADER); 
 
    shader(` 
      precision highp float; 
      uniform sampler2D texture; 
 
      vec4 sample(vec2 uv) { 
          return texture2D(texture, uv); 
      } 
 
      ${fragCode} 
 
      void main(void) { 
          gl_FragColor = frag(vec2(  
            gl_FragCoord.x / ${canvas.width}.,   
            1. - gl_FragCoord.y / ${canvas.height}.  
          )); 
      } 
    `, gl.FRAGMENT_SHADER); 
 
    gl.linkProgram(pid); 
    gl.useProgram(pid); 
 
    let array = new Float32Array([-1,  3, -1, -1, 3, -1]); 
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); 
    gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW); 
 
    let al = gl.getAttribLocation(pid, "coords"); 
    gl.vertexAttribPointer(al, 2, gl.FLOAT, false, 0, 0); 
    gl.enableVertexAttribArray(al); 
 
    let texture = gl.createTexture(); 
    gl.bindTexture(gl.TEXTURE_2D, texture); 
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, loader); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 
 
    var textureLocation = gl.getUniformLocation(pid, "texture"); 
    gl.uniform1i(textureLocation, 0); 
  
    filter.ready && filter.ready(); 
    filter.apply(); 
     
    function shader(src, type) { 
      let sid = gl.createShader(type); 
      gl.shaderSource(sid, src); 
      gl.compileShader(sid); 
      var message = gl.getShaderInfoLog(sid); 
      gl.attachShader(pid, sid); 
      if (message.length > 0) { 
        console.log(src.split('\n').map(function (str, i) { 
          return ("" + (1 + i)).padStart(4, "0") + ": " + str 
        }).join('\n')); 
        throw message; 
      } 
    } 
  } 
   
  let filter = { 
     
    canvas: canvas, 
     
    ready: null, 
     
    uniform: function(type, name, v1, v2, v3, v4) { 
      if (!pid) 
        throw new Error('program not ready'); 
      var ul = gl.getUniformLocation(pid, name); 
      gl['uniform' + type](ul, v1, v2, v3, v4); 
      return filter; 
    }, 
     
    apply: function() { 
      if (!pid) 
        throw new Error('program not ready'); 
      gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); 
      gl.clearColor(0, 0, 0, 0); 
      gl.drawArrays(gl.TRIANGLES, 0, 3); 
      return filter; 
    } 
  } 
   
  return filter; 
} 
</script> 
<style> 
body{ 
margin:0; 
 
} 
</style>

READ ALSO
Проблема с отсчетом setInterval в Javascript

Проблема с отсчетом setInterval в Javascript

у меня такой вопрос: почему, для того, чтобы отсчет шел до 0, нужно i = -2(Естественно когда i = 0, отсчет идет до 2)Вот код:

77
Добавить слушателя к div в цикле

Добавить слушателя к div в цикле

Хочу отобразить онлайн,оффлайн пользователей чата и приписать определенную логику при нажатии на одного из такого пользователя(div)Есть JSONArray,читаю...

129
можно ли сделать сменяющийся статус без js?

можно ли сделать сменяющийся статус без js?

вот есть js, который позволяет менять строку на какую-то другую строку, а можно достичь таких же успехов на css?

91
пару вопросов по решению к задаче [дубликат]

пару вопросов по решению к задаче [дубликат]

На данный вопрос уже ответили:

101