前言 这是webgl入门的第2节,内容包含weblg的坐标系和动态设置图形样式。
1. webgl坐标系统 由于webgl处理的是三维图形,所以它使用三维坐标系统。具有x轴、y轴、z轴。当你面对屏幕的时候,X轴是水平的(正方形向右),Y轴是垂直的(正方向向上(原文中有错)),而Z轴垂直于屏幕(正方向向外) 这套坐标系又叫右手坐标系。
如图所示,webgl的坐标系(原点在画布中间)跟canvas的坐标系(原点在画布左上角)是不同的。使用canvas显示webgl的时候,需要将webgl的坐标转换成canvas坐标。
canvas中心点在:(0.0,0.0,0.0)
canvas的上边缘和下边缘:(0.0,1.0,0.0)和(0.0,-1.0,0.0)
canvas的左边缘和右边缘:(-1.0,0.0,0.0)和(1.0,0.0,0.0)
原书关于这里的描述是错误的,已订正
你可以点击这里 通过修改gl_Position来查看效果。
2.绘制一个点(版本2) 这一节我们将讨论如何在Javascript和着色器之间传输数据。
在这一节中,webgl程序可以将顶点位置坐标从js传到着色器程序中我,然后在对应的位置将点绘制出来。
2.1 attribute变量 我们的目标是将位置信息从js传到着色器,有2种方式可以做到这点:attribute变量和uniform变量。使用哪个变量取决于要传递的数据本身。
attribute变量 传输的是与顶点有关的数据
uniform变量 传输的是那些对于所有顶点都相同的数据
attribute变量是GLSL ES变量,被用来从外部向顶点着色器内部传递数据,只有顶点着色器能使用它
使用attribute变量,程序应该包含如下步骤:
在顶点着色器中,声明attribute变量
将attribute变量赋值给gl_Position变量
向attribute变量传输数据
html代码(跟之前一样):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > webgl学习</title > </head > <body > <canvas id ="canvas" width ="400" height ="400" > </canvas > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-utils.js" > </script > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-debug.js" > </script > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/cuon-utils.js" > </script > <script src ="./index.js" > </script > </body > </html >
javascript代码(顶点着色器不一样了):
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 var VSHADER_SOURCE = ` attribute vec4 a_Position\n; void main(){\n gl_Position = a_Position;\n gl_PointSize = 10.0;\n }\n ` var FSHADER_SOURCE = ` void main(){\n gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n }\n ` function main () { var canvas = document .getElementById ('canvas' ) var gl = canvas.getContext ('webgl' ) if (!gl) { console .log ('Failed to get the rendering context' ) retun } if (!initShaders (gl, VSHADER_SOURCE , FSHADER_SOURCE )) { console .log ('设置着色器失败' ) return } var a_Position = gl.getAttribLocation (gl.program , 'a_Position' ) if (a_Position < 0 ) { console .log ('Failed to get the storage of a_Position' ) return } 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 ) } window .onload = function ( ) { main () }
点击查看效果
3. 代码解析 3.1 声明attribute变量 我们在着色器中声明的attribute变量
attribute vec4 a_Position;
在这一行中,关键词attribute被称为存储限定符 ,表示接下来的变量是一个attribute变量。attribute变量必须声明为全局变量,数据将从着色器外部传递给该变量。
变量声明的格式如下:<存储限定符><类型><变量名>
然后我们将attribute变量赋值给gl_Position
1 gl_Position = a_Position;
3.2 获取attribute变量的存储位置 每一个变量都有一个存储地址,我们通过存储地址向变量传输数据。为了向attribute变量传输数据,我们要先获取其存储地址。
1 2 var a_Position = gl.getAttribLocation (gl.program , 'a_Position' )
方法的第一个参数是程序对象,它包含顶点着色器和片元着色器。gl.program是怎么来的,你可以查看html代码中引入的帮助函数cuon-utils.js
方法返回值是attribute变量的存储地址,这个地址被存储在js变量中。为了便于理解,本书存储变色器地址的js变量名跟着色器变量名一致(都是a_Position)。
gl.getAttribLocation(program,name)函数规范如下:
类型
参数
含义
参数
program
指定包含顶点着色器和片元着色器的程序对象
参数
name
指定想要获取存储地址的attribute变量名称
返回值
大于等于0
变量地址
返回值
-1
指定的attribute变量不存在,或其命名具有gl_或者webgl_前缀
错误
INVALID_OPERATION
程序对象未能成功连接
INVALID_VALUE
name参数的长度大于attribute变量名的最大程度(256个字节)
3.3 向attribute变量赋值 1 2 gl.vertexAttrib3f (a_Position, 0.0 , 0.0 , 0.0 )
gl.vertexAttrib3f(location,v0,v1,v2)的用法
类型
参数
含义
参数
location
指定要修改的attribute变量的存储位置
v0
第一个分量的值(x的值)
v1
第二个分量的值(y的值)
v2
第三个分量的值(z的值)
返回值
无
错误
INVALID_OPERATION
没有当前program对象
INVALID_VALUE
location大于等于attribute变量的最大数目默认为8.
gl.vertexAttrib3f() 同族函数
gl.vertexAttrib3f
是一系列同族函数中的一个,该系列函数的任务就是象js顶点着色器中的attribute传值。
gl.vertexAttrib1f(location,v0)
gl.vertexAttrib2f(location,v0,v1)
gl.vertexAttrib3f(location,v0,v1,v2)
gl.vertexAttrib4f(location,v0,v1,v2,v3)
v0为x坐标,v1为y坐标,v2为z坐标,v3默认填1.0就可以。
3.4 webgl函数命名规范 <函数名称><参数个数><参数类型>
4 通过鼠标点击绘点 这一节我们将灵活的拓展js传输数据到顶点着色器的能力:在鼠标点击的位置上把点绘制出来。
html代码(跟之前相同)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > webgl学习</title > </head > <body > <canvas id ="canvas" width ="400" height ="400" > </canvas > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-utils.js" > </script > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/webgl-debug.js" > </script > <script src ="https://mycode04-1252305175.cos.ap-guangzhou.myqcloud.com/webgl-lib/lib/cuon-utils.js" > </script > <script src ="./index.js" > </script > </body > </html >
js代码
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 71 72 73 74 75 76 77 78 79 var VSHADER_SOURCE = ` attribute vec4 a_Position\n; void main(){\n gl_Position = a_Position;\n gl_PointSize = 10.0;\n }\n ` var FSHADER_SOURCE = ` void main(){\n gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n }\n ` function main () { var canvas = document .getElementById ('canvas' ) var gl = canvas.getContext ('webgl' ) if (!gl) { console .log ('Failed to get the rendering context' ) retun } if (!initShaders (gl, VSHADER_SOURCE , FSHADER_SOURCE )) { console .log ('设置着色器失败' ) return } var a_Position = gl.getAttribLocation (gl.program , 'a_Position' ) if (a_Position < 0 ) { console .log ('Failed to get the storage of a_Position' ) return } canvas.onmousedown = function (ev ) { click (ev, gl, canvas, a_Position) } gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ) gl.clear (gl.COLOR_BUFFER_BIT ) var g_pointArr = [] function click (ev, gl, canvas, a_Position) { var x = ev.clientX var y = ev.clientY var rect = ev.target .getBoundingClientRect () var x = (x - rect.left - canvas.width / 2 ) / (canvas.width / 2 ) var y = (canvas.height / 2 - (y - rect.top )) / (canvas.height / 2 ) g_pointArr.push (x) g_pointArr.push (y) gl.clear (gl.COLOR_BUFFER_BIT ) var len = g_pointArr.length for (var i = 0 ; i < len; i += 2 ) { gl.vertexAttrib3f (a_Position, g_pointArr[i], g_pointArr[i + 1 ], 0.0 ) gl.drawArrays (gl.POINTS , 0 , 1 ) } } } window .onload = function ( ) { main () }
点击查看效果
5 函数解析 5.1 注册鼠标响应函数 这跟我们普通的注册dom事件是一样的。
1 2 3 canvas.onmousedown = function (ev ) { click (ev, gl, canvas, a_Position) }
5.2 计算鼠标点击时的坐标 这一段程序中比较难理解的可能就是这点了。其实很简单。
1 2 3 4 5 6 7 var x = ev.clientX var y = ev.clientY var rect = ev.target .getBoundingClientRect ()var x = (x - rect.left - canvas.width / 2 ) / (canvas.width / 2 )var y = (canvas.height / 2 - (y - rect.top )) / (canvas.height / 2 )
以x坐标为例,我们简单的说明下。
ev.clientX
为鼠标点击时鼠标距离浏览器左边的距离。rect.left
为canvas元素左边距离浏览器屏幕左边的距离。
那么x-rect.left
就是鼠标距离浏览器左边的距离减去canvas元素距离浏览器左边的距离,得到的就是鼠标距离canvas左边的距离。
x - rect.left - canvas.width / 2
这个式子计算的结果的绝对值就是鼠标距离canvas中心点(webgl原点)的距离。
(x - rect.left - canvas.width / 2) / (canvas.width / 2)
就是鼠标点击时鼠标在wegl系统中的坐标值。
y坐标同理,因为我们就得到了鼠标点击时的x、y坐标。
6. 改变点的颜色 要改变点的颜色,就要使用片元着色器。可以使用uniform变量,将颜色值传给着色器。代码如下:
html代码同上,我这里就不写了。
js代码:
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 var VSHADER_SOURCE = ` attribute vec4 a_Position\n; void main(){\n gl_Position = a_Position;\n gl_PointSize = 10.0;\n }\n ` var FSHADER_SOURCE = ` precision mediump float;\n uniform vec4 u_FragColor;\n void main(){\n gl_FragColor = u_FragColor;\n }\n ` function main () { var canvas = document .getElementById ('canvas' ) var gl = canvas.getContext ('webgl' ) if (!gl) { console .log ('Failed to get the rendering context' ) retun } if (!initShaders (gl, VSHADER_SOURCE , FSHADER_SOURCE )) { console .log ('设置着色器失败' ) return } var a_Position = gl.getAttribLocation (gl.program , 'a_Position' ) if (a_Position < 0 ) { console .log ('Failed to get the storage of a_Position' ) return } var u_FragColor = gl.getUniformLocation (gl.program , 'u_FragColor' ) canvas.onmousedown = function (ev ) { click (ev, gl, canvas, a_Position) } gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ) gl.clear (gl.COLOR_BUFFER_BIT ) var g_pointArr = [] var g_colorArr = [] function click (ev, gl, canvas, a_Position) { var x = ev.clientX var y = ev.clientY var rect = ev.target .getBoundingClientRect () var x = (x - rect.left - canvas.width / 2 ) / (canvas.width / 2 ) var y = (canvas.height / 2 - (y - rect.top )) / (canvas.height / 2 ) g_pointArr.push ([x, y]) if (x >= 0.0 && y >= 0.0 ) { g_colorArr.push ([1.0 , 0.0 , 0.0 , 1.0 ]) } else if (x < 0.0 && y < 0.0 ) { g_colorArr.push ([0.0 , 1.0 , 0.0 , 1.0 ]) } else { g_colorArr.push ([1.0 , 1.0 , 1.0 , 1.0 ]) } gl.clear (gl.COLOR_BUFFER_BIT ) var len = g_pointArr.length for (var i = 0 ; i < len; i++) { gl.vertexAttrib3f (a_Position, g_pointArr[i][0 ], g_pointArr[i][1 ], 0.0 ) console .log ('u_FragColor:' , u_FragColor) gl.uniform4f ( u_FragColor, g_colorArr[i][0 ], g_colorArr[i][1 ], g_colorArr[i][2 ], g_colorArr[i][3 ] ) gl.drawArrays (gl.POINTS , 0 , 1 ) } } } window .onload = function ( ) { main () }
点击这里查看效果
7. 程序解析 使用片元着色器就必须使用uniform变量,当然你也可以使用varying变量,不过varying变量要复杂的多,我们暂时先不讲它。
使用uniform变量之前,首先需要按照与声明attribute变量相同的格式<存储限定符><类型><变量名>
来声明uniform变量。
1 uniform vec4 u_FragColor
在声明uniform变量之前,我们还使用精度限定词指定了变量的范围和精度,本例中为中等精度。关于精度的详细讨论我们在第5章再讲。
gl.getUniformLocation(program,name)
类型
参数
含义
参数
program
指定包含顶点着色器和片元着色器的程序对象
name
指定想要获取存储地址的uniform变量名称
返回值
non-null
指定的uniform变量位置
null
指定的uniform变量不存在,或者其命名具有gl_或者webgl_前缀
错误
INVALID_OPPERATION
程序对象未能成功连接
INVALID_VALUE
name参数长度大于uniform变量名的最大长度(默认256字节)
gl.uniform4f(location,v0,v1,v2,v3)
类型
参数
含义
参数
location
将要修改的uniform变量的存储位置
v0,v1,v2,v3
指定填充uniform变量的第1-4分量
返回值
无
错误
INVALID_OPERTAION
没有当前program对象,或者location是非法的变量存储位置
gl.uniform4f
也有一系列的同族函数,gl.uniform1f()
函数传递1个值,gl.uniform2f()
函数传递2个值,gl.uniform3f()
函数传递3个值,
总结 在这一章中我们学习了,一些webgl的核心函数,并学习了如何使用它们,我们重点学习了着色器的相关知识,它是webgl的基石。