故障效果
此篇文章介紹如何使用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>
複製程式碼