项目地址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相应份量会获得改变,从而实现透视的效果~