在鼠标移动中创建涂抹/液化效果,使用webgl连续动画回原始状态

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在鼠标移动中创建涂抹/液化效果,使用webgl连续动画回原始状态相关的知识,希望对你有一定的参考价值。

我试图找到可用于创建涂抹/液化效果的信息或示例,这些效果会持续动画回原始状态。

最初我正在考虑使用three.js或pixi.js渲染一些文本,然后使用鼠标事件和光线投射将网格拖出位置,我发现最接近的是这个。

https://codepen.io/shshaw/pen/qqVgbg

let renderer = PIXI.autoDetectRenderer(window.innerWidth,
window.innerHeight, { transparent: true });

我认为理想情况下我会将文本渲染为图像,然后涂抹效果将应用于像素,并且它们会慢慢地动画回原始状态。与此类似。

http://www.duhaihang.com/#/work/

我想我可能需要使用自定义的GLSL着色器和某种缓冲区来保持构成图像的像素的原始状态和当前状态。

任何帮助或方向将不胜感激。

答案

两者似乎相对简单。

第一个,就像你提到的那样,你制作一个绘制平面的顶点网格(网格)。您将纹理贴图到平面,当您拖动鼠标时,向鼠标触摸的每个顶点添加一个位移。随着时间的推移将位移重置为0(如0位移量)

这是一个例子:它只是将一个顶点移位一个随机数量而不是更可预测的东西。最后我只是节省了位移应该消失的时间,然后在着色器中我做了一个简单的线性lerp(可以使用更高级的lerp来反弹或者其他东西)。这几乎是着色器中发生的一切。

const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const vs = `
attribute vec4 position;
attribute vec3 displacement;

uniform mat4 u_matrix;
uniform float u_time;
uniform float u_timeToGoBack;

varying vec2 v_texcoord;

void main() {
  // because position goes -1 <-> 1 we can just use
  // it for texture coords
  v_texcoord = position.xy * .5 + .5;  

  // displacement.z is the time at which it should be undisplaced
  float displaceTime = displacement.z - u_time;
  float lerp = clamp(displaceTime / u_timeToGoBack, 0., 1.);
  vec2 displace = displacement.xy * lerp;

  gl_Position = u_matrix * (position + vec4(displace, 0, 0));
}
`;
const fs = `
precision mediump float;

uniform sampler2D texture;

varying vec2 v_texcoord;

void main() {
  gl_FragColor = texture2D(texture, v_texcoord);
}
`;

const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// create a grid of points in a -1 to +1 quad
const positions = [];
const displacements = [];
const indices = [];

const res = 100;
for (var y = 0; y < res; ++y) {
  var v = (y / (res - 1)) * 2 - 1;
  for (var x = 0; x < res; ++x) {
    var u = (x / (res - 1)) * 2 - 1;
    
    positions.push(u, v);
    displacements.push(0, 0, 0);
  }  
}

for (var y = 0; y < res - 1; ++y) {
  var off0 = (y + 0) * res;
  var off1 = (y + 1) * res;
  for (var x = 0; x < res - 1; ++x) {
    indices.push(
      off0 + x + 0, off0 + x + 1, off1 + x + 0,
      off1 + x + 0, off0 + x + 1, off1 + x + 1
    );
  }  
}

// create buffers and fills them in.
// (calls gl.createBuffer and gl.bufferData for each array)
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  position: { numComponents: 2, data: positions, },
  displacement: { numComponents: 3, data: displacements, },
  indices: indices,
});

// this will be replaced when the image has loaded;
var img = { width: 1, height: 1 };

const tex = twgl.createTexture(gl, {
  src: 'https://farm6.staticflickr.com/5078/14032935559_8c13e9b181_z_d.jpg',
  crossOrigin: '',
}, function(err, texture, source) {
  img = source;                               
});

var currentTime = 0;
var currentMatrix;
const timeToGoBack = 2; // in seconds;
    
function render(time) {
  time *= 0.001;  // convert to seconds
  
  currentTime = time;
  
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.useProgram(programInfo.program);
  
  var aspect = img.width / img.height;
  var mat = m4.ortho(0, gl.canvas.clientWidth, gl.canvas.clientHeight, 0, -1, 1);
  mat = m4.translate(mat, [gl.canvas.clientWidth / 2, gl.canvas.clientHeight / 2, 0]);
  mat = m4.scale(mat, [img.width * .25, img.height * .25, 1]);

  currentMatrix = mat;
  
  // calls gl.bindBuffer, gl.vertexAttribPointer to setup
  // attributes
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  twgl.setUniforms(programInfo, {
    u_matrix: mat,
    u_texture: tex, 
    u_time: currentTime,
    u_timeToGoBack: timeToGoBack,
  });
  
  gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

const displace = new Float32Array(3);
    
gl.canvas.addEventListener('mousemove', function(event, target) {
  target = target || event.target;
  const rect = target.getBoundingClientRect();

  const rx = event.clientX - rect.left;
  const ry = event.clientY - rect.top;

  const x = rx * target.width  / target.clientWidth;
  const y = ry * target.height / target.clientHeight;
  
  // reverse project the mouse onto the image
  var rmat = m4.inverse(currentMatrix);
  var s = m4.transformPoint(
    rmat, [x / target.width * 2 - 1, y / target.height * 2 - 1, 0]);
  
  // s is now a point in the space of `position`

  // lets just move closest point?
  var gx = Math.round((s[0] * .5 + .5) * res);
  var gy = Math.round((s[1] * .5 + .5) * res);
  
  gx = clamp(gx, 0, res - 1);
  gy = clamp(gy, 0, res - 1);
  
  const offset = ((res - gy - 1) * res + gx) * 3 * 4;
  
  displace[0] = rand(-.1, .1);
  displace[1] = rand(-.1, .1);
  displace[2] = currentTime + timeToGoBack;
   
  gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.displacement.buffer);
  gl.bufferSubData(gl.ARRAY_BUFFER, offset, displace);
});
    
function rand(min, max) {
  return Math.random() * (max - min) + min;
}

function clamp(v, min, max) {
  return Math.max(min, Math.min(max, v));
}
body {
  margin: 0;
}
canvas {
  width: 100vw;
  height: 100vh;
  display: block;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

以上是关于在鼠标移动中创建涂抹/液化效果,使用webgl连续动画回原始状态的主要内容,如果未能解决你的问题,请参考以下文章

《WebGL 编程指南》进阶篇

在 raylib 中使用着色器时,对象不会根据鼠标位置移动

使用PS随意更换相片底色

在 Python 中创建连续年份月份的列表

涂抹笔刷工具的介绍

在连续的内存块中创建具有可变元素的链表