参考书籍:《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); | |
} |
效果
上述代码在 HTML 中定义了一个 400x400 的 canvas,并通过 js 代码获取到了 canvas 元素对象以及其 WebGL 绘图上下文对象,通过 WebGL 绘图上下文对象以 RBGA 的形式设置了 canvas 的背景颜色,最后用之前设置的背景色清空 canvas 绘图区域。
一旦通过 clearColor 方法指定背景色后,背景色就会驻存在 WebGL 系统中,在下一次重新调用 clearColor 方法设置背景色前不会改变。
clear方法说明
clear 用于清空指定缓冲区数据,它接受一个枚举类型的参数 buffer,具体说明如下:
- gl.COLOR_BUFFER_BIT:指定颜色缓冲区
- gl.DEPTH_BUFFER_BIT: 指定深度缓冲区
- 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); | |
} |
效果
顶点着色器 (Vertex shader): 用来描述顶点特性(如位置,颜色等)的程序。顶点是值二维或三维空间中的一个点,比如二维或三维图像的端点或交点。
片元着色器 (Fragment shader): 进行逐片元处理过程如光照的程序。片元是 WebGL 术语,可以将其理解为像素(图像的单元)。
WebGL渲染过程
1.WebGL 程序包括两部分:运行在浏览器中的 JavaScript 和运行在 WebGL 系统的着色器程序
2.WebGL 一般用于绘制三维图形,使用三维坐标。通常情况下 WebGL 中的坐标系是右手坐标系,坐标原点是 canvas 绘图区域的正中心,x 轴正方向为向右,y 轴正方向为向上,z 轴正方向为垂直于屏幕向外
3.canvas 绘图区域的坐标原点在绘图区域的左上角,x 轴正方向为向右,y 轴正方向为向下
# 动态向着色器传输数据
之前的着色器程序中,点的位置、大小、颜色等信息都是硬编码的,缺乏可扩展性。我们需要动态从 JavaScript 程序中传递数据给着色器程序。
有两种方式可以做到这一点:attribute 变量和 uniform 变量
- attribute 变量:传输和顶点相关的数据
- 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() { | |
// 获取 & 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); | |
} |
效果
相关函数说明
- 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() { | |
// 获取 & 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); | |
} | |
} |
效果
相关函数说明
- 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。