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,具体说明如下:
- 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);
}
效果
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变量
- 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() {
// 获取<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。