Buffer 缓冲区

要实现上图效果,画出三个不同尺寸,不同颜色的点,可由下面代码实现。

<script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute float a_PointSize; void main(){ gl_Position = a_Position; gl_PointSize = a_PointSize; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> precision mediump float; uniform vec4 u_FragColor; void main(){ gl_FragColor = u_FragColor; } </script> function init(){ var canvas = document.getElementById('canvas'), gl = canvas.getContext('webgl'); if(!gl){ console.log('Failed to get WebGL.'); return; } if(!initShaders(gl, "vertexShader", "fragmentShader")){ console.log('Failed to init Shaders.'); return; } // 获取 attribute, uniform 变量的存储地址 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'), a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize'), u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor'); if(a_Position < 0 || a_PointSize < 0 || !u_FragColor){ console.log('Failed to get the location'); console.log(a_Position, a_PointSize, u_FragColor); return; } // 设置背景色, 清空颜色缓冲区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 红点 gl.vertexAttrib3f(a_Position, 0.0, 0.5, 0.0); gl.vertexAttrib1f(a_PointSize, 10.0); gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0); gl.drawArrays(gl.POINTS, 0, 1); // 绿点 gl.vertexAttrib3f(a_Position, -0.5, -0.4, 0.0); gl.vertexAttrib1f(a_PointSize, 20.0); gl.uniform4f(u_FragColor, 0.0, 1.0, 0.0, 1.0); gl.drawArrays(gl.POINTS, 0, 1); // 蓝点 gl.vertexAttrib3f(a_Position, 0.5, -0.4, 0.0); gl.vertexAttrib1f(a_PointSize, 30.0); gl.uniform4f(u_FragColor, 0.0, 0.0, 1.0, 1.0); gl.drawArrays(gl.POINTS, 0, 1); }

传值方法 gl.vertexAttrib3f( ), gl.vertexAttrib1f( ), gl.uniform4f( ) 和绘图方法 gl.drawArrays( ) 一共被调用了3次, 如果你要画成千上万个点,怎么写? 传一千次?
每一次都是一滴一滴的滴水,还是一次性的把水倒进杯子了好呢?

缓冲区对象

缓冲区对象是 WebGL 系统中的一块内存区域,我们可以一次性地向缓冲区对象中填充大量的顶点数据。具体步骤为:

创建缓冲区对象 gl.createBuffer( )
绑定缓冲区对象 gl.bindBuffer( ) 把缓冲区对象绑定到 target: Vertex Shader 的 gl.ARRAY_BUFFER (顶点属性) 或 gl.ELEMENT_ARRAY_BUFFER (数据的索引) 中
将数据写入缓冲区对象 gl.bufferData( ) 不是直接向 gl.createBuffer( ) 出来的写入数据,而是向绑定了的 target 写入数据
将缓冲区对象分配给 attribute 变量 gl.vertexAttribPointer( ) 缓冲区对象的指针指向 attribute 变量的 location,一次性写入多个数据
开启 attribute 变量 gl.enableVertexAttribArray( ) 使 Vertex Shader 能够访问缓冲区内的数据
因为上面的方法只负责顶点着色器,而没有顾及到片元着色器,可用下面代码一次性写入数据画3个点,都是绿色。 <script id="vertexShader" type="x-shader/x-vertex"> attribute vec4 a_Position; attribute float a_PointSize; void main(){ gl_Position = a_Position; gl_PointSize = a_PointSize; } </script> <script id="fragmentShader" type="x-shader/x-fragment"> precision mediump float; uniform vec4 u_FragColor; void main(){ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); } </script> function init(){ var canvas = document.getElementById('canvas'), gl = canvas.getContext('webgl'); if(!gl){ console.log('Failed to get WebGL.'); return; } if(!initShaders(gl, "vertexShader", "fragmentShader")){ console.log('Failed to init Shaders.'); return; } // 获取 attribute 变量的存储地址 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'), a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize'); if(a_Position < 0 || a_PointSize < 0){ console.log('Failed to get the location'); console.log(a_Position, a_PointSize); return; } var vertics = new Float32Array([0.0, 0.5, -0.5, -0.4, 0.5, -0.4]), sizes = new Float32Array([10.0, 20.0, 30.0]); // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, vertics, a_Position, 2); initVertexBuffer(gl, sizes, a_PointSize, 1); // 设置背景色, 清空颜色缓冲区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 画点 gl.drawArrays(gl.POINTS, 0, 3); } function initVertexBuffer(gl, data, location, size){ var vertexBuffer = gl.createBuffer(); if(!vertexBuffer){ console.log('Failed to create the buffer object.'); return ; } gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); gl.bufferData( gl.ARRAY_BUFFER, data, gl.STATIC_DRAW ); gl.vertexAttribPointer( location, size, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray( location ); }

这时候,你用了两个 Buffer 来装数据,虽然数据可以一次性写入,但还是有问题的。当你要画一千个点时,有可能在代码里的数据就出错, 多了或少了,或次序错了。怎么保证 a_Position 中的 Float32Array([ ]) 每两个数据对应好 a_PointSize 中的 Float32Array([ ]) 每一个数据?

一个缓冲区放多种数据

如图,在一个 Float32Array([ ]) 中,放两个位置数据再放一个尺寸数据,每三个数据为一组,不断循环。主要是修改 gl.vertexAttribPointer( location, size, type, normalized, stride, offset ) 中的 stride 和 offset。stride 的中文意思是“步进”,在这里,stride 是每个字节数 BYTES_PER_ELEMENT 的3倍。每3倍跨一步,位置数据的 offset 都是0,而尺寸数据的 offset 就在两个位置数据之后,即两个 BYTES_PER_ELEMENT。

function init(){ ... ... var verticsSize = new Float32Array([ 0.0, 0.5, 10.0, -0.5, -0.4, 20.0, 0.5, -0.4, 30.0]); var element_size = verticsSize.BYTES_PER_ELEMENT; // 创建缓冲区对象,对其写入数据 initVertexBuffer(gl, verticsSize, a_Position, a_PointSize, element_size); // 设置背景色, 清空颜色缓冲区 gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT); // 画点 gl.drawArrays(gl.POINTS, 0, 3); } function initVertexBuffer(gl, data, a_Position, a_PointSize, size){ var vertexBuffer = gl.createBuffer(); if(!vertexBuffer){ console.log('Failed to create the buffer object.'); return ; } gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); gl.bufferData( gl.ARRAY_BUFFER, data, gl.STATIC_DRAW ); // 位置数据 gl.vertexAttribPointer( a_Position, 2, gl.FLOAT, false, size * 3, 0); gl.enableVertexAttribArray( a_Position ); // 尺寸数据 gl.vertexAttribPointer( a_PointSize, 1, gl.FLOAT, false, size * 3, size * 2); gl.enableVertexAttribArray( a_PointSize ); }

用 varying 来实现传多种颜色

如果用 Buffer 来只传一次数据,达到第一张图的效果,请转到 varying.html