故障效果
此篇文章介绍如何使用webgl实现一个下面这样子的故障效果,因为屏幕录制的原因这个gif图片看起来不是那么清晰,感兴趣的同学可以看完文章后动手实践一下,亲自看一下效果。
第一步:将图片渲染出来
为了达到最后的这种故障效果,我们先要将图片渲染出来,也就是实现基本的shader功能(此部分不会着重介绍)
1.1 导入相关配置文件
<script src="./lib/cuon-matrix.js"></script>
<script src="./lib/cuon-utils.js"></script>
<script src="./lib/webgl-debug.js"></script>
<script src="./lib/webgl-utils.js"></script>
这四个文件是关于webgl的方法封装,数学运算以及debug的文件。
1.2 顶点着色器和片元着色器
// 顶点着色器
let VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}`
// 片元着色器
let FSHADER_SOURCE = `
precision mediump float;
uniform sampler2D u_Sampler;
uniform float time;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}`
在顶点着色器中我们定义了三个变量,a_Position是用作处理顶点坐标,a_TexCoord和v_TexCoord用来向片元着色器传递数据。
而在片元着色器中我们使用v_TexCoord接收顶点着色器传过来的数据,使用u_Sampler处理纹理数据,预留了一个time变量用作后面的动效变量。
1.3 初始化并渲染纹理
let canvas = this.$refs.webgl //获取canvas
let gl = getWebGLContext(canvas); //获取canvas的上下文
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE); //初始化shader,引入的js文件中函数
let n = this.initVertexBuffers(gl); //初始化buffer,执行函数可在全部代码中查看
this.inirTextures(gl, n); //初始化textures,执行函数可在全部代码中查看
let u_time = gl.getUniformLocation(gl.program, "time"); //获取片元着色器中时间的存储地址
let newTime = 0.1; //时间变量的起始值
let draw = function(){
newTime = newTime + 0.1; //时间变量增值
gl.uniform1f(u_time, newTime); //进行赋值
gl.clearColor(0.0, 0.0, 0.0, 1.0); //清空背景
gl.clear(gl.COLOR_BUFFER_BIT); //清空buffer
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); //开始绘制
requestAnimationFrame(draw); //不断的执行动画
}
draw() //开始执行
上面的代码中主要涉及到了初始化wegl的操作,其中的initShaders是1.1引入的js文件中的函数,而initVertexBuffers和inirTextures函数是我们自定义的,具体的内容可以在最后面的全部代码中看到,并且我们这里向片元着色器不断的传入newTime变量的值,用来作为变换参数。 当上面的操作完成以后我们就可以得到纹理正确渲染的样式了:
第二步:确定我们要使用的变换函数
我们知道在webgl中我们一个函数要操作的是整个图片上面的所有的像素,所以我们要选定一个合适的函数来整体操作图片,那么为了让你不那么烧脑,我们来一点点的理解:
我们首先使用dot函数对两个vec2做了点积操作,其函数图像就是上面这样,需要注意这不是一条直线,只是因为y值变化较小而已。那么为了让y轴的变化明显,我们在外面添加了一个sin函数,这样函数图像就有一点意思了,但是密度还是不够。
为了增加函数的密度,我们在sin函数的后面乘上了43758.0(为什么是这个值?后面调出来的),但是新的问题来了,函数的值域过大。
为了减少值域的范围我们使用fract函数,将函数的值域控制在在-1.0到1.0之间。
最后我们再对函数做了调整,那么就成了这个样子是不是感觉只是很多随机的点,其实不是的,放大它的一部分来看一下。经过上面一点点的添加,我们就得到了一个函数公式,接下来就是将这个函数公式应用在片元着色器中了。
第三步:片元着色器使用函数
在片元着色器中我们先定义两个函数来做第二步中的变换函数。
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.0, 78.0))) * 43758.0) * 2.0 - 1.0;
}
float offset(float blocks, vec2 uv) {
return rand(vec2(time, floor(uv.y * blocks)));
}
在offset函数中我们使用time这个变量来作为第二步函数中的第一个x值,使用输入uv的y值也就是纵坐标来作为第二步函数中的第二个x值(因为我们要做横向的故障效果),上面的函数定义完成以后就是将这个函数应用在texture中了。
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
gl_FragColor.r = texture2D(u_Sampler, v_TexCoord + vec2(offset(16.0, v_TexCoord) * 0.03, 0.0)).r;
gl_FragColor.g = texture2D(u_Sampler, v_TexCoord + vec2(offset(8.0, v_TexCoord) * 0.03 * 0.07, 0.0)).g;
gl_FragColor.b = texture2D(u_Sampler, v_TexCoord + vec2(offset(8.0, v_TexCoord) * 0.03, 0.0)).b;
}
本身在main函数中只是存在gl_FragColor赋值的操作,但是在上面的代码中我们添加了对其RGB三个通道的变化操作,经过上面的一步步后,我们就可以在自己的浏览器中看到效果了。
在最后为了方便你查看效果我这边赋上所有的代码,但是需要你自己去引入1.1中的4个js文件,需要注意我这边的代码是写在vue中的。
<template>
<div>
<canvas id="glcanvas" ref="webgl" width="750" height="1334"></canvas>
</div>
</template>
<script>
/* eslint-disable */
import testImg from './static/img/img1.jpeg' //引入自己的本地图片文件
export default {
props: {
msg: String
},
mounted() {
let VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}`
let FSHADER_SOURCE = `
precision mediump float;
uniform sampler2D u_Sampler;
uniform float time;
varying vec2 v_TexCoord;
float rand(vec2 co){
return fract(sin(dot(co.xy ,vec2(12.0, 78.0))) * 43758.0) * 2.0 - 1.0;
}
float offset(float blocks, vec2 uv) {
return rand(vec2(time, floor(uv.y * blocks)));
}
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
gl_FragColor.r = texture2D(u_Sampler, v_TexCoord + vec2(offset(16.0, v_TexCoord) * 0.03, 0.0)).r;
gl_FragColor.g = texture2D(u_Sampler, v_TexCoord + vec2(offset(8.0, v_TexCoord) * 0.03 * 0.07, 0.0)).g;
gl_FragColor.b = texture2D(u_Sampler, v_TexCoord + vec2(offset(8.0, v_TexCoord) * 0.03, 0.0)).b;
}`
let canvas = this.$refs.webgl
let gl = getWebGLContext(canvas);
initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
let n = this.initVertexBuffers(gl);
this.inirTextures(gl, n);
let u_time = gl.getUniformLocation(gl.program, "time");
let newTime = 0.1;
let draw = function(){
newTime = newTime + 0.1;
gl.uniform1f(u_time, newTime);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
requestAnimationFrame(draw);
}
draw()
},
methods: {
initVertexBuffers(gl){
var verticesTexCoords = new Float32Array([
-1.0, 1.0, 0.0, 1.0,
-1.0, -1.0, 0.0, 0.0,
1.0, 1.0, 1.0, 1.0,
1.0, -1.0, 1.0, 0.0,
]);
var n = 4;
var vertexCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position);
var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord)
return n;
},
inirTextures(gl, n){
var texture = gl.createTexture();
var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
var image = new Image();
image.onload = ()=>{this.loadTexture(gl, n, texture, u_Sampler, image);};
image.crossOrigin = "anonymous";
image.src = testImg
return true;
},
loadTexture(gl, n, texture, u_Sampler, image){
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.uniform1i(u_Sampler, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
}
}
}
</script>