WebGL——粒子化圖片

項目地址html

思路

最近在學習webGL原生,給的3d項目又不是不少,就想到了其實2d的項目也能夠拿webGL練習,在接到這個項目以後,腦子裏蹦出個要作出粒子的效果,大體思路已經有了,就是獲取圖片的有效像素做爲point的xy座標,z軸的不一樣來實現3d粒子效果,順着思路往下想,透視投影的話那我生成xy座標還得和z作一個透視得逆轉變,使他們在透視投影得時候仍是看到的是正常的圖片,但發現有兩個問題,一是我這個數學漿糊來回倒騰逆矩陣太迷糊,二就是在透視投影中z軸不一樣尺寸不一樣,意思就是遠小近大,致使每一個粒子得大小也就不一樣,因此我換了種思路,簡單易懂那就是正交投影切透視投影web

想到正交投影得特性徹底就能夠知足我把粒子排列成圖案,又想到透視投影能夠把粒子打散,我得思路就有了:canvas

| 生成座標 —— 正交透視切換bash

效果: markdown

實現

1.生成座標

這裏的想法是運用canvas得getImageData 讀取有效像素(alpha!=0),生成x,y以及隨機得z座標,並結合像素顏色生成buffer供webGL引用。dom

getImageData 也有一些小技巧,例如能夠實現碰撞檢測
複製代碼

代碼以下:oop

// image 已經準備好了的
const data = [];
const canvas = <HTMLCanvasElement>document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
ctx.drawImage(image,0,0);
// 獲取像素數據
const imageData = ctx.getImageData(0,0,canvas.width,canvas.height);
//以中心點定位
const offsetX = imageData.width/2;
const offsetY = imageData.height/2;
for(let x = 0;x<imageData.width;x++){
    for(let y = 0;y<imageData.height;y++){
        const r = x*4+y*imageData.width*4;
        //過濾無效像素 減少數據量
        if(imageData.data[r+3]){
            //buffer中記錄了座標軸和顏色值
            data.push(
                x-offsetX,                  //x
                -(y-offsetY),               //y
                (Math.random()-.5)*offsetX, //z
                imageData.data[r+0]/255,    //r
                imageData.data[r+1]/255,    //g
                imageData.data[r+2]/255,    //b
                imageData.data[r+3]/255,    //a
            );
        }
    }
}
複製代碼

接下來用這些數據傳給webGL(常規的建立shader,program不作贅述,參照手擼3d賀卡):post

//獲取attribute
    aPosition = gl.getAttribLocation(program,'aPosition');
    gl.enableVertexAttribArray(aPosition);
    aColor = gl.getAttribLocation(program,'aColor');
    gl.enableVertexAttribArray(aColor);
    
    //用做世界座標轉成齊次座標
    uViewPort = gl.getUniformLocation(program,'uViewPort');
    gl.uniform2f(uViewPort,window.innerWidth,window.innerHeight);
    
    //爲gl.ARRAY_BUFFER寫入上述數據data,以及設置attribute讀取數據
    gl.bindBuffer(gl.ARRAY_BUFFER,gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);
    gl.vertexAttribPointer(aPosition,3,gl.FLOAT,false,7*4,0);
    gl.vertexAttribPointer(aColor,3,gl.FLOAT,false,7*4,3*4);
    gl.drawArrays(
        gl.POINTS,
        0,
        data.length/7   //除以7是由於每一個頂點我寫的七個數據(xyz,rgba)
    );
複製代碼

這時的着色器很簡單(由於是gl.POINTS模式因此要設置gl_PointSize):學習

//___________________頂點着色器_________________________
    precision mediump float;
    attribute vec3 aPosition;
    attribute vec4 aColor;
    uniform vec2 uViewPort;
    varying vec4 vColor;
    void main(void) {
        gl_Position = vec4(aPosition/uViewPort.xyx, 1);
        gl_PointSize = 1.0;         //須設置
        vColor = aColor;
    }
