WebGL学习(一)

参考书籍:《WebGL编程指南》

canvas

canvas是HTML5新增标签,用于定义网页上的绘图区域,可以通过JavaScript代码在上面绘制图形

关于Canvas更多信息请参考MDN相关文档 Canvas简单使用示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Canvas</title>
</head>
<body onload="main()">
    <canvas id="webgl" width="400" height="400">
        Please use the browser supporting "canvas"
    </canvas>

    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <script src="HelloCanvas.js"></script>
</body>
</html>
function main() {
    // 获取canvas元素
    var canvas = document.getElementById('webgl');

    // 获取WebGL绘图上下文(调用cuon-util.js中封装的方法)
    var gl = getWebGLContext(canvas);
    if(!gl) {
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // 指定清空canvas的颜色
    gl.clearColor(0.2, 0.4, 0.6, 0.8);

    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT); 
}
效果

HelloCanvas

上述代码在HTML中定义了一个400x400的canvas,并通过js代码获取到了canvas元素对象以及其WebGL绘图上下文对象,通过WebGL绘图上下文对象以RBGA的形式设置了canvas的背景颜色,最后用之前设置的背景色清空canvas绘图区域。

一旦通过clearColor方法指定背景色后,背景色就会驻存在WebGL系统中,在下一次重新调用clearColor方法设置背景色前不会改变。

clear方法说明

clear用于清空指定缓冲区数据,它接受一个枚举类型的参数buffer,具体说明如下:

  1. gl.COLOR_BUFFER_BIT:指定颜色缓冲区
  2. gl.DEPTH_BUFFER_BIT: 指定深度缓冲区
  3. gl.STENCIL_BUFFER_BIT: 指定模板缓冲区 返回值:无 错误:INVALID_VALUE - 缓冲区不是以上三种类型

各缓冲区默认行为: 颜色缓冲区:相关函数gl.clearColor(r, g, b, a),默认值(0.0, 0.0, 0.0, 0.0) 深度缓冲区:相关函数gl.clearDepth(depth),默认值1.0 模板缓冲区:相关函数gl.clearStencil(s),默认值0

初识着色器程序

WebGL依赖于着色器(shader)机制进行绘图,在WebGL中着色器程序是以字符串的形式嵌入在JavaScript代码中的

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Draw a point (1)</title>
</head>
<body onload="main()">
    <canvas id="webgl" width="400" height="400">
        Please use the browser supporting "canvas"
    </canvas>

    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <script src="HelloPoint1.js"></script>
</body>
</html>
// 顶点着色器程序(vertex shader program)
var VSHADER_SOURCE = 
    'void main() {\n' + 
    ' gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 设置坐标
    ' gl_PointSize = 10.0;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序(Fragment shader program)
var FSHADER_SOURCE = 
    'void main() {\n' +
    ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';


function main() {
    // 获取canvans元素
    var canvans = document.getElementById('webgl');

    // 获取WebGL上下文
    var gl = getWebGLContext(canvans);
    if(!gl){
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // 初始化着色器
    if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
        console.log('Failed to initialize shaders.');
        return;
    }

    // 设置canvas颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}
效果

HelloPoint1

顶点着色器(Vertex shader): 用来描述顶点特性(如位置,颜色等)的程序。顶点是值二维或三维空间中的一个点,比如二维或三维图像的端点或交点。 片元着色器(Fragment shader): 进行逐片元处理过程如光照的程序。片元是WebGL术语,可以将其理解为像素(图像的单元)。

WebGL渲染过程

WebGL渲染过程

1.WebGL程序包括两部分:运行在浏览器中的JavaScript和运行在WebGL系统的着色器程序 2.WebGL一般用于绘制三维图形,使用三维坐标。通常情况下WebGL中的坐标系是右手坐标系,坐标原点是canvas绘图区域的正中心,x轴正方向为向右,y轴正方向为向上,z轴正方向为垂直于屏幕向外 3.canvas绘图区域的坐标原点在绘图区域的左上角,x轴正方向为向右,y轴正方向为向下

动态向着色器传输数据

之前的着色器程序中,点的位置、大小、颜色等信息都是硬编码的,缺乏可扩展性。我们需要动态从JavaScript程序中传递数据给着色器程序。 有两种方式可以做到这一点:attribute变量和uniform变量

  1. attribute变量:传输和顶点相关的数据
  2. uniform变量:传输对于所有顶点都相同的数据或者和顶点无关的数据
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Draw a point (2)</title>
</head>
<body onload="main()">
    <canvas id="webgl" width="400" height="400">
        Please use the browser supporting "canvas"
    </canvas>

    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <script src="HelloPoint2.js"></script>
</body>
</html>
// 顶点着色器程序(vertex shader program)
var VSHADER_SOURCE = 
    'attribute vec4 a_Position;\n' +
    'attribute float a_PointSize;\n' +
    'void main() {\n' + 
    ' gl_Position = a_Position;\n' + // 设置坐标
    ' gl_PointSize = a_PointSize;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序(Fragment shader program)
var FSHADER_SOURCE = 
    'void main() {\n' +
    ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置颜色
    '}\n';


function main() {
    // 获取<canvans>元素
    var canvans = document.getElementById('webgl');

    // 获取WebGL上下文
    var gl = getWebGLContext(canvans);
    if(!gl){
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // 初始化着色器
    if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
        console.log('Failed to initialize shaders.');
        return;
    }

    // 获取attribute变量a_Position的存储地址
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0){
        console.log('Failed to get the storage location of a_Position');
        return;
    }

    // 传输顶点大小(方式一)
    gl.vertexAttrib3f(a_Position, 0.5, 0.5, 0.0);
    // 传输顶点大小(方式二)
    // var positions = new Float32Array([0.5, 0.5, 0.0, 1.0])
    // gl.vertexAttrib4fv(a_Position, positions);


    // 获取attribute变量a_PointSize的存储地址
    var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
    if(a_PointSize < 0){
        console.log('Failed to get the storage location of a_PointSize');
        return;
    }
    
    // 传输顶点大小
    gl.vertexAttrib1f(a_PointSize, 200.0);


    // 设置canvas颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);
}
效果

