WebGL学习笔记02

WebGL分享 by 达达 at 2015-01-21

上一篇笔记,我梳理了自己对WebGL的一些观点,这一篇笔记开始梳理我所学习到的知识。

因为这是一篇学习笔记,所以我不会很系统很详细的一一介绍技术点和API参数,只会对知识做一些归纳终结和坑点介绍,所以系统的学习还是得靠书籍,我学习的时候看的是《WebGL Programming Guide》,可以在网上找到英文版pdf,也可以在亚马逊上找到中文版,我极力推荐这本书,因为它图文结合,让人直观的理解很多复杂的概念。

好了,接下来就进入主题。

我在刚接触到WebGL,制作第一个demo的时候,我学习到以下几个概念:

  1. Canvas
  2. WebGL Context
  3. Vertex Shader
  4. Fragment Shader
  5. Program

Canvas是HTML5加入的新标签,可以用来做2D图形绘制,用起来有点像用GDI+自己绘图。

在使用WebGL相关的API之前,先得从一个Canvas上获得WebGL Context,然后才能开始使用WebGL相关接口。

WebGL Context的获取过程是一个段比较通用的代码,可以封装成一个函数,代码在文章最后给出。

在刚开始学习WebGL相关API的时候,我拿出之前买的几本OpenGL书(买来都没有看)想作为参照,发现API差别很大。后来经过了解,才知道老版本的OpenGL是命令式的编程接口,新版本的OpenGL是基于Shader的编程接口,两者风格差异很大。

简单来讲,命令式的编程接口就是你让它画什么它就画什么,比如你让它画个点它就画个点,OpenGL内部知道如何去画一个点。而基于Shader的编程接口则是你教它怎么画它就怎么画,Shader编程就是你教OpenGL怎么画的过程。基于Shader的接口设计,让调用者能获得更高的灵活性,也可以获得更好的性能。

在OpenGL中,把Shader分为两类,Vertex Shader和Fragment Shader,我以下简称VShader和FShader。VShader的作用是对所有顶点进行计算和转换,能实现坐标移动和旋转等操作。FShader则是对光栅化之后的图形碎片进行计算和转换,通常是颜色转换,能实现光照阴影等效果。

VShader和FShader必须配套使用,一对VShader和FShader组成一个Program,OpenGL每次渲染的时候只能使用一个Program,但是一个程序可以同时有多个Program。

VShader看起来像这样:

void main() {\n' +
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // Set the vertex coordinates of the point
    gl_PointSize = 10.0;                          // Set the point size
}

VShader的以顶点为单位执行的,传给WebGL的每个顶点都会放到VShader执行一次计算,所以顶点数量的多少会影响到程序性能,VShader计算的复杂度也会影响到程序性能。

FShader看起来像这样:

void main() {
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Set the point color
}

FShader的执行是以光栅化之后的碎片为单位执行的,我们传递给WebGL的顶点数据是一组矢量数据,没有具体到屏幕像素,WebGL的主要工作就是把这些矢量数据进行光栅化,转换成屏幕上的一个个像素,FShader则用来让WebGL知道各个像素的颜色值。

VShader和FShader用的是一种专有的编程语言,叫做OpenGL Shading Language,简称GLSL。因为它们是一种编程语言,所以需要先编译链接成Program后才能使用。

WebGL Context的获取,以及Shader的编译和Program的链接,都是比较通用的过程,做实验的时候我把它们封装成一个一组工具函数:

var GLUtil = function() {
var self = {};

var loadShader = function(gl, type, source) {
    // Create shader object
    var shader = gl.createShader(type);
    if (shader == null) {
        console.log('unable to create shader');
        return null;
    }

    // Set the shader program
    gl.shaderSource(shader, source);

    // Compile the shader
    gl.compileShader(shader);

    // Check the result of compilation
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
        var error = gl.getShaderInfoLog(shader);
        console.log('Failed to compile shader: ' + error);
        gl.deleteShader(shader);
        return null;
    }

    return shader;
}

self.GetContext = 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;
}

self.CreateProgram = function(gl, vshader, fshader) {
    // Create shader object
    var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
    var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
    if (!vertexShader || !fragmentShader) {
        console.log('Failed to create program for WebGL');
        return null;
    }

    // Create a program object
    var program = gl.createProgram();
    if (!program) {
        return null;
    }

    // Attach the shader objects
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);

    // Link the program object
    gl.linkProgram(program);

    // Check the result of linking
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
        var error = gl.getProgramInfoLog(program);
        console.log('Failed to link program: ' + error);
        gl.deleteProgram(program);
        gl.deleteShader(fragmentShader);
        gl.deleteShader(vertexShader);
        return null;
    }

    return program;
}

return self;
}();

我做的第一个实验是绘制一个红点,代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>WebGL Labs 01 - Point</title>
        <script src="GLUtil.js"></script>
        <script type="text/javascript">
// Vertex shader program
var VSHADER_SOURCE = 
    'void main() {\n' +
    '   gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
    '   gl_PointSize = 10.0;\n' +                    // Set the point size
    '}\n';

// Fragment shader program
var FSHADER_SOURCE =
    'void main() {\n' +
    '   gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
    '}\n';

function main() {
    // Retrieve <canvas> element
    var canvas = document.getElementById('webgl');

    // Get the rendering context for WebGL
    var gl = GLUtil.GetContext(canvas);
    if (!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // Create shader program.
    var program = GLUtil.CreateProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
    if (!program) {
        console.log('Failed to create program for WebGL');
        return;
    }

    gl.useProgram(program);

    // Specify the color for clearing <canvas>
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // Clear <canvas>
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Draw a point
    gl.drawArrays(gl.POINTS, 0, 1);
}
        </script>
    </head>
    <body onload="main()">
        <center style="margin-top:100px">
            <canvas id="webgl" width="400" height="400">
            Please use a browser that supports "canvas"
            </canvas>
        </center>
    </body>
</html>