写在前面
上一篇笔记中在顶点着色器源程序中写死了gl_Position
,想要修改点的位置就得修改着色器源程序,这当然没什么意义,我们需要一种足够灵活的方式,比如attribute变量
一.封装util
上一篇笔记中我们为了绘制一个假矩形(一个略大的点),写了70多行代码,还只包含了错误检查,兼容性处理还没有做。每一步都在调用WebGL原生API,虽然流程很清晰,但实在太麻烦,1个点70行,画两个点140行可不行,所以,先动手封装util
开始之前,需要先注意几个问题:
1.getContext的兼容性问题
getContext('webgl')
是标准方式,但在低版本浏览器中需要传入不同的字符串,如下:
/**
* Creates a webgl context.
* @param {!Canvas} canvas The canvas tag to get context
* from. If one is not passed in one will be created.
* @return {!WebGLContext} The created context.
*/
var create3DContext = function(canvas, opt_attribs) {
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
var context = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
context = canvas.getContext(names[ii], opt_attribs);
} catch(e) {}
if (context) {
break;
}
}
return context;
}
以上代码摘自google团队的webgl-utils.js,除create3DContext
外还提供了requestAnimationFrame
的兼容版本,后面动画也用得到,所以我们的util就直接include webgl-utils.js了
2.保留program对象的引用
gl.createProgram()
返回的program对象不仅在初始化着色器的过程中会用到,在我们即将要进行的attribute变量传值中也要用,program对象是js和着色器内部通信的桥梁,但问题是WebGL没有提供gl.getProgram()
这样的getter,所以在封装util时一定要保留program对象的引用,供外部使用,例如:
function initShaders(vsSrc, fsSrc) {
var program = _createProgram(vsSrc, fsSrc);
if (!program) {
log('Failed to create program');
return false;
}
gl.useProgram(program);
// Save program object
//!!! because there is no getter like gl.getProgram()
glUtil.program = program;
return true;
}
以上代码摘自笔者的gl-util.js,需要的话可以直接拿去用,接口说明如下:
// 全局变量:glUtil
return {
program: null, // runtime assignment
getContext: getContext,
initShaders: initShaders,
debug: function(onOrOff) {
if (typeof onOrOff === 'boolean') {
debug = onOrOff;
}
}
};
二.术语及API解释
1.着色器
着色器分为顶点着色器和片元着色器:
顶点着色器
用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点
片元着色器
进行逐片元处理过程(如光照)的程序。片元是一个WebGL术语,可以简单理解为像素(图像的单元)
P.S.实际上片元表示显示在屏幕上的一个像素,还包括这个像素点的位置、颜色和其他信息
2.顶点着色器源程序
用GLSL ES写的程序,以字符串形式传入WebGL系统内部
// 顶点着色器源程序
var vsSrc = 'void main() {' +
'gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // 设置坐标
'gl_PointSize = 200.0;' + // 设置尺寸
'}';
其中,给gl_Position
赋值是必须的,否则着色器无法正常工作,gl_Position
是vec4
类型的,即四维向量(齐次坐标,最后一个分量一般取1.0)
gl_PointSize
有默认值1.0,可以不必赋值,gl_PointSize
是float
类型的,不接受3f
(C语言中3f表示浮点数3)这样的形式,而且只在绘制孤立点时生效(什么叫绘制孤立点,我们后面再说)
3.片元着色器源程序
和顶点着色器源程序一样,但控制的是片元着色器
// 片元着色器源程序
var fsSrc = 'void main() {' +
'gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75);' + // 设置颜色
'}';
gl_FragColor
是唯一的内置变量,也是vec4
类型,但表示rgba色值,rgba的取值都是0~1的,不同于CSS中rgb是0~255的
4.绘制操作
绘制操作只有一个接口,但功能很强大:
// 绘制矩形(一个点,但点的尺寸略大)
gl.drawArrays(gl.POINTS, 0, 1);
API具体信息如下:
gl.drawArrays(mode, first, count)
mode指定绘制方式,接受常量:
---
gl.POINTS 孤立点(v0, v1...)
gl.LINES 孤立线段(v0v1, v2v3...)
gl.LINE_STRIP 连续线段(v0v1, v1v2...)
gl.LINE_LOOP 连续线段连成圈(v0v1, v1v2...vnv0)
gl.TRIANGLES 孤立三角形(v0v1v2, v3v4v5...)
gl.TRIANGLE_STRIP 连续三角带(v0v1v2, v2v1v3, v2v3v4...),用来构造复杂模型,如球、树
注意:第二个三角形是v2v1v3,不是v1v2v3,这是为了保证第二个三角形的绘制也按照逆时针顺序
gl.TRIANGLE_FAN 三角扇(v0v1v2, v0v2v3, v0v3v4),也可以用来构造复杂模型,但在实际应用中不常见,因为需要求出所有三角形的公共顶点v0
first指定从哪个顶点开始绘制
---
0表示从第一个开始,配合缓冲区对象使用(缓冲区对象中可以存放多个顶点信息)
count指定需要绘制点的个数,同样配合缓冲区对象使用
---
调用gl.drawArray时,顶点着色器会被执行count次,每次处理一个顶点,顶点着色器执行完毕后片元着色器开始执行,给片元涂色(中间还有光栅化的过程)
特别注意:顶点着色器逐顶点执行,片元着色器逐片元执行,并不是只执行一次
三.attribute变量
在着色器源程序中声明一个变量,然后js给这个变量传值,再绘制出来,就实现了动态修改点的位置,简单地说就是这样
1.声明attribute变量
// 顶点着色器源程序
var vsSrc = 'attribute vec4 a_Position;' +
'void main() {' +
'gl_Position = a_Position;' + // 设置坐标
'gl_PointSize = 200.0;' + // 设置尺寸
'}';
// 片元着色器源程序
//...不变
类似于C语言的变量声明方式,attribute
叫存储限定符,vec4
是变量类型。变量名a_Position
中a_
前缀表示变量是attribute变量,当然这个只是习惯,建议采用
P.S.为什么在顶点着色器源程序中声明attribute变量,而不是在片元…因为我们想要动态修改点的坐标,坐标信息来自顶点着色器源程序中的gl_Position
,和片元着色器没关系
注意:只有顶点着色器才能使用attribute变量,片元着色器中需要使用uniform变量,此外还有varying变量,具体区别在GLSL ES笔记中再详细说明
2.初始化着色器
直接调用util,1行代码就能完成剩下的7件事(从createShader
到useProgram
的7个步骤)
// 1.初始化着色器
glUtil.initShaders(vsSrc, fsSrc);
3.给attribute变量赋值
变量赋值分为2步,先获取存储位置,再赋值
// 2.给attribute变量赋值
// 获取attribute变量的存储位置
var a_Position = gl.getAttribLocation(glUtil.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
// 把顶点位置传递给attribute变量
gl.vertexAttrib3f(a_Position, 0.5, 0.0, 0.0);
4.绘制
// 绘制点
gl.drawArrays(gl.POINTS, 0, 1);
第一个参数传入gl.POINTS
表示绘制孤立点,还可以绘制线段、三角形等等,以后再说
四.DEMO
包含上述代码的完整的例子,请查看:http://www.ayqy.net/temp/webgl/attribute/index.html
五.总结
现在我们已经可以动态修改顶点坐标了,当然,这只是很简单的一步,后面还有:
如何在鼠标点击的位置绘制一个点?
答案没有想象的那么简单,canvas和WebGL坐标系不同,需要转换坐标
如何修改点的颜色?
用uniform变量就可以了
如何绘制多个点
循环
gl.drawArrays(gl.POINTS, 0, 1);
?可以吗?
这些都是后面的笔记要讨论的问题,先不要着急
参考资料
- 《WebGL编程指南》