HelloPoint2

相关函数说明
  • getAttribLocation(program, name):获取name参数指定的attribute变量的存储地址。program参数指定包含顶点着色器和片元着色器的着色器程序对象。name参数指定要获取的attribute变量的名称。成功返回变量地址,失败返回-1。
  • vertexAttrib3f(location, v0, v1, v2):将(v0, v1, v2)传递给location参数指定的attribute变量。location参数指定要修改的attribute变量的地址,v0指定填充attribute变量第一个分量的值,v1、v2依次类推。它是一系列同族方法中的一个,类型的有vertexAttrib1f、vertexAttrib2f、vertexAttrib4f。
  • vertexAttrib4fv(location, position):vertexAttrib4f方法的矢量版本(名称以v结尾),它接受类型化数组为参数。location指定要修改的attribute变量的地址,position是代表矢量的类型化数组

attribute变量只能在顶点着色器中使用,uniform变量在顶点着色器和片元着色器中均可使用。

还可以通过js事件来动态传输数据到着色器中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Colored Points</title>
</head>
<body onload="main()">
    <canvas id="webgl" width="400" height="400">
        Please use the browser supporting "canvas"
    </canvas>

    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <script src="ColoredPoints.js"></script>
</body>
</html>
// 顶点着色器程序(vertex shader program)
var VSHADER_SOURCE = 
    'attribute vec4 a_Position;\n' +
    'attribute float a_PointSize;\n' +
    'void main() {\n' + 
    ' gl_Position = a_Position;\n' + // 设置坐标
    ' gl_PointSize = a_PointSize;\n' + // 设置尺寸
    '}\n';


// 片元着色器程序(Fragment shader program)
var FSHADER_SOURCE = 
    'precision mediump float;\n' +
    'uniform vec4 u_FragColor;\n' + // uniform变量
    'void main() {\n' +
    ' gl_FragColor = u_FragColor;\n' + // 设置颜色
    '}\n';


function main() {
    // 获取<canvans>元素
    var canvans = document.getElementById('webgl');

    // 获取WebGL上下文
    var gl = getWebGLContext(canvans);
    if(!gl){
        console.log('Failed to get the rendering context for WebGL');
        return;
    }

    // 初始化着色器
    if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
        console.log('Failed to initialize shaders.');
        return;
    }

    // 获取a_Position变量的存储位置
    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
    if(a_Position < 0) {
        console.log('Failed to get storage location of a_Position');
        return;
    }
    // 获取a_PointSize变量的存储位置
    var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize');
    if(a_PointSize < 0) {
        console.log('Failed to get storage location of a_PointSize');
        return;
    }

    // 获取u_FragColor变量的存储位置
    var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
    if(!u_FragColor){
        console.log('Failed to get storage location of u_FragColor');
        return;
    }

    // 传入a_PointSize
    gl.vertexAttrib1f(a_PointSize, 10.0);

    // 设置canvas颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);

    // 清空canvas
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 绘制一个点
    gl.drawArrays(gl.POINTS, 0, 1);

    // 注册鼠标点击事件响应函数
    canvans.onmousedown = function(ev) {
        click(ev, gl, canvans, a_Position, u_FragColor);

    }
}

var g_points = []; // 鼠标点击位置数组
var g_colors = []; // 存储颜色的数组
/**
 * 点击事件响应函数
 * @param {*} ev 
 * @param {*} gl 
 * @param {*} canvans 
 * @param {*} a_Position 
 * @param {*} u_FragColor
 */
function click(ev, gl, canvas, a_Position, u_FragColor) {
    var x = ev.clientX; // 鼠标点击处x坐标
    var y = ev.clientY; // 鼠标点击处y坐标
    var rect = ev.target.getBoundingClientRect();

    x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
    y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);
    //将坐标存储到g_points数组中
    g_points.push([x, y]); 

    // 将点的颜色存储到g_colors数组中
    if(x >= 0.0 && y >= 0.0) { // 第一象限
        g_colors.push([1.0, 0.0, 0.0, 1.0]) // 红色
    }else if(x < 0.0 && y < 0.0 ){ // 第三象限
        g_colors.push([0.0, 1.0, 0.0, 1.0]) // 绿色
    }else { // 其他象限
        g_colors.push([1.0, 1.0, 1.0, 1.0]); //白色
    }

    // 清除canvas
    gl.clear(gl.COLOR_BUFFER_BIT);

    var len = g_points.length;
    for(var i=0; i < len; i++){
        var xy = g_points[i];
        var rgba = g_colors[i];

        //将点的位置传递到变量a_Position中
        gl.vertexAttrib3f(a_Position, xy[0], xy[1], 0.0);
        //将点的颜色传递到变量u_FragColor中
        gl.uniform4f(u_FragColor, rgba[0], rgba[1],rgba[2], rgba[3]);

        // 绘制点
        gl.drawArrays(gl.POINTS, 0, 1);
    }
}
效果

ColoredPoint

相关函数说明
  • getUniformLocation(program, name): 获取name指定的uniform变量的地址。program参数指定着色器程序对象,name参数指定要获取的uniform变量名称。成功返回uniform变量地址,失败返回null。
  • uniform4f(location, v0, v1, v2, v3):将数据(v0, v1, v2, v3)传递给location参数指定的uniform变量。它也是一系列同组方法中的一个,类似的有uniform1f、uniform2f、uniform3f。