本文介绍了在鼠标移动中创建涂抹/液化效果,使用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 ;改变vec2 v_texcoord; void main(){//因为位置变为-1< - > 1我们可以使用//它用于纹理坐标v_texcoord = position.xy * .5 + .5; // displacement.z是它应该被取消的时间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; vary vec2 v_texcoord; void main(){gl_FragColor = texture2D(texture,v_texcoord); }; const programInfo = twgl.createProgramInfo(gl,[vs,fs]); //在-1到+1 quadconst位置创建一个点网格= []; 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(你,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); //创建缓冲区并填充它们。(为每个数组调用gl.createBuffer和gl.bufferData)const bufferInfo = twgl.createBufferInfoFromArrays(gl,{position:{numComponents:2,data:positions,},displacement :{numComponents:3,data:displacements,},indices:indices,}); //这将在图像加载时被替换; 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; // 很快; function render(time){time * = 0.001; //转换为秒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; //调用gl.bindBuffer,gl.vertexAttribPointer来设置//属性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; //将鼠标反向投影到图像var rmat = m4.inverse(currentMatrix) ); var s = m4.transformPoint(rmat,[x / target.width * 2 - 1,y / target.height * 2 - 1,0]); // s现在是`position`空间中的一个点//让我们移动最近的点?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.bufferSubD ata(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;身高:100vh; display:block;} < script src =https ://twgljs.org/dist/2.x/twgl-full.min.js>< /脚本><画布>< /画布> 对于第二个而不是替换顶点你做一个位移纹理,随着时间的推移你将该位移重置为0 您可以看到淡出此处的示例。如果您采用该样本而不是绘制随机方块,则在鼠标下绘制,然后使用该纹理作为主图像的位移。通过置换我的意思是通常你在像这样的片段着色器中查找纹理 vec4 color = texture2D(someTexture,someTextureCoords); 相反,你想用一个位移代替顶点坐标,就像这样 //假设位移纹理与相同// //主纹理可以使用相同的纹理坐标 //首先查找位移并转换为-1< - > 1范围 //我们只使用R和G通道,它们将成为U和V //位移到我们的纹理坐标 vec2 displacement = texture2D(displacementTexture,someTextureCoords).rg * 2. - 1。; vec2 uv = someTextureCoords + displacement * displacementRange; vec4 color = texture2d(someTexture,uv); 以上链接的示例用于置换 var vs =`attribute vec4 position; uniform mat4 u_matrix; void main(){gl_Position = u_matrix * position ;}`; var fs =`precision mediump float; uniform vec4 u_color; void main(){gl_FragColor = u_color;}`; var vsQuad =`attribute vec4 position; attribute vec2 texcoord; varying vec2 v_texcoord; void main(){gl_Position =位置; v_texcoord = texcoord;}`; var fsFade =`precision mediump float; vary vec2 v_texcoord; uniform sampler2D u_texture; uniform float u_mixAmount; const float kEpsilon = 2./256.;void main(){//转换颜色从0.-大于1。到-1。 - > +1。所以我们可以调整到零vec4 color = texture2D(u_texture,v_texcoord)* 2. - 1。 //弄清楚要调整多少vec4 adjust = -color * u_mixAmount; //如果调整太小(因为纹理只有8位)//调整最小量。 //也可以通过使用浮点纹理来解决这个问题adjust = mix(adjust,sign(color)* -kEpsilon,step(abs(adjust),vec4(kEpsilon))); //调整颜色+ =调整; //把它写回来转换回0 - > 1 gl_FragColor = color * .5 + .5;}`; var fsDisplace =`precision mediump float; vary vec2 v_texcoord; uniform sampler2D u_texture; uniform sampler2D u_displacementTexture; uniform vec2 u_displacementRange; void main(){//假设位移纹理为与//主纹理相同的大小你可以使用相同的纹理坐标//首先查找位移并转换为-1< - > 1范围//我们只使用R和G通道,它们将成为U和V //位移到我们的纹理坐标vec2 displacement = texture2D(u_displacementTexture,v_texcoord).rg * 2. - 1。 vec2 uv = v_texcoord + displacement * u_displacementRange; gl_FragColor = texture2D(u_texture,uv);}`; var $ = document.querySelector.bind(document); var mixAmount = 0.03; var gl = $(canvas)。getContext(webgl); var m4 = twgl .m4; var programInfo = twgl.createProgramInfo(gl,[vs,fs]); var fadeProgramInfo = twgl.createProgramInfo(gl,[vsQuad,fsFade]); var displaceProgramInfo = twgl.createProgramInfo(gl,[vsQuad,fsDisplace]) ; //这将在图像加载时被替换; var img = {width:1,height:1}; const tex = twgl.createTexture(gl,{src:'https://farm6.staticflickr.com/5078 /14032935559_8c13e9b181_z_d.jpg',crossOrigin:'',flipY:true,},function(err,texture,source){img = source;}); //创建-1到+1 quadvar quadBufferInfo = twgl.primitives.createXYQuadBufferInfo (gl); //创建2个RGBA纹理+深度framebuffersvar fadeAttachments = [{format:gl.RGBA,min:gl.NEAREST,max:gl.NEAREST,wrap:gl.CLAMP_TO_EDGE,},{format:gl.DEPTH_STENCIL} ,]; var fadeFbi1 = twgl.createFramebufferInfo (gl,fadeAttachments); var fadeFbi2 = twgl.createFramebufferInfo(gl,fadeAttachments); function drawThing(gl,x,y,rotation,scale,color){var matrix = m4.ortho(0,gl.canvas.width,gl .canvas.height,0,-1,1); matrix = m4.translate(matrix,[x,y,0]); matrix = m4.rotateZ(矩阵,旋转); matrix = m4.scale(matrix,[scale,scale,1]); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl,programInfo,quadBufferInfo); twgl.setUniforms(programInfo,{u_matrix:matrix,u_color:color,}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo);} function rand(min,max){if(max === undefined){max = min; min = 0; } return min + Math.random()*(max - min);} function render(time){if(twgl.resizeCanvasToDisplaySize(gl.canvas)){//将清除颜色设置为0.5,即0位移//我们的着色器gl.clearColor(0.5,0.5,0.5,0.5); //调整帧缓冲区附件的大小,使它们的大小与画布twgl.resizeFramebufferInfo(gl,fadeFbi1,fadeAttachments)的大小相同; //将颜色缓冲区清除为0.5 twgl.bindFramebufferInfo(gl,fadeFbi1); gl.clear(gl.COLOR_BUFFER_BIT); //调整第二帧缓冲区附件的大小,使它们的大小与画布twgl.resizeFramebufferInfo(gl,fadeFbi2,fadeAttachments)的大小相同; //将颜色缓冲区清除为0.5 twgl.bindFramebufferInfo(gl,fadeFbi2); gl.clear(gl.COLOR_BUFFER_BIT); } //使用mixAmount从fadeFbi1复制到fabeFbi2中。 // fadeFbi2将包含mix(fadeFb1,u_fadeColor,u_mixAmount)twgl.bindFramebufferInfo(gl,fadeFbi2); gl.useProgram(fadeProgramInfo.program); twgl.setBuffersAndAttributes(gl,fadeProgramInfo,quadBufferInfo); twgl.setUniforms(fadeProgramInfo,{u_texture:fadeFbi1.attachments [0],u_mixAmount:mixAmount,}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo); //现在为fadeFb2绘制新内容。请注意我们不清楚! twgl.bindFramebufferInfo(gl,fadeFbi2); var x = rand(gl.canvas.width); var y = rand(gl.canvas.height); var rotation = rand(Math.PI); var scale = rand(10,20); var color = [rand(1),rand(1),rand(1),1]; drawThing(gl,x,y,旋转,缩放,颜色); //现在使用fadeFbi2作为位移,同时将tex绘制到画布twgl.bindFramebufferInfo(gl,null); gl.useProgram(displaceProgramInfo.program); twgl.setBuffersAndAttributes(gl,displaceProgramInfo,quadBufferInfo); twgl.setUniforms(displaceProgramInfo,{u_texture:tex,u_displacementTexture:fadeFbi2.attachments [0],u_displacementRange:[0.1,0.1],}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo); //交换变量,以便下次var temp = fadeFbi1时渲染到相反的纹理; fadeFbi1 = fadeFbi2; fadeFbi2 = temp; requestAnimationFrame(render);} requestAnimationFrame(render); body {margin:0; canvas {display:block;宽度:100vw;身高:100vh; } < script src =https:// twgljs .ORG / DIST / twgl-full.min.js>< /脚本><画布>< /画布> 所以剩下的就是让它在鼠标下绘制而不是随意 var vs =`属性vec4 position; uniform mat4 u_matrix; void main(){gl_Position = u_matrix * position;}`; var fs =`precision mediump float; uniform vec4 u_color; void main(){gl_FragColor = u_color;}`; var vsQuad =`attribute vec4 position; attribute vec2 texcoord; uniform mat4 u_matrix; varying vec2 v_texcoord; void main(){gl_Position = u_matrix *位置; v_texcoord = texcoord;}`; var fsFade =`precision mediump float; vary vec2 v_texcoord; uniform sampler2D u_texture; uniform float u_mixAmount; const float kEpsilon = 2./256.;void main(){vec4 color = texture2D(u_texture,v_texcoord) )* 2. - 1。; vec4 adjust = -color * u_mixAmount; adjust = mix(adjust,sign(color)* -kEpsilon,step(abs(adjust),vec4(kEpsilon))); color + = adjust; gl_FragColor = color * .5 + .5;}`; var fsDisplace =`precision mediump float; vary vec2 v_texcoord; uniform sampler2D u_texture; uniform sampler2D u_displacementTexture; uniform vec2 u_displacementRange; void main(){//假设位移纹理是与//主纹理相同的大小你可以使用相同的纹理坐标//首先查找位移并转换为-1< - > 1范围//我们只使用R和G通道,它们将成为U和V //位移到我们的纹理坐标vec2 displacement = texture2D(u_displacementTexture,v_texcoord).rg * 2. - 1。 vec2 uv = v_texcoord + displacement * u_displacementRange; gl_FragColor = texture2D(u_texture,uv);}`; var $ = document.querySelector.bind(document); var mixAmount = 0.03; var gl = $(canvas)。getContext(webgl); var m4 = twgl .m4; var programInfo = twgl.createProgramInfo(gl,[vs,fs]); var fadeProgramInfo = twgl.createProgramInfo(gl,[vsQuad,fsFade]); var displaceProgramInfo = twgl.createProgramInfo(gl,[vsQuad,fsDisplace]) ; //这将在图像加载时被替换; 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;}); //创建-1到+1 quadvar quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl); //创建2个RGBA纹理+深度framebuffersvar fadeAttachments = [{format:gl.RGBA,min:gl.NEAREST,max:gl.NEAREST,wrap:gl.CLAMP_TO_EDGE,},]; var fadeFbi1 = twgl.createFramebufferInfo(gl, fadeAttachments); var fadeF bi2 = twgl.createFramebufferInfo(gl,fadeAttachments); function drawThing(gl,x,y,rotation,scale,color){var matrix = m4.ortho(0,gl.canvas.width,gl.canvas.height,0, -1,1); matrix = m4.translate(matrix,[x,y,0]); matrix = m4.rotateZ(矩阵,旋转); matrix = m4.scale(matrix,[scale,scale,1]); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl,programInfo,quadBufferInfo); twgl.setUniforms(programInfo,{u_matrix:matrix,u_color:color,}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo);} function rand(min,max){if(max === undefined){max = min; min = 0; } return min + Math.random()*(max - min);} var drawRect = false; var rectX; var rectY; var currentMatrix; function render(time){if(twgl.resizeCanvasToDisplaySize(gl.canvas)){/ /将清晰颜色设置为0.5,这对于我们的着色器gl.clearColor(0.5,0.5,0.5,0.5)是0位移//; //调整帧缓冲区附件的大小,使它们的大小与画布twgl.resizeFramebufferInfo(gl,fadeFbi1,fadeAttachments)的大小相同; //将颜色缓冲区清除为0.5 twgl.bindFramebufferInfo(gl,fadeFbi1); gl.clear(gl.COLOR_BUFFER_BIT); //调整第二帧缓冲区附件的大小,使它们的大小与画布twgl.resizeFramebufferInfo(gl,fadeFbi2,fadeAttachments)的大小相同; //将颜色缓冲区清除为0.5 twgl.bindFramebufferInfo(gl,fadeFbi2); gl.clear(gl.COLOR_BUFFER_BIT); } //使用mixAmount从fadeFbi1复制到fabeFbi2中。 // fadeFbi2将包含mix(fadeFb1,u_fadeColor,u_mixAmount)twgl.bindFramebufferInfo(gl,fadeFbi2); gl.useProgram(fadeProgramInfo.program); twgl.setBuffersAndAttributes(gl,fadeProgramInfo,quadBufferInfo); twgl.setUniforms(fadeProgramInfo,{u_matrix:m4.identity(),u_texture:fadeFbi1.attachments [0],u_mixAmount:mixAmount,}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo); if(drawRect){drawRect = false; //现在为fadeFb2绘制新内容。请注意我们不清楚! twgl.bindFramebufferInfo(gl,fadeFbi2); var rotation = rand(Math.PI); var scale = rand(10,20); var color = [rand(1),rand(1),rand(1),1]; drawThing(gl,rectX,rectY,rotation,scale,color); } //现在使用fadeFbi2作为位移,同时将tex绘制到画布twgl.bindFramebufferInfo(gl,null); 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 * 0.5,img.height * 0.5,1]); currentMatrix = mat; gl.useProgram(displaceProgramInfo.program); twgl.setBuffersAndAttributes(gl,displaceProgramInfo,quadBufferInfo); twgl.setUniforms(displaceProgramInfo,{u_matrix:mat,u_texture:tex,u_displacementTexture:fadeFbi2.attachments [0],u_displacementRange:[0.05,0.05],}); twgl.drawBufferInfo(gl,gl.TRIANGLES,quadBufferInfo); //交换变量,以便下次var temp = fadeFbi1时渲染到相反的纹理; fadeFbi1 = fadeFbi2; fadeFbi2 = temp; requestAnimationFrame(render);} requestAnimationFrame(render); 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; //反向投影鼠标到图像var rmat = m4.inverse(currentMatrix); var clipspacePoint = [x / target.width * 2 - 1, - (y / target.height * 2 - 1),0]; var s = m4.transformPoint( rmat,clipspacePoint); // s现在是图像四边形空间中的一个点。四边形变为-1到1 //我们将使用像素绘制它,因为drawThing需要//像素值而我们的置换贴图与画布drawRect = true的大小相同; rectX =(s [0] * .5 + .5)* gl.canvas.width; rectY =(-s [1] * .5 + .5)* gl.canvas.height;}); body {margin:0; canvas {display:block;宽度:100vw;身高:100vh; } < script src =https:// twgljs .ORG / DIST / twgl-full.min.js>< /脚本><画布>< /画布> 获得第二个示例的确切效果看起来像是通过某种噪声函数运行位移。您可以使用 WebGL Inspector 或着色器编辑器查看着色器内部并查看它们正在做什么。 这是另一个示例,它创建了一个位移纹理,它比边缘更向中心位移。 注意:我应该说清楚我没有看到你链接的例子如何工作的细节,我只是建议他们做某事类似到此。找出他们真正做的事情的最好方法是查看他们的代码并运行前面段落中提到的工具来查看内部并查看正在发生的事情。也许他们没有使用直接位移,而是使用像法线这样的位移作为位移。也许不是绘制纯色(第2和第3个示例)或纹理(第4个示例),而是使用程序生成的图案绘制或使用基于屏幕的纹理坐标绘制重复纹理图案。也许位移纹理是一个单独的纹理,它们有一个混合蒙版,它们以白色绘制,淡入淡出以决定要应用多少位移纹理。在WebGL中有无数种方法可以做。 I am trying to find information or examples that I can use to create a smudge/liquify effect that continuously animates back to the original state.Initially I was looking at using three.js or pixi.js to render some text and then use mouse events and ray casting to drag the mesh out of position, the closest thing I have found is this.https://codepen.io/shshaw/pen/qqVgbglet renderer = PIXI.autoDetectRenderer(window.innerWidth,window.innerHeight, { transparent: true });I think that ideally I would render the text as an image and then the smudge effect would be applied to the pixels and they would slowly animate back to their original states. Similar to this.http://www.duhaihang.com/#/work/I think I may need to use a custom GLSL shader and some kind of buffer to hold the original and the current state of the pixels making up the image.Any help or direction would be much appreciated. 解决方案 Both seem relatively straightforward.The first one, like you mentioned, you make a mesh (grid) of vertices that draw a plane. You texture map the face to the plane, as you drag the mouse around add a displacement to the each vertex the mouse touches. Over time reset the displacement back to 0 (as in 0 amount of displacement)here's an example: It's only displacing a single vertex a random amount instead of something more predictable. Finally I'm just saving the time at which the displacement should fade out by, then in the shader I do a simple linear lerp (could use a fancier lerp for a bounce or something). This is so pretty much everything happens in the shader.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 quadconst 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/2.x/twgl-full.min.js"></script><canvas></canvas>For the second one instead of displacing vertices you make a displacement texture, over time you reset that displacement back to 0 You can see an example of fading things out here. If you took that sample and instead of drawing random square you draw under the mouse, then use that texture as a displacement to your main image. By displacement I mean normally you look up a texture in a fragment shader like thisvec4 color = texture2D(someTexture, someTextureCoords);Instead you want to displace the vertex coords with a displacement, something like this// assuming the displacement texture is the same size as // the main texture you can use the same texture coords// first look up the displacement and convert to -1 <-> 1 range// we're only using the R and G channels which will become U and V// displacements to our texture coordinatesvec2 displacement = texture2D(displacementTexture, someTextureCoords).rg * 2. - 1.;vec2 uv = someTextureCoords + displacement * displacementRange;vec4 color = texture2d(someTexture, uv);Here's the sample linked above being used for displacementvar vs = `attribute vec4 position;uniform mat4 u_matrix;void main() { gl_Position = u_matrix * position;}`;var fs = `precision mediump float;uniform vec4 u_color;void main() { gl_FragColor = u_color;}`;var vsQuad = `attribute vec4 position;attribute vec2 texcoord;varying vec2 v_texcoord;void main() { gl_Position = position; v_texcoord = texcoord;}`;var fsFade = `precision mediump float;varying vec2 v_texcoord;uniform sampler2D u_texture;uniform float u_mixAmount;const float kEpsilon = 2./256.;void main() { // convert color from 0.->1. to -1. -> +1. so we can go adjust toward zero vec4 color = texture2D(u_texture, v_texcoord) * 2. - 1.; // figure out how much to adjust vec4 adjust = -color * u_mixAmount; // If the adjustment is too small (because the texture is only 8bits) // the adjust the minimum amount. // Could also solve this by using floating point textures adjust = mix(adjust, sign(color) * -kEpsilon, step(abs(adjust), vec4(kEpsilon))); // adjust it color += adjust; // write it back converting back to 0 -> 1 gl_FragColor = color * .5 + .5;}`;var fsDisplace = `precision mediump float;varying vec2 v_texcoord;uniform sampler2D u_texture;uniform sampler2D u_displacementTexture;uniform vec2 u_displacementRange;void main() { // assuming the displacement texture is the same size as // the main texture you can use the same texture coords // first look up the displacement and convert to -1 <-> 1 range // we're only using the R and G channels which will become U and V // displacements to our texture coordinates vec2 displacement = texture2D(u_displacementTexture, v_texcoord).rg * 2. - 1.; vec2 uv = v_texcoord + displacement * u_displacementRange; gl_FragColor = texture2D(u_texture, uv);}`;var $ = document.querySelector.bind(document);var mixAmount = 0.03;var gl = $("canvas").getContext("webgl");var m4 = twgl.m4;var programInfo = twgl.createProgramInfo(gl, [vs, fs]);var fadeProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsFade]);var displaceProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsDisplace]);// 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: '', flipY: true,}, function(err, texture, source) { img = source; });// Creates a -1 to +1 quadvar quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);// Creates 2 RGBA texture + depth framebuffersvar fadeAttachments = [ { format: gl.RGBA, min: gl.NEAREST, max: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, }, { format: gl.DEPTH_STENCIL },];var fadeFbi1 = twgl.createFramebufferInfo(gl, fadeAttachments);var fadeFbi2 = twgl.createFramebufferInfo(gl, fadeAttachments);function drawThing(gl, x, y, rotation, scale, color) { var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1); matrix = m4.translate(matrix, [x, y, 0]); matrix = m4.rotateZ(matrix, rotation); matrix = m4.scale(matrix, [scale, scale, 1]); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo); twgl.setUniforms(programInfo, { u_matrix: matrix, u_color: color, }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);}function rand(min, max) { if (max === undefined) { max = min; min = 0; } return min + Math.random() * (max - min);}function render(time) { if (twgl.resizeCanvasToDisplaySize(gl.canvas)) { // set the clear color to 0.5 which is 0 displacement // for our shader gl.clearColor(0.5, 0.5, 0.5, 0.5); // resize the framebuffer's attachments so their the // same size as the canvas twgl.resizeFramebufferInfo(gl, fadeFbi1, fadeAttachments); // clear the color buffer to 0.5 twgl.bindFramebufferInfo(gl, fadeFbi1); gl.clear(gl.COLOR_BUFFER_BIT); // resize the 2nd framebuffer's attachments so their the // same size as the canvas twgl.resizeFramebufferInfo(gl, fadeFbi2, fadeAttachments); // clear the color buffer to 0.5 twgl.bindFramebufferInfo(gl, fadeFbi2); gl.clear(gl.COLOR_BUFFER_BIT); } // fade by copying from fadeFbi1 into fabeFbi2 using mixAmount. // fadeFbi2 will contain mix(fadeFb1, u_fadeColor, u_mixAmount) twgl.bindFramebufferInfo(gl, fadeFbi2); gl.useProgram(fadeProgramInfo.program); twgl.setBuffersAndAttributes(gl, fadeProgramInfo, quadBufferInfo); twgl.setUniforms(fadeProgramInfo, { u_texture: fadeFbi1.attachments[0], u_mixAmount: mixAmount, }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo); // now draw new stuff to fadeFb2. Notice we don't clear! twgl.bindFramebufferInfo(gl, fadeFbi2); var x = rand(gl.canvas.width); var y = rand(gl.canvas.height); var rotation = rand(Math.PI); var scale = rand(10, 20); var color = [rand(1), rand(1), rand(1), 1]; drawThing(gl, x, y, rotation, scale, color); // now use fadeFbi2 as a displacement while drawing tex to the canvas twgl.bindFramebufferInfo(gl, null); gl.useProgram(displaceProgramInfo.program); twgl.setBuffersAndAttributes(gl, displaceProgramInfo, quadBufferInfo); twgl.setUniforms(displaceProgramInfo, { u_texture: tex, u_displacementTexture: fadeFbi2.attachments[0], u_displacementRange: [0.1, 0.1], }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo); // swap the variables so we render to the opposite textures next time var temp = fadeFbi1; fadeFbi1 = fadeFbi2; fadeFbi2 = temp; requestAnimationFrame(render);}requestAnimationFrame(render);body { margin: 0; }canvas { display: block; width: 100vw; height: 100vh; }<script src="https://twgljs.org/dist/twgl-full.min.js"></script><canvas></canvas>So all that's left is to make it draw under the mouse instead of at randomvar vs = `attribute vec4 position;uniform mat4 u_matrix;void main() { gl_Position = u_matrix * position;}`;var fs = `precision mediump float;uniform vec4 u_color;void main() { gl_FragColor = u_color;}`;var vsQuad = `attribute vec4 position;attribute vec2 texcoord;uniform mat4 u_matrix;varying vec2 v_texcoord;void main() { gl_Position = u_matrix * position; v_texcoord = texcoord;}`;var fsFade = `precision mediump float;varying vec2 v_texcoord;uniform sampler2D u_texture;uniform float u_mixAmount;const float kEpsilon = 2./256.;void main() { vec4 color = texture2D(u_texture, v_texcoord) * 2. - 1.; vec4 adjust = -color * u_mixAmount; adjust = mix(adjust, sign(color) * -kEpsilon, step(abs(adjust), vec4(kEpsilon))); color += adjust; gl_FragColor = color * .5 + .5;}`;var fsDisplace = `precision mediump float;varying vec2 v_texcoord;uniform sampler2D u_texture;uniform sampler2D u_displacementTexture;uniform vec2 u_displacementRange;void main() { // assuming the displacement texture is the same size as // the main texture you can use the same texture coords // first look up the displacement and convert to -1 <-> 1 range // we're only using the R and G channels which will become U and V // displacements to our texture coordinates vec2 displacement = texture2D(u_displacementTexture, v_texcoord).rg * 2. - 1.; vec2 uv = v_texcoord + displacement * u_displacementRange; gl_FragColor = texture2D(u_texture, uv);}`;var $ = document.querySelector.bind(document);var mixAmount = 0.03;var gl = $("canvas").getContext("webgl");var m4 = twgl.m4;var programInfo = twgl.createProgramInfo(gl, [vs, fs]);var fadeProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsFade]);var displaceProgramInfo = twgl.createProgramInfo(gl, [vsQuad, fsDisplace]);// 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; });// Creates a -1 to +1 quadvar quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);// Creates 2 RGBA texture + depth framebuffersvar fadeAttachments = [ { format: gl.RGBA, min: gl.NEAREST, max: gl.NEAREST, wrap: gl.CLAMP_TO_EDGE, },];var fadeFbi1 = twgl.createFramebufferInfo(gl, fadeAttachments);var fadeFbi2 = twgl.createFramebufferInfo(gl, fadeAttachments);function drawThing(gl, x, y, rotation, scale, color) { var matrix = m4.ortho(0, gl.canvas.width, gl.canvas.height, 0, -1, 1); matrix = m4.translate(matrix, [x, y, 0]); matrix = m4.rotateZ(matrix, rotation); matrix = m4.scale(matrix, [scale, scale, 1]); gl.useProgram(programInfo.program); twgl.setBuffersAndAttributes(gl, programInfo, quadBufferInfo); twgl.setUniforms(programInfo, { u_matrix: matrix, u_color: color, }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo);}function rand(min, max) { if (max === undefined) { max = min; min = 0; } return min + Math.random() * (max - min);}var drawRect = false;var rectX;var rectY;var currentMatrix;function render(time) { if (twgl.resizeCanvasToDisplaySize(gl.canvas)) { // set the clear color to 0.5 which is 0 displacement // for our shader gl.clearColor(0.5, 0.5, 0.5, 0.5); // resize the framebuffer's attachments so their the // same size as the canvas twgl.resizeFramebufferInfo(gl, fadeFbi1, fadeAttachments); // clear the color buffer to 0.5 twgl.bindFramebufferInfo(gl, fadeFbi1); gl.clear(gl.COLOR_BUFFER_BIT); // resize the 2nd framebuffer's attachments so their the // same size as the canvas twgl.resizeFramebufferInfo(gl, fadeFbi2, fadeAttachments); // clear the color buffer to 0.5 twgl.bindFramebufferInfo(gl, fadeFbi2); gl.clear(gl.COLOR_BUFFER_BIT); } // fade by copying from fadeFbi1 into fabeFbi2 using mixAmount. // fadeFbi2 will contain mix(fadeFb1, u_fadeColor, u_mixAmount) twgl.bindFramebufferInfo(gl, fadeFbi2); gl.useProgram(fadeProgramInfo.program); twgl.setBuffersAndAttributes(gl, fadeProgramInfo, quadBufferInfo); twgl.setUniforms(fadeProgramInfo, { u_matrix: m4.identity(), u_texture: fadeFbi1.attachments[0], u_mixAmount: mixAmount, }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo); if (drawRect) { drawRect = false; // now draw new stuff to fadeFb2. Notice we don't clear! twgl.bindFramebufferInfo(gl, fadeFbi2); var rotation = rand(Math.PI); var scale = rand(10, 20); var color = [rand(1), rand(1), rand(1), 1]; drawThing(gl, rectX, rectY, rotation, scale, color); } // now use fadeFbi2 as a displacement while drawing tex to the canvas twgl.bindFramebufferInfo(gl, null); 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 * 0.5, img.height * 0.5, 1]); currentMatrix = mat; gl.useProgram(displaceProgramInfo.program); twgl.setBuffersAndAttributes(gl, displaceProgramInfo, quadBufferInfo); twgl.setUniforms(displaceProgramInfo, { u_matrix: mat, u_texture: tex, u_displacementTexture: fadeFbi2.attachments[0], u_displacementRange: [0.05, 0.05], }); twgl.drawBufferInfo(gl, gl.TRIANGLES, quadBufferInfo); // swap the variables so we render to the opposite textures next time var temp = fadeFbi1; fadeFbi1 = fadeFbi2; fadeFbi2 = temp; requestAnimationFrame(render);}requestAnimationFrame(render);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 clipspacePoint = [x / target.width * 2 - 1, -(y / target.height * 2 - 1), 0]; var s = m4.transformPoint(rmat, clipspacePoint); // s is now a point in the space of the image's quad. The quad goes -1 to 1 // and we're going to draw into it using pixels because drawThing takes // a pixel value and our displacement map is the same size as the canvas drawRect = true; rectX = ( s[0] * .5 + .5) * gl.canvas.width; rectY = (-s[1] * .5 + .5) * gl.canvas.height;});body { margin: 0; }canvas { display: block; width: 100vw; height: 100vh; }<script src="https://twgljs.org/dist/twgl-full.min.js"></script><canvas></canvas>Getting the exact effect of your second example looks like it's running the displacement through some kind of noise function. You could use something like the WebGL Inspector or the Shader Editor to look inside the shaders and see what they're doing.Here's another example that creates a displacement texture that displaces more toward the center than the edge. NOTE: I should make it clear I didn't look at the details of how the examples you linked to worked, I'm only suggesting they are doing something similar to this. The best way to find out what they're really doing is to look at their code and run the tools mentioned in the previous paragraphs to look inside and see what's going on. Maybe they aren't using direct displacement but instead using something like normals as displacements. Maybe instead of drawing a solid color (the 2nd and 3rd examples) or a texture (the 4th example), they're drawing with a procedurally generated pattern or using screen based texture coordinates for a repeating texture pattern. Maybe the displacement texture is a separate texture and they have a "mix mask" that they draw in white and fade to black to decide how much of the displacement texture to apply. There is an infinite number of ways to do things in WebGL. 这篇关于在鼠标移动中创建涂抹/液化效果,使用webgl连续动画回原始状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
09-22 09:21