項目地址html
最近在學習webGL原生,給的3d項目又不是不少,就想到了其實2d的項目也能夠拿webGL練習,在接到這個項目以後,腦子裏蹦出個要作出粒子的效果,大體思路已經有了,就是獲取圖片的有效像素做爲point的xy座標,z軸的不一樣來實現3d粒子效果,順着思路往下想,透視投影的話那我生成xy座標還得和z作一個透視得逆轉變,使他們在透視投影得時候仍是看到的是正常的圖片,但發現有兩個問題,一是我這個數學漿糊來回倒騰逆矩陣太迷糊,二就是在透視投影中z軸不一樣尺寸不一樣,意思就是遠小近大,致使每一個粒子得大小也就不一樣,因此我換了種思路,簡單易懂那就是正交投影切透視投影。web
想到正交投影得特性徹底就能夠知足我把粒子排列成圖案,又想到透視投影能夠把粒子打散,我得思路就有了:canvas
| 生成座標 —— 正交透視切換bash
效果: markdown
這裏的想法是運用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
在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; } 複製代碼
最終造成的效果有向左下發射的效果
透視除法只是將齊次座標中的 W 份量轉換爲1的專用名詞
在渲染管線中處於光柵化階段內,因此是自動執行的,爲了找透視除法的執行位置我也是搜了很久。。
相關渲染管線請查看opengl圖形管線,文中所述的爲openGL es3,不過大體是差很少的,能夠參考下
下圖中左側的投影矩陣是我在這個項目中的模型,主要是矩陣中第四行第三個位置由0~other值切換,就會致使原有座標的w份量不爲1,不爲1以後通過光柵化階段中的透視除法後,其他xyz相應份量會獲得改變,從而實現透視的效果~