//___________________片元着色器_________________________
    varying vec4 vColor;
    void main(void) {
        gl_FragColor = vColor;
    }
複製代碼

到這裏的效果其實就是一張靜態圖片的樣子了,可是本質上是每一個像素實際上是3d的一個點,不作截圖了。spa

2.正交矩陣切透視矩陣

在1中我沒有寫代碼意義上的正交矩陣,其實不寫的話自己就能夠當作一個 x,y,z從(-1,1)的正交投影,因此沒寫,在這一步中要用到透視投影,因此爲上述代碼作了些調整,把上述代碼中的uViewPort替換成矩陣便可,剩下的就是對矩陣作修改,修改以下:

//___________________TypeScript_________________________
    import * as Matrix from 'gl-mat4';
    const ortho = Matrix.create();
    Matrix.ortho(
        ortho,
        -canvas.width/2,
        canvas.width/2,
        -canvas.height/2,
        canvas.height/2,
        -canvas.width/2,
        canvas.width/2
    );
    worldViewProjection = gl.getUniformLocation(program,'worldViewProjection');
    gl.uniformMatrix4fv(worldViewProjection,false,ortho);
    
//___________________頂點着色器_________________________
    precision mediump float;
    attribute vec3 aPosition;
    attribute vec4 aColor;
    uniform mat4 worldViewProjection;
    varying vec4 vColor;
    void main(void) {
        gl_Position = worldViewProjection*vec4(aPosition, 1);
        gl_PointSize = 1.0;         //須設置
        vColor = aColor;
    }
複製代碼

根據openGL透視除法的特性,我在每次渲染的時候只須要動態調整ortho[11]就能調整透視程度,當且僅當ortho[11]==0的時候此投影爲正交投影。

render();
function render(time){
    //更新ortho
    ortho[11] = (Math.cos(time/1000)+1)/50;
    gl.uniformMatrix4fv(worldViewProjection,false,ortho);
    //清空
    gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
    //渲染
    gl.drawArrays(gl.POINTS,0,data.length/7);
    requestAnimationFrame(render);
}
複製代碼

效果以下

剩下的就自由發揮了,由於z軸是隨機的,還有投影矩陣中[11]也是有規律的變化,在頂點着色器中能夠根據這兩個參數進行相應的簡單計算。

個人計算是:

//___________________頂點着色器_________________________
    precision mediump float;
    attribute vec3 aPosition;
    attribute vec4 aColor;
    uniform mat4 worldViewProjection;
    varying vec4 vColor;
    varying float vAlpha;
    void main(void) {
        gl_Position = worldViewProjection * vec4(aPosition, 1.0);
        vAlpha = 1.-pow(worldViewProjection[3].b*50.,2.);
        gl_Position.xy += gl_Position.z*pow((1.-vAlpha)*gl_Position.w,gl_Position.w);
        gl_PointSize = 1.0;
        vColor = aColor;
    }
//___________________片元着色器_________________________
    varying vec4 vColor;
    varying float vAlpha;
    void main(void) {
        gl_FragColor = vColor*vAlpha;
    }
複製代碼

最終造成的效果有向左下發射的效果

3.芝士店 —— 透視除法

透視除法只是將齊次座標中的 W 份量轉換爲1的專用名詞

在渲染管線中處於光柵化階段內,因此是自動執行的,爲了找透視除法的執行位置我也是搜了很久。。

相關渲染管線請查看opengl圖形管線,文中所述的爲openGL es3,不過大體是差很少的,能夠參考下

下圖中左側的投影矩陣是我在這個項目中的模型,主要是矩陣中第四行第三個位置由0~other值切換,就會致使原有座標的w份量不爲1,不爲1以後通過光柵化階段中的透視除法後,其他xyz相應份量會獲得改變,從而實現透視的效果~

相關文章
相關標籤/搜索