webgl 着色器 要使用 WebGL 进行绘图就必须使用着色器。
WebGL 需要两种着色器。
顶点着色器 (Vertex shader)∶顶点着色器是用来描述顶点特性(如位置、颜色等)
的程序。顶点(vertex)是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点。
片元着色器 (Fragment shader)∶进行逐片元处理过程如光照(的程序。片元(fragment)是一个WebGL术语,你可以将其理解为像素(图像的单元)。
顶点着色器 顶点着色器工作流程 示例代码
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 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" > <title > </title > <script id ="vertexShader" type ="x-shader/x-vertex" > attribute vec3 a_position; void main ( ){ gl_Position = vec4 (a_position,1.0 ); gl_PointSize = 10.0 ; } </script > <script id ="fragmentShader" type ="x-shader/x-fragment" > void main ( ){ gl_FragColor = vec4 (1.0 , 0.0 , 0.0 , 1.0 ); } </script > </head > <body > <canvas id ='glcanvas' width ='600' height ='600' > </canvas > <script > var canvas = document .getElementById ('glcanvas' ); var gl = canvas.getContext ('webgl' ) || canvas.getContext ('experimental-webgl' ); var vertexShaderSource = document .getElementById ('vertexShader' ).innerText ; var fragShaderSource = document .getElementById ('fragmentShader' ).innerText ; var vShader = gl.createShader (gl.VERTEX_SHADER ); gl.shaderSource (vShader, vertexShaderSource); gl.compileShader (vShader); var fShader = gl.createShader (gl.FRAGMENT_SHADER ); gl.shaderSource (fShader, fragShaderSource); gl.compileShader (fShader); var shaderProgram = gl.createProgram (); gl.attachShader (shaderProgram, vShader); gl.attachShader (shaderProgram, fShader); gl.linkProgram (shaderProgram); gl.useProgram (shaderProgram); var vertices = [ 0.0 , 0.5 , -0.5 , -0.5 , 0.5 , -0.5 ]; var vertex_buffer = gl.createBuffer (); gl.bindBuffer (gl.ARRAY_BUFFER , vertex_buffer); gl.bufferData (gl.ARRAY_BUFFER , new Float32Array (vertices), gl.STATIC_DRAW ); gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); var a_position = gl.getAttribLocation (shaderProgram, "a_position" ); gl.vertexAttribPointer (a_position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_position); gl.drawArrays (gl.POINTS , 0 , 3 ) </script > </body > </html >
一、在缓冲区对象中保存顶点数据
1 2 3 4 5 6 7 8 9 10 var vertices = [ 0.0 , 0.5 , -0.5 , -0.5 , 0.5 , -0.5 ];var vertex_buffer = gl.createBuffer (); gl.bindBuffer (gl.ARRAY_BUFFER , vertex_buffer); gl.bufferData (gl.ARRAY_BUFFER , new Float32Array (vertices), gl.STATIC_DRAW );
二、将缓冲区对象分配给 shader中的 attribute 变量
1 2 3 4 5 6 var a_position = gl.getAttribLocation (shaderProgram, "a_position" ); gl.vertexAttribPointer (a_position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);
表示每次取 缓冲区的两个值传给 顶点着色器的 a_position
变量
三、开始绘制图形
gl.drawArrays(gl.POINTS, 0, 3)
由于在绘制单个的点,第1个参数 mode 是g1.POINTs
设置第2个参数frst为0,表示从缓冲区中的第1个坐标开始画起
设置第3个参数count为3,表示准备绘制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 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 <html > <head > <meta charset ="utf-8" > <title > </title > <script id ="vertexShader" type ="x-shader/x-vertex" > attribute vec3 a_position; attribute float a_pointSize; void main ( ){ gl_Position = vec4 (a_position,1.0 ); gl_PointSize = a_pointSize; } </script > <script id ="fragmentShader" type ="x-shader/x-fragment" > void main ( ){ gl_FragColor = vec4 (1.0 , 0.0 , 0.0 , 1.0 ); } </script > </head > <body > <canvas id ='glcanvas' width ='600' height ='600' > </canvas > <script > var canvas = document .getElementById ('glcanvas' ); var gl = canvas.getContext ('webgl' ) || canvas.getContext ('experimental-webgl' ); var vertexShaderSource = document .getElementById ('vertexShader' ).innerText ; var fragShaderSource = document .getElementById ('fragmentShader' ).innerText ; var vShader = gl.createShader (gl.VERTEX_SHADER ); gl.shaderSource (vShader, vertexShaderSource); gl.compileShader (vShader); var fShader = gl.createShader (gl.FRAGMENT_SHADER ); gl.shaderSource (fShader, fragShaderSource); gl.compileShader (fShader); var shaderProgram = gl.createProgram (); gl.attachShader (shaderProgram, vShader); gl.attachShader (shaderProgram, fShader); gl.linkProgram (shaderProgram); gl.useProgram (shaderProgram); gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); var vertices = new Float32Array ([ 0.0 , 0.5 , -0.5 , -0.5 , 0.5 , -0.5 ]); var n = 3 ; var sizes = new Float32Array ([ 10.0 , 20.0 , 30.0 ]); var vertex_buffer = gl.createBuffer (); gl.bindBuffer (gl.ARRAY_BUFFER , vertex_buffer); gl.bufferData (gl.ARRAY_BUFFER , vertices, gl.STATIC_DRAW ); var a_position = gl.getAttribLocation (shaderProgram, "a_position" ); gl.vertexAttribPointer (a_position, 2 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_position); var size_buffer = gl.createBuffer (); gl.bindBuffer (gl.ARRAY_BUFFER , size_buffer); gl.bufferData (gl.ARRAY_BUFFER , sizes, gl.STATIC_DRAW ); var a_pointSize = gl.getAttribLocation (shaderProgram, "a_pointSize" ); gl.vertexAttribPointer (a_pointSize, 1 , gl.FLOAT , false , 0 , 0 ); gl.enableVertexAttribArray (a_pointSize); gl.drawArrays (gl.POINTS , 0 , 3 ) </script > </body > </html >
上述代码比较容易理解,就是分别创建了一个缓存来存储顶点数据 和 一个缓存来存储 顶点的尺寸信息
一个缓冲区存多个数据 可以通过设置 gl.vertexAttribPointer( )
的 stride ( 步进) 和 offset ( 偏移 ) 参数
,把多个要传到 attribute 的数据放到同一个 缓冲区。比如,可以将顶点的坐标和尺寸数据按照如下方式交错组织。
1 2 3 4 5 6 var verticesSizes = new Float32Array ([ 0.0 , 0.5 , 10.0 , -0.5 , -0.5 , 20.0 , 0.5 , -0.5 , 30.0 ]);
示例代码:
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 <html > <head > <meta charset ="utf-8" > <title > </title > <script id ="vertexShader" type ="x-shader/x-vertex" > attribute vec3 a_position; attribute float a_pointSize; void main ( ){ gl_Position = vec4 (a_position,1.0 ); gl_PointSize = a_pointSize; } </script > <script id ="fragmentShader" type ="x-shader/x-fragment" > void main ( ){ gl_FragColor = vec4 (1.0 , 0.0 , 0.0 , 1.0 ); } </script > </head > <body > <canvas id ='glcanvas' width ='600' height ='600' > </canvas > <script > var canvas = document .getElementById ('glcanvas' ); var gl = canvas.getContext ('webgl' ) || canvas.getContext ('experimental-webgl' ); var vertexShaderSource = document .getElementById ('vertexShader' ).innerText ; var fragShaderSource = document .getElementById ('fragmentShader' ).innerText ; var vShader = gl.createShader (gl.VERTEX_SHADER ); gl.shaderSource (vShader, vertexShaderSource); gl.compileShader (vShader); var fShader = gl.createShader (gl.FRAGMENT_SHADER ); gl.shaderSource (fShader, fragShaderSource); gl.compileShader (fShader); var shaderProgram = gl.createProgram (); gl.attachShader (shaderProgram, vShader); gl.attachShader (shaderProgram, fShader); gl.linkProgram (shaderProgram); gl.useProgram (shaderProgram); var verticesSizes = new Float32Array ([ 0.0 , 0.5 , 10.0 , -0.5 , -0.5 , 20.0 , 0.5 , -0.5 , 30.0 ]); var vertex_buffer = gl.createBuffer (); gl.bindBuffer (gl.ARRAY_BUFFER , vertex_buffer); gl.bufferData (gl.ARRAY_BUFFER , new Float32Array (verticesSizes), gl.STATIC_DRAW ); gl.clearColor (0.0 , 0.0 , 0.0 , 1.0 ); gl.clear (gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); var FSIZE = verticesSizes.BYTES_PER_ELEMENT ; var a_position = gl.getAttribLocation (shaderProgram, "a_position" ); gl.vertexAttribPointer (a_position, 2 , gl.FLOAT , false , FSIZE * 3 , 0 ); gl.enableVertexAttribArray (a_position); var a_pointSize = gl.getAttribLocation (shaderProgram, "a_pointSize" ); gl.vertexAttribPointer (a_pointSize, 1 , gl.FLOAT , false , FSIZE * 3 , FSIZE * 2 ); gl.enableVertexAttribArray (a_pointSize); gl.drawArrays (gl.POINTS , 0 , 3 ) </script > </body > </html >
绑定 a_position
、 a_pointSize
的核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var verticesSizes = new Float32Array ([ 0.0 , 0.5 , 10.0 , -0.5 , -0.5 , 20.0 , 0.5 , -0.5 , 30.0 ]);var FSIZE = verticesSizes.BYTES_PER_ELEMENT ; var a_position = gl.getAttribLocation (shaderProgram, "a_position" ); gl.vertexAttribPointer (a_position, 2 , gl.FLOAT , false , FSIZE * 3 , 0 );var a_pointSize = gl.getAttribLocation (shaderProgram, "a_pointSize" ); gl.vertexAttribPointer (a_pointSize, 1 , gl.FLOAT , false , FSIZE * 3 , FSIZE * 2 );
gl.getAttribLocation
的参数如下:
在前面的示例程序中,缓冲区只含有一一种数据,即顶页点的坐标,所以将stride 和 offset 设置为0 即可。然而,在本例中,当缓冲区中有了多种数据(比如此例中的顶点坐标和顶点尺寸)时,我们就需要考虑 stride 和 offset 的值
如图所示:
stride 指定相邻两个顶点间的字节数 。每一个顶点有3个数据值(两个坐标数据和一个尺寸数据),默认为 0
。因此stride 应该设置为每项数据大小的三倍,即3×FSIZE(F1loat32Array中每个元素所占的字节数 )。
offset 表示当前考虑的数据项距离首个元素的距离(单位是字节) ,即偏移参数 。在verticessizes数组中,表示图形端点的坐标数据是前面两个,所以offset 应当为0 , gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, FSIZE * 3, 0);
表示图形端点尺寸的是第三个数,所以offset 应当为 FSIZE * 2
, gl.vertexAttribPointer(a_pointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);