WEBGL编程指南 WebGL编程指南/(美)松田浩一(Matsuda,K.),(美)李(Lea,R.)著;谢光磊译。
一北京:电子工业出 版社,2014.6
书名原文:WebGL programming guide: interactive 3D graphics programming with WebGL
ISBN 978-7-121-22942-8
温故而知新,可以为师矣?
第1章 WebGL 概述 这一章简要介绍了WebGL 技术的若干关键特性和WebGL程序(网页)的结构。总之,这一章最重要的内容是,WebGL程序使用三种语言开发:HTML、JavaScript 和 GLSL ES——然而,由于着色器代码GLSLES 内嵌在JavaScript中,所以WebGL网页的 文件结构和传统网页一样。下一章将通过一些简单的WebGL示例,一步一步把你带进 WebGL的大门。
OpenGL、OpenGL ES 1.1//2.0/3.0 和 WebGL之间的关系
第2章 WebGL 入门 本章内容:
WebGL如何获取canvas元素,如何在其上绘图。
HTML文件如何引人WebGL JavaScript 文件。
简单的WebGL绘图函数。
WebGL 中的着色器程序。
2D canvas
var ctx = canvas.getContext ('2d');
var ctx = canvas.getContext (‘2d’);
//绘制蓝色矩形
ctx.fillStyle = ‘rgba(0,0,255,1.0)’; //设置填充颜色为蓝色
ctx.fillRect(120,10,150,150);//用这个颜色填充矩形
3D canvas
最短的WebGL程序:清空绘图区
1 2 3 4 5 6 7 var canvas = document .getElementById ("webgl" );var gl = canvas.getContext ("webgl" );gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); g1.clear (gl.COLOR_BUFFER_BIT );
关于WebGLRenderingContext的文档可以看这里:WebGLRenderingContext:
gl.clearColor(red, green, blue, alpha) 指定绘图区域的背景色:
示例程序执行了gl.clearColor(0.0, 0.0, 0.0, 1.0),背景色被指定为黑色。
一旦指定了背景色之后,背景色就会驻存在WebGL系统(WebGL System)中,在下一次调用g1.clearColor()方法前不会改变。
最后,你可以调用g1.clear()函数,用之前指定的背景色清空(即用背景色填充, 擦除已经绘制的内容)绘图区域。
gl.clear(gl.COLOR_BUFFER_ BIT);
注意,函数的参数是g1.COLOR_BUFFER_BIT,而不是(你可能认为会是)表示绘图区域的canvas。这是因为 WebGL中的g1.clear()方法实际上继承自OpenGL,它基于基本缓冲区模型,这可比二维绘图上下文复杂得多。清空绘图区域,实际上是在清空颜色缓冲区 (color buffer),传递参数g1.COLOR_BUFFER_BIT就是在告诉WebGL清空颜色缓冲区。除了颜色缓冲区,WebGL还会使用其他种类的缓冲区,比如深度缓冲区和模板缓冲区。
mask 一个用于指定需要清除的缓冲区的 GLbitfield (en-US) 。可能的值有: gl.COLOR_BUFFER_BIT //颜色缓冲区 gl.DEPTH_BUFFER_BIT //深度缓冲区 gl.STENCIL_BUFFER_BIT //模板缓冲区 错误抛出 如果mask不是以上列出的值,会抛出 gl.INVALID_ENUM 错误。
返回值 无
如果没有指定背景色(也就是说,你没有调用g1.clearColor()),那么使用的默认。
绘制一个点:
封装的一个initShaders函数,方便复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 function initShaders (gl, vshader, fshader ) { var program = createProgram (gl, vshader, fshader); if (!program) { console .log ('Failed to create program' ); return false ; } gl.useProgram (program); gl.program = program; return true ; } function createProgram (gl, vshader, fshader ) { var vertexShader = loadShader (gl, gl.VERTEX_SHADER , vshader); var fragmentShader = loadShader (gl, gl.FRAGMENT_SHADER , fshader); if (!vertexShader || !fragmentShader) { return null ; } var program = gl.createProgram (); if (!program) { return null ; } gl.attachShader (program, vertexShader); gl.attachShader (program, fragmentShader); gl.linkProgram (program); 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; } function loadShader (gl, type, source ) { var shader = gl.createShader (type); if (shader == null ) { console .log ('unable to create shader' ); return null ; } gl.shaderSource (shader, source); gl.compileShader (shader); 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; }
啰嗦一通等价下面11行代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 let vertexShader = gl.createShader (gl.VERTEX_SHADER );gl.shaderSource (vertexShader, vertexShaderSource); gl.compileShader (vertexShader); let fragmentShader = gl.createShader (gl.FRAGMENT_SHADER );gl.shaderSource (fragmentShader, fragmentShaderSource); gl.compileShader (fragmentShader); let program = gl.createProgram ();gl.attachShader (program, vertexShader); gl.attachShader (program, fragmentShader); gl.linkProgram (program); gl.useProgram (program);
没有传入顶点坐标,但是在顶点着色器中写死了顶点位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var canvas = document .getElementById ("point1" );var gl = canvas.getContext ("webgl" );var vertexShaderSource = ` void main() { gl_Position = vec4(0.0,0.0,0.0,1.0); gl_PointSize = 10.0; } ` ;var fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` ;gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT ); initShaders (gl, vertexShaderSource, fragmentShaderSource);gl.drawArrays (gl.POINTS ,0 ,1 );
gl.drawArrays(mode,first,count)
gl.drawarrays()是一个强大的函数,它可以用来绘制各种图形,该函数的规范如下表所示。
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/drawArrays
mode GLenum (en-US) 类型,指定绘制图元的方式,可能值如下。 gl.POINTS: 绘制一系列点。 gl.LINE_STRIP: 绘制一个线条。即,绘制一系列线段,上一点连接下一点。 gl.LINE_LOOP: 绘制一个线圈。即,绘制一系列线段,上一点连接下一点,并且最后一点与第一个点相连。 gl.LINES: 绘制一系列单独线段。每两个点作为端点,线段之间不连接。 gl.TRIANGLE_STRIP:绘制一个三角带。 gl.TRIANGLE_FAN:绘制一个三角扇。 gl.TRIANGLES: 绘制一系列三角形。每三个点作为顶点。
first GLint (en-US) 类型 ,指定从哪个点开始绘制。
count GLsizei (en-US) 类型,指定绘制需要使用到多少个点。
返回值 无。
异常 如果 mode 不是一个可接受值,将会抛出 gl.INVALID_ENUM 异常。 如果 first 或者 count 是负值,会抛出 gl.INVALID_VALUE 异常。 如果 gl.CURRENT_PROGRAM 为 null,会抛出 gl.INVALID_OPERATION 异常。
使用attribute传递顶点坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var gl = canvas.getContext ("webgl" );var vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } ` ;var fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);var a_Position = gl.getAttribLocation (gl.program , 'a_Position' );gl.vertexAttrib3f (a_Position, 0.0 , 0.0 , 0.0 ); gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT ); gl.drawArrays (gl.POINTS , 0 , 1 );
gl.getattribLocation (program, name)
获取由 name参数指定的attribute变量的存储地址。
参数
program
指定包含顶点着色器和片元着色器的着色器程序对象
name
指定想要获取其存储地址的attribute变量的名称
返回值
大于等于0
attribute变量的存储地址
-1
指定的attribute变量不存在
gi.vertexAttrib3f (location, v0, v1, v2)
将数据(v0.v1,v2)传给由location参数指定的attribute变量
该函数的第1个参数是 attribute变量的存储地址,即gl.getAttribLocation()的返 回值;第2、3、4个参数是三个浮点型数值,即点的x、y和z坐标值。函数被调用后,这三个值被一起传给顶点着色器中的a_Position变量。
gl.vertexAttrib3f()的同族函数
gl.vertexAttrib3f()是一系列同族函数中的一个,该系列函数的任务就是从 JavaScript向顶点着色器中的attribute变量传值。
gl.vertexAttrib1f(location, v0)
gl.vertexAttrib2t(location, v0, v1)
gl.vertexAttrib3f(location, v0, v1, v2)
gl.vertexAttrib4f(location, v0, vl, v2, v3)
鼠标点击绘制点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 let canvas = document .getElementById ("point2" );let gl = canvas.getContext ("webgl" );let vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } ` ;let fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT ); let a_Position = gl.getAttribLocation (gl.program , 'a_Position' );let g_points = [];canvas.onmousedown = function (ev ) { let x = ev.clientX ; let y = ev.clientY ; let 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.push (x, y); gl.clear (gl.COLOR_BUFFER_BIT ); for (let i = 0 ; i < g_points.length ; i += 2 ) { gl.vertexAttrib3f (a_Position, g_points[i], g_points[i + 1 ], 0.0 ); gl.drawArrays (gl.POINTS , 0 , 1 ); } }
webgl默认是右手坐标系
x轴:大拇指朝右。y轴:食指朝上。z轴:中指朝向自己
鼠标位置改变点的颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 let canvas = document .getElementById ("point3" );let gl = canvas.getContext ("webgl" );let vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; gl_PointSize = 10.0; } ` ;let fragmentShaderSource = ` precision mediump float; uniform vec4 u_FragColor; void main() { gl_FragColor = u_FragColor; } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT ); let a_Position = gl.getAttribLocation (gl.program , 'a_Position' );let g_points = [];let u_FragColor = gl.getUniformLocation (gl.program , 'u_FragColor' );let g_colors = [];canvas.onmousedown = function (ev ) { let x = ev.clientX ; let y = ev.clientY ; let 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.push ([x, y]); g_colors.push ([(x+1 )/2 ,(y+1 )/1 , 1.0 , 1.0 ]); gl.clear (gl.COLOR_BUFFER_BIT ); for (let i = 0 ; i < g_points.length ; i ++ ) { gl.vertexAttrib3f (a_Position, g_points[i][0 ], g_points[i][1 ], 0.0 ); gl.uniform4f (u_FragColor, g_colors[i][0 ], g_colors[i][1 ], g_colors[i][2 ], g_colors[i][3 ]); gl.drawArrays (gl.POINTS , 0 , 1 ); } }
第3章 绘制和变换三角形 本章内容:
三角形在三维图形学中的重要地位,以及WebGL如何绘制三角形。
使用多个三角形绘制其他类型的基本图形。
利用简单的方程对三角形做基本的变换,如移动、旋转和缩放。
利用矩阵简化变换。
WebGL提供了一种很方便的机制,即缓冲区对象(buffer object),它可以一次性地向 着色器传入多个顶点的数据。缓冲区对象是WebGL系统中的一块内存区域,我们可以 一次性地向缓冲区对象中填充大量的顶点数据,然后将这些数据保存在其中,供顶点着色器使用。
使用缓冲区对象向顶点着色器传入多个顶点的数据,需要遵循以下五个步骤。处理 其他对象,如纹理对象(第4章)、帧缓冲区对象(第8章“光照””)时的步骤也比较类似, 我们来仔细研究一下:
创建缓冲区对象(gl.createBuffer())。
绑定缓冲区对象(g1.bindBuffer())。
将数据写人缓冲区对象(g1.bufferData())。
将缓冲区对象分配给一个attribute变量(g1.vertexAttribPointer())。
开启 attribute变量(g1.enableVertexAttribArray())。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 let vertexShaderSource = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; } ` ;let fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);let a_Position = gl.getAttribLocation (gl.program , "a_Position" );let data = new Float32Array ([0.0 , 0.5 ,-0.5 , -0.5 , 0.5 , -0.5 ]); let buffer = gl.createBuffer ();gl.bindBuffer (gl.ARRAY_BUFFER , buffer); gl.bufferData (gl.ARRAY_BUFFER , data, gl.STATIC_DRAW ); gl.vertexAttribPointer (a_Position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_Position); gl.drawArrays (gl.TRIANGLES , 0 , 3 );
g1.bufferData(target, data, usage)
开辟存储空间,向绑定在target上的缓冲区对象中写入数据data
target gl.ARRAY_BUFFER 或g1.ELEMENT ARRAY_ BUFFER
data 写入缓冲区对象的数据(类型化数组,参阅下一节)
usage 表示程序将如何使用存储在缓冲区对象中的数据。该参数 将帮助WebGL优化操作,但是就算你传入了错误的值,也 不会终止程序(仅仅是降低程序的效率)
gl.STATIC DRAW 只会向缓冲区对象中写入一次数据,但需要绘制很多次
g1.STREAM DRAW 只会向缓冲区对象中写入一次数据,然后绘制若干次
g1.DYNAMIC_DRAW 会向缓冲区对象中多次写入数据,并绘制很多次
返回值 无
错误 INVALID ENUM target不是上述值之一,这时将保持原有的绑定情况不变
类型化数组
为了绘制三维图形,WebGL通常需要同时处理大量相同类型的数据,例如顶点的坐 标和颜色数据。为了优化性能,WebGL为每种基本数据类型引入了一种特殊的数组(类 型化数组)。浏览器事先知道数组中的数据类型,所以处理起来也更加有效率。
与JavaScript 中的Array数组相似,类型化数组也有一系列方法和属性(包括一个常 量属性),如表3.2所示。注意,与普通的Array数组不同,类型化数组不支持push()和 pop()方法 。
平移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 let vertexShaderSource = ` attribute vec4 a_Position; uniform vec4 u_Translate; void main() { gl_Position = a_Position+u_Translate; gl_PointSize = 10.0; } ` ;let fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);let a_Position = gl.getAttribLocation (gl.program , "a_Position" );let data = new Float32Array ([ 0.0 , 0.5 , -0.5 , -0.5 , 0.5 , -0.5 ]); let buffer = gl.createBuffer ();gl.bindBuffer (gl.ARRAY_BUFFER , buffer); gl.bufferData (gl.ARRAY_BUFFER , data, gl.STATIC_DRAW ); gl.vertexAttribPointer (a_Position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_Position); let u_Translate = gl.getUniformLocation (gl.program , "u_Translate" );gl.uniform4f (u_Translate, 0.0 , 0.5 , 0.0 , 0.0 ); gl.drawArrays (gl.POINTS , 0 , 3 );
旋转矩阵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 let vertexShaderSource = ` attribute vec4 a_Position; uniform mat4 u_Rotate; void main() { gl_Position = u_Rotate*a_Position; gl_PointSize = 40.0; } ` ;let fragmentShaderSource = ` void main() { gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); } ` ;initShaders (gl, vertexShaderSource, fragmentShaderSource);let a_Position = gl.getAttribLocation (gl.program , "a_Position" );let k = .3 , sqtr3 = Math .sqrt (3 );let data = new Float32Array ([-sqtr3 * k, -k, sqtr3 * k, -k, 0 , 2 * k]); let buffer = gl.createBuffer ();gl.bindBuffer (gl.ARRAY_BUFFER , buffer); gl.bufferData (gl.ARRAY_BUFFER , data, gl.STATIC_DRAW ); gl.vertexAttribPointer (a_Position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_Position); let u_Translate = gl.getUniformLocation (gl.program , "u_Rotate" );let ang = 0 ;let rad = ang * Math .PI / 180 ;let cosB = Math .cos (rad), sinB = Math .sin (rad);let xformMatrix = new Float32Array ([cosB, sinB, 0 , 0 , -sinB, cosB, 0 , 0 , 0 , 0 , 1 , 0 ,0 , 0 , 0 , 1 ]); gl.uniformMatrix4fv (u_Translate, false , xformMatrix); gl.drawArrays (gl.TRIANGLES , 0 , 3 ); setInterval (() => { ang -= 2 ; let rad = ang * Math .PI / 180 ; let cosB = Math .cos (rad), sinB = Math .sin (rad); let xformMatrix = new Float32Array ([ cosB, sinB, 0 , 0 , -sinB, cosB, 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 ]); gl.uniformMatrix4fv (u_Translate, false , xformMatrix); gl.drawArrays (gl.POINTS , 0 , 3 ); }, 40 );
注意WebGL中矩阵是列主序的,就是和公式的行列是反的,构建数组的时候注意顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let xformMatrix = new Float32Array ([ cosB, sinB, 0 , 0 , -sinB, cosB, 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 ]); let xformMatrix = new Float32Array ([ 1 , 0 , 0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 1 , 0 , Tx , Ty , Tz , 1 ]); let xformMatrix = new Float32Array ([ Sx , 0 , 0 , 0 , 0 , Sy , 0 , 0 , 0 , 0 , Sz , 0 , 0 , 0 , 0 , 1 ]);
第4章高级变换与动画基础 本章内容:
学习使用一个矩阵变换库,该库封装了矩阵运算的数学细节。
快速上手使用该矩阵库,对图形进行复合变换。
在该矩阵库的帮助下,实现简单的动画效果。
坐车提供的一个矩阵函数库:
第5章颜色与纹理 第6章OpenGL ES着色器语言 (GLSL ES) 第7章进入三维世界 第8章光照 第9章层次模型 第10章高级技术
在线案例地址: https://sites.google.com/site/webglbook/home