webgl實現故障效果

騰訊DeepOcean發表於2019-11-18

故障效果

此篇文章介紹如何使用webgl實現一個下面這樣子的故障效果,因為螢幕錄製的原因這個gif圖片看起來不是那麼清晰,感興趣的同學可以看完文章後動手實踐一下,親自看一下效果。

webgl實現故障效果

第一步:將圖片渲染出來

為了達到最後的這種故障效果,我們先要將圖片渲染出來,也就是實現基本的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實現故障效果

第二步:確定我們要使用的變換函式

我們知道在webgl中我們一個函式要操作的是整個圖片上面的所有的畫素,所以我們要選定一個合適的函式來整體操作圖片,那麼為了讓你不那麼燒腦,我們來一點點的理解:

webgl實現故障效果
我們首先使用dot函式對兩個vec2做了點積操作,其函式影像就是上面這樣,需要注意這不是一條直線,只是因為y值變化較小而已。

webgl實現故障效果

那麼為了讓y軸的變化明顯,我們在外面新增了一個sin函式,這樣函式影像就有一點意思了,但是密度還是不夠。

webgl實現故障效果

為了增加函式的密度,我們在sin函式的後面乘上了43758.0(為什麼是這個值?後面調出來的),但是新的問題來了,函式的值域過大。

webgl實現故障效果

為了減少值域的範圍我們使用fract函式,將函式的值域控制在在-1.0到1.0之間。

webgl實現故障效果
最後我們再對函式做了調整,那麼就成了這個樣子是不是感覺只是很多隨機的點,其實不是的,放大它的一部分來看一下。

webgl實現故障效果

經過上面一點點的新增,我們就得到了一個函式公式,接下來就是將這個函式公式應用在片元著色器中了。

第三步:片元著色器使用函式

在片元著色器中我們先定義兩個函式來做第二步中的變換函式。

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>
複製程式碼

相關文章