項目地址html
最近在學習webGL原生,給的3d項目又不是不少,就想到了其實2d的項目也能夠拿webGL練習,在接到這個項目以後,腦子裏蹦出個要作出粒子的效果,大體思路已經有了,就是獲取圖片的有效像素做爲point的xy座標,z軸的不一樣來實現3d粒子效果,順着思路往下想,透視投影的話那我生成xy座標還得和z作一個透視得逆轉變,使他們在透視投影得時候仍是看到的是正常的圖片,但發現有兩個問題,一是我這個數學漿糊來回倒騰逆矩陣太迷糊,二就是在透視投影中z軸不一樣尺寸不一樣,意思就是遠小近大,致使每一個粒子得大小也就不一樣,因此我換了種思路,簡單易懂那就是正交投影切透視投影。web
想到正交投影得特性徹底就能夠知足我把粒子排列成圖案,又想到透視投影能夠把粒子打散,我得思路就有了:canvas
| 生成座標 —— 正交透視切換bash
效果: dom
這裏的想法是運用canvas得getImageData
讀取有效像素(alpha!=0),生成x,y以及隨機得z座標,並結合像素顏色生成buffer供webGL引用。post
getImageData 也有一些小技巧,例如能夠實現碰撞檢測
複製代碼
代碼以下:學習
// 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賀卡):ui
//獲取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
):spa
//___________________頂點着色器_________________________
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的一個點,不作截圖了。.net
在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相應份量會獲得改變,從而實現透視的效果~