用WebGL绘制一个矩形_WebGL笔记1

一.canvas 2D绘制矩形

用canvas 2D绘制一个矩形很容易,几行代码轻松搞定:

<!-- html -->
<h5>canvas2d</h5>
<canvas id="canvas2d" width="400" height="400">
    Please use a browser that supports "canvas"
</canvas>

// js
// 获取元素引用
var canvas2d = document.getElementById('canvas2d');
// 获取2D context
var ctx = canvas2d.getContext('2d');

// 设置填充色
ctx.fillStyle = 'rgba(255, 0, 255, 0.75)';
// 绘制矩形块
ctx.fillRect(100, 100, 200, 200);

实际效果就是一个200x200px淡紫色矩形块,周围一圈宽度100px的透明环。从代码的角度来看,应该是:400x400px的透明canvas中心被涂上了一个不透明度为0.75的200x200px的紫色矩形块

ctx.fillRect(100, 100, 200, 200);可以看出canvas 2D的坐标系与屏幕坐标系一样,左上角是(0, 0),x轴向右为正,y轴向下为正

ctx.fillStyle = 'rgba(255, 0, 255, 0.75)';可以看出rgba()与CSS一样,rgb值为0~255,a为0~1表示不透明度

二.WebGL绘制矩形

1.获取context

类似于canvas2d.getContext('2d'),WebGL需要获取专用的context:

var webgl = document.getElementById('webgl');
var gl = webgl.getContext('webgl');

这里习惯把WebGL的context命名为gl,是为了以后调用API时与OpenGL调用方式保持一致,嗯,就图个好看

2.清空canvas

类似于canvas 2D提供的clearRect方法,WebGL也有橡皮擦一样的东西,而且更强大,可以指定擦除后的颜色:

// 指定清空canvas的颜色
// 参数是rgba,范围0.0~1.0
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清空canvas
// gl.COLOR_BUFFER_BIT颜色缓存,默认清空色rgba(0.0, 0.0, 0.0, 0.0) 透明黑色,通过gl.clearColor指定
// gl.DEPTH_BUFFER_BIT深度缓存,默认深度1.0,通过gl.clearDepth指定
// gl.STENCIL_BUFFER_BIT模板缓存,默认值0,通过gl.clearStencil()指定
gl.clear(gl.COLOR_BUFFER_BIT);

对于本例而言,清空canvas不是必须的,但一定要会用这种方法,不然就疯了,因为在canvas 2D中,如果要设置黑色背景,我们会这样做:

// 黑色背景
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, canvas2d.width, canvas2d.height);

同样的思路,画一个黑块铺满整个canvas,在WebGL中当然也可以,但会相当麻烦

3.绘制矩形

为了绘制一个矩形,我们需要先做8件事情:

  1. 编写着色器源程序

    使用着色器语言(GLSL ES)编写着色器源程序,再以字符串形式传入WebGL系统内部编译执行:

    // 0.着色器源程序
    // 顶点着色器源程序
    var vsSrc = 'void main() {' +
        'gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' + // 设置坐标
        'gl_PointSize = 200.0;' +                   // 设置尺寸
    '}';
    // 片元着色器源程序
    var fsSrc = 'void main() {' +
        'gl_FragColor = vec4(1.0, 0.0, 1.0, 0.75);' + // 设置颜色
    '}';
    

    GLSL ES的语法和C语言比较像,具体语法规则在以后的笔记里再说

  2. 创建着色器对象

    没什么好说的,游戏规则

    // 1.创建着色器对象
    var vs = gl.createShader(gl.VERTEX_SHADER);
    var fs = gl.createShader(gl.FRAGMENT_SHADER);
    // 检查创建结果
    if (vs === null) {
        log('gl.createShader(gl.VERTEX_SHADER) failed');
    }
    if (fs === null) {
        log('gl.createShader(gl.FRAGMENT_SHADER) failed');
    }
    
  3. 填充源程序

    有了着色器对象,就可以把着色器源程序塞进去了

    // 2.填充源程序
    gl.shaderSource(vs, vsSrc);
    gl.shaderSource(fs, fsSrc);
    
  4. 编译

    有了源码,赶紧编译,看报不报错

    // 3.编译
    gl.compileShader(vs);
    gl.compileShader(fs);
    // 检查编译错误
    if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
        log('gl.compileShader(vs) failed');
        log(gl.getShaderInfoLog(vs));   // 输出错误信息
    }
    if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
        log('gl.compileShader(fs) failed');
        log(gl.getShaderInfoLog(fs));   // 输出错误信息
    }
    
  5. 创建程序对象

    除了着色器对象,还需要程序对象,js与WebGL内部交流主要通过这个程序对象,先做一个出来

    // 4.创建程序对象
    var prog = gl.createProgram();
    // 检查创建结果
    if (prog === null) {
        log('gl.createProgram() failed');
    }
    

    注意:上面4步都是一个顶点XX和一个片元XX,而这一步只需要1个prog对象

  6. 为程序对象分配着色器

    每个prog都需要2个着色器(1个顶点着色器,1个片元着色器),所以检查分配错误时要与2比较,看prog身上是不是绑了2个shader

    // 5.为程序对象分配着色器
    gl.attachShader(prog, vs);
    gl.attachShader(prog, fs);
    // 检查分配错误
    if (gl.getProgramParameter(prog, gl.ATTACHED_SHADERS) !== 2) {
        log('gl.getProgramParameter(prog, gl.ATTACHED_SHADERS) failed');
    }
    
  7. 连接程序对象

    类似于C语言程序的compile -> link -> run

    // 6.连接程序对象
    gl.linkProgram(prog);
    // 检查连接错误
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
        log('gl.linkProgram(prog) failed');
        log(gl.getProgramInfoLog(prog));
    }
    
  8. 使用程序对象

    不做错误检查,因为没有返回值

    // 7.使用程序对象
    gl.useProgram(prog);
    

