attribute变量与顶点着色器_WebGL笔记2

写在前面

上一篇笔记中在顶点着色器源程序中写死了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_Positionvec4类型的,即四维向量(齐次坐标,最后一个分量一般取1.0)

gl_PointSize有默认值1.0,可以不必赋值,gl_PointSizefloat类型的,不接受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_Positiona_前缀表示变量是attribute变量,当然这个只是习惯,建议采用

P.S.为什么在顶点着色器源程序中声明attribute变量,而不是在片元…因为我们想要动态修改点的坐标,坐标信息来自顶点着色器源程序中的gl_Position,和片元着色器没关系

注意:只有顶点着色器才能使用attribute变量,片元着色器中需要使用uniform变量,此外还有varying变量,具体区别在GLSL ES笔记中再详细说明

2.初始化着色器

直接调用util,1行代码就能完成剩下的7件事(从createShaderuseProgram的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编程指南》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code