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

# canvas

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

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

HelloCanvas.html
<!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 代码中

HelloPoint1.html
<!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>
HelloPoint1.js 28
// 顶点着色器程序 (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 变量:传输对于所有顶点都相同的数据或者和顶点无关的数据
HelloPoint2.html
<!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>
HelloPoint2.js
// 顶点着色器程序 (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() {
    // 获取 & lt;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 事件来动态传输数据到着色器中

ColoredPoint.html
<!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>
ColoredPoint.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() {
    // 获取 & lt;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。
更新于 阅读次数