至此,为绘制一个矩形做的准备工作就完成了

最后当然是draw点什么

    // 绘制矩形(一个点,但点的尺寸略大)
    gl.drawArrays(gl.POINTS, 0, 1);

三.DEMO

包含上述代码的完整的例子,请查看:http://www.ayqy.net/temp/webgl/hoho/index.html

四.WebGL与canvas 2D的差异

1.坐标系不同

在WebGL中,canvas的中心是(0, 0),x轴向右为正,y轴向上为正,z轴从屏幕射出指向脸为正(右手坐标系)

2.坐标值不同

WebGL中,整个画布是Rect(-1.0, 1.0, 2, 2),也就是说canvas左上角坐标是(-1.0, 1.0),右上角坐标为(1.0, 1.0),画布尺寸是2×2,如果canvas本身是400x400px的,那么WebGL中坐标值的单位就是200px

注意:对于非坐标值属性,比如上面着色器源程序中的gl_PointSize,其单位仍然是px

3.rgb值不同

片元着色器中有用到色值,vec4(r, g, b, a),与CSS的rgba()不同,WebGL中rgb值范围是0.0~1.0,而且必须是带小数的形式,因为vec4构造函数只接受float型参数,int不会被隐式转换float

4.颜色叠加规则不同

运行DEMO能够发现canvas 2D与Web GL画出的紫色矩形颜色不同,canvas 2D中我们先绘制了黑色背景,再画上了紫色矩形,紫色矩形有0.25的透明,最终显示的颜色是紫色与黑色的叠加。

而WebGL中,紫色矩形的颜色没有与黑色叠加(body背景色就是我们给紫色矩形指定的颜色,能清楚地看到WebGL中没有发生紫黑叠加)。尝试在紫色矩形背后(z坐标为-0.1)再绘制一个黑色矩形,发现颜色仍然没有叠加,这需要特别注意

五.总结

从API使用步骤上来看WebGL就是这么麻烦,画一个矩形都要写这么多代码,实际上我们只绘制了一个点(略大只的点),绘制矩形还要更麻烦些,需要用4个顶点确定2个三角片,再分别填色

至于顶点着色器,片元着色器是什么?有什么作用?需要一个稍微复杂一点的例子来说明,我们在以后的笔记里再解释

参考资料

  • 《WebGL编程指南》

用WebGL绘制一个矩形_WebGL笔记1》上有2条评论

    1. ayqy 文章作者

      深度检测默认是disabled的,颜色/透明度叠加与深度检测没有直接关系。需要开启混合(blend)并指定混合函数

      gl.enable(gl.BLEND);
      gl.blendFunc(sfactor, dfactor);
      // 或
      // gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
      

      Demo已更新,补充了blend

      回复

发表评论

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

*

code