PhiloGL学习(1)——场景创建及方块欲露还羞出水面
前言
上一篇文章中介绍了我认识PhiloGL框架的机缘以及初步的探讨(见JS前端三维地球渲染——中国各城市航空路线展示),在此文中仅仅对此框架进行了简单介绍并初步介绍了一些该框架的知识。首先三维这个东西本身涉及的技术和知识点就非常多,我也基本属于初次接触;其次学习也需要过程,需要一点点积累,不积跬步无以至千里。
这几天天天加班,但是也利用空闲时间学习了些此框架的基础知识,本文为大家介绍如何创建一个简单的二维场景。
一、 HTML部分
PhiloGL采用canvas来加载三维模型,所以只有在支持HTML5的浏览器才能正常显示PhiloGL的东西。
1.1 添加canvas组建
在html页面的body中添加:
<canvas id="test1" style="border: none;" width="500" height="500"></canvas>
1.2 添加PhiloGL引用
<script type="text/javascript" src="/path/to/PhiloGL.js"></script>
1.3 引用自定义js文件
在此文件中我们会写创建场景逻辑等等,将此文件同样添加到html页面中。
二、 GLSL部分
GLSL(GL Shading Language)是用来在OpenGL中着色的语言,GLSL语言在GPU上执行,PhiloGL也使用GLSL语言进行着色。具体可以查阅相关资料,这里作简单介绍。
GLSL分为两部分,fragment shading(fs) 和 vertext shading(vs),分别为片段着色器和顶点着色器。
2.1 fs.glsl 部分简单定义如下:
#ifdef GL_ES
precision highp float;
#endif
varying vec4 vColor;
void main(void) {
gl_FragColor = vColor;
}
2.2 vs.glsl 部分简单定义如下:
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec4 vColor;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = aVertexColor;
}
2.3 GLSL 语法简介
- 变量类型
变量的类型有 void、bool、int、float、vec2、vec3、vec4、mat4等。vec3表示三维、vec4表示4维,mat4表示4*4矩阵。
- attribute
attribute 表示只读的顶点数据,只用在顶点着色器中,即只存在vs中,它必须是全局范围声明的,不能在函数内部。
- varying
varying 表示顶点着色器的输出数据,作为片段着色器的只读输入数据,即在vs中设置后可以在fs中为作为常量使用。例如颜色或纹理坐标,纹理在后面介绍。
- uniform
uniform 表示一致变量,在着色器执行期间一致变量的值是不变的,由外部初始化。一致变量在fs和vs中是共享的,多用于设置摄像头的视角和投影等。它也只能是全局变量。
- 固定常量
- 片段着色器
gl_FragColor 输出的颜色用于随后的像素操作。可以采用上述变量的方式,也可以直接设置固定值。如下:
gl_FragColor = vec4(0.4, 0.5, 0.6, 0.7);
这样使用此fs.glsl的对象就会被设置成此颜色。颜色值小于1为rgba。
- 顶点着色器
gl_Position 输出属性,变换后顶点的位置,用于固定裁剪等操作,所有的顶点着色器必须设置此值。
由于aVertexPosition为vec3,所以vec4(aVertexPosition, 1.0)为根据外部传入的aVertexPosition创建一个vec4变量,第四个值为1.0。
还有一些其他变量及其他常量和函数等,可以自行查阅,后续用到的时候也会再做相应介绍。
2.4 GLSL存放位置
GLSL可以直接以javascript代码块的方式给出,也可以以文件的方式给出。
- 代码块方式
在html文件中添加如下代码块:
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
varying vec4 vColor;
void main(void) {
gl_FragColor = vColor;
}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying vec4 vColor;
void main(void) {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = aVertexColor;
}
</script>
注意两段代码的type类型,分别对应fs和vs。
- 文件方式
分别定义xxx.fs.glsl文件和xxx.vs.glsl文件,将2.1和2.2部分的内容放入其中即可。
以代码块方式和文件方式在调用上会有不同,后面会具体介绍。
三、 自定义JS部分
PhiloGL采用纯javascript语言来渲染三维场景。
3.1 the least demo
使用下面的代码即可创建一个最基本的demo:
function webGLStart() {
PhiloGL('test1', {
program: {
from: 'ids',
vs: 'shader-vs',
fs: 'shader-fs'
},
onError: function(e) {
alert(e);
},
onLoad: function(app) {
var gl = app.gl,
canvas = app.canvas,
program = app.program,
camera = app.camera;
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
program.setBuffers({
'square': {
attribute: 'aVertexPosition',
value: new Float32Array([1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]),
size: 3
},
'squareColors': {
attribute: 'aVertexColor',
value: new Float32Array([0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 1, 1, 1, 1]),
size: 4
}
});
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
camera.view.id();
camera.view.$translate(0, 0, -7);
//Draw Square
//set uniforms
program.setUniform('uMVMatrix', camera.view);
program.setUniform('uPMatrix', camera.projection);
program.setBuffer('square');
program.setBuffer('squareColors');
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
});
}
在html文件中添加对此js文件的引用,并在body的onload事件中调用webGLStart函数即可看到在浏览器中加载出一个四边形。
<body onload="webGLStart();">
<canvas id="test1" style="border: none;" width="500" height="500"></canvas>
</body>
接下来介绍PhiloGL类各部分的意义。
3.2 PhiloGL
PhiloGL是框架的顶级类,在其中定义了三维场景的所有模块,如摄像机、场景、GLSL加载、键盘鼠标响应事件等等。
- 与canvas的对应
PhiloGL传入的第一个参数(上文中的test1),即为html页面中的canvas定义的id,PhiloGL根据此值来找到canvas加载三维场景。
- program部分
program部分可以加载多个GLSL语言模块,每一个均有一个vs和一个fs组成。如下:
program : [ {
id : 'advance',
from : 'ids',
vs : 'shader-vs',
fs : 'shader-fs-advance'
}]
其id是为此部分GLSL定义的id,from表示加载来源,vs为对应的vs部分,fs为对应的fs部分。
from可以为ids或者uris,ids表示从script中取,uris表示从文件中取。当设置为uris的时候,需要添加一个path项,用于设置glsl文件存放路径。
- onLoad
onLoad部分控制整个三维场景的加载。
onLoad: function(app) {
var gl = app.gl,
canvas = app.canvas,
program = app.program,
camera = app.camera;
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
gl.clearDepth(1);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
program.setBuffers({
'square': {
attribute: 'aVertexPosition',
value: new Float32Array([1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]),
size: 3
},
'squareColors': {
attribute: 'aVertexColor',
value: new Float32Array([0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 1, 1, 1, 1]),
size: 4
}
});
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
camera.view.id();
camera.view.$translate(0, 0, -7);
//Draw Square
//set uniforms
program.setUniform('uMVMatrix', camera.view);
program.setUniform('uPMatrix', camera.projection);
program.setBuffer('square');
program.setBuffer('squareColors');
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
app表示整个三维场景对象,gl表示philogl对象,program就是本小节第一部分设置的program即GLSL部分,所以program主要设置的是GLSL中的变量。
program.setBuffers({
'square': {
attribute: 'aVertexPosition',
value: new Float32Array([1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]),
size: 3
},
'squareColors': {
attribute: 'aVertexColor',
value: new Float32Array([0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 0.5, 0.5, 1, 1, 1, 1, 1, 1]),
size: 4
}
});
此段代码完成了aVertexPosition和aVertexColor两个attribute变量的设置。其中attribute值即为glsl中定义的attribute变量名称,vlaue表示设置的值,size表示变量的尺寸,如果变量类型是vec3则为3、vec4则为4。设置的value为new Float32Array类型,如果为33即9个值且size为3则表示3组值(即三个顶点),如果为44即16个且size为4,则表示为4组值(四个顶点)。
下面与之对应的setBuffer表示对当前对象设置此变量值,因为同一个场景中可以创建多个对象,不同的对象可以使用相同的GLSL语言进行控制,那么就要为这些对象的相同变量设置不同的值,这样就可以通过setBuffer来控制某个对象的变量值。
program.setBuffer('square');
program.setBuffer('squareColors');
通过setUniform设置GLSL中的uniform变量。
program.setUniform('uMVMatrix', camera.view);
program.setUniform('uPMatrix', camera.projection);
此处设置的两个变量均与摄像机(camera)有关,所谓摄像机的概念是说假设现在有个实体场景存在这你要绘制出的对象,那么当我们将摄像机放置在不同位置的时候摄像机拍摄到的场景是不同的,所以此处的摄像机的概念同样如此,表示我们从哪个角度(点)来观察这个对象。当然采用这种方式,每一个对象均需要一个摄像机对其进行拍摄使我们能够正常看到此对象。
camera.view表示摄像机视角,就是摄像机从哪个位置拍摄此物体。
camera.projection表示投影矩阵,简单的说就是一个三维点如何投影在二维平面上。因为摄像机拍摄的对象最终反映到摄像机的镜头里是在一个平面上,这中间就存在投影的问题。公式为:
y ~ Cx
其中x是一个三维的点,C为投影矩阵,y就是投影的结果(二维平面中的点)。
从vs中我们也能看出这一点:
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
uPMatrix传入的就是camera.projection,相当于C矩阵,vec4(aVertexPosition, 1.0)表示三维位置,二者相乘得到物体三维点在摄像机中的投影,再乘以uMVMatrix矩阵,将其从摄像机平面再投影到我们所看的这个平面,这样我们便能看到此物体。
camera.view.$translate表示此摄像机相对起点的偏移。原始位置在(0,0,0)点。
当场景和对象均设置好后,即可进行绘制。
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
注意此处的第三个参数3和4,此值表示绘制的对象是几边形。
类型有 TRIANGLES
, TRIANGLE_STRIP
, POINTS
, LINES
。
TRIANGLES绘制三角形、TRIANGLE_STRIP绘制多边形、POINTS绘制点、LINES绘制线
四、 总结
本文简单介绍了PhiloGL框架如何上手、GLSL语言以及简单的绘制一个方块,当然可能有很多我理解错误或者不深刻的地方欢迎各位大神批评指正!后面一篇文章为大家介绍如何将这个方块动起来。