Подскажите, есть ли легкий способ создать эффекта миража(марева) на технологиях canvas / svg для изображения. В приведенном примере используется перемещение текстур, можно ли проще, к примеру для анимации эффекта огня?
Выбираем картинку с объемными объектами на дальнем плане, чтобы эффект миража лучше проявился.
Применение фильтров сильно нагружает видеокарту, поэтому при реальном применении этого эффекта старайтесь уменьшить размеры зоны действия фильтра.
Применяем к ней два фильтра 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>
Вот смастерил 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>
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
у меня такой вопрос: почему, для того, чтобы отсчет шел до 0, нужно i = -2(Естественно когда i = 0, отсчет идет до 2)Вот код:
Хочу отобразить онлайн,оффлайн пользователей чата и приписать определенную логику при нажатии на одного из такого пользователя(div)Есть JSONArray,читаю...
вот есть js, который позволяет менять строку на какую-то другую строку, а можно достичь таких же успехов на css?