WebGL passes multiple data in the same buffer to the step and offset parameters of gl.vertexAttribPointer() of the vertex shader

Table of Contents

Table of Contents

In order to pass vertex coordinates into the shader, you need to follow these five steps:

but! ! !

Sample code:

Function specification of gl.vertexAttribPointer()

stride parameter

Vertex coordinate data

offset parameter

vertex size data


First, analyze how to implement the example below as three points of different sizes and positions.

In order to pass the vertex coordinates into the shader, you need to follow the following five steps:

1. Create a buffer object

2. Bind the buffer object to the target

3. Write the vertex coordinate data into the buffer object

4. Assign the buffer object to the corresponding attribute object

5. Activate the attribute variable

Of course, you can create two buffer objects to store coordinate data and non-coordinate data respectively, and then pass them to the corresponding shader variables a_Position and a_pointSize

But! ! !

Using multiple buffer objects to pass multiple types of data to the shader is more suitable for situations where the amount of data is not large. When a program has complex three-dimensional graphics with tens of thousands of vertices, it can be difficult to maintain all the vertex data. Imagine what would happen if the 3D model in the program had tens of thousands of vertices. Therefore,WebGL allows us to pack the coordinates and size data of vertices into the same buffer object, and access different types of data in the buffer object through some mechanism. For example, the coordinates and size data of the vertices can be interleaved as follows, as shown in the figure below:


It can be seen that once we interleave several “vertex-by-vertex” data (coordinates and dimensions) in an array and write the array to a buffer object. WebGL needs to obtain certain specific data (coordinates or dimensions) from the buffer differently, that is, using the fifth parameter stride and the sixth parameter offset of the gl.vertexAttribPointer() function. Let’s take a look at the code.

Sample code:

//Vertex shader program
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\\
' +
  'attribute float a_PointSize;\\
' +
  'void main() {\\
' +
  ' gl_Position = a_Position;\\
' +
  ' gl_PointSize = a_PointSize;\\
' +
  '}\\
';

// Fragment shader program
var FSHADER_SOURCE =
  'void main() {\\
' +
  ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\\
' +
  '}\\
';

function main() {
  // Retrieve <canvas> element
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  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;
  }
  //Set the vertex coordinates and point size
  var n = initVertexBuffers(gl);
  if (n < 0) {
    console.log('Failed to set the vertex information');
    return;
  }
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.POINTS, 0, n);
}

function initVertexBuffers(gl) {
  var verticesSizes = new Float32Array([
    //Vertex coordinates and point size
     0.0, 0.5, 10.0, // first point
    -0.5, -0.5, 20.0, // second point
     0.5, -0.5, 30.0 // third point
  ]);
  var n = 3; // The number of vertices

  //Create buffer object
  var vertexSizeBuffer = gl.createBuffer();
  if (!vertexSizeBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
  }
  //Write vertex coordinates and dimensions to the buffer and open
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW);
  var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
  // Get the storage location of a_Position, allocate the buffer and open it
  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 -1;
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
  gl.enableVertexAttribArray(a_Position); // Enable allocation
  // Get the storage location of a_PointSize, allocate the buffer and open it
  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 -1;
  }
  gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2);
  gl.enableVertexAttribArray(a_PointSize); // Enable buffer allocation
  return n;
}

First, we define a typed array verticesSizes. Next: create a buffer object, bind it, and write data to the buffer object. We then store the size (in bytes) of each element in the verticeSize array into FSIZE, which we will use later. Typed arrays have the BYTES_PER_ELEMENT attribute, from which you can get the number of bytes occupied by each element in the array.

Note that the verticesSizes parameter setting here is different from simply passing in vertex coordinates, because two types of data are stored in the buffer object: vertex coordinates and vertex size.

Function specification of gl.vertexAttribPointer()

stride parameter

The parameter stride represents the number of bytes of all data of a single vertex (here, the coordinates and size of the vertex) in the buffer object, that is, the distance between two adjacent vertices, that is, the stride parameter.

Vertex coordinate data

When the buffer contains only one kind of data, for example, the coordinates of the vertex, we can set it to 0. However, here, when there is a variety of data in the buffer (such as the vertex coordinates and vertex dimensions here), we need to consider the value of the parameter stride, as shown in the following figure:

As shown in the figure above, each vertex has 3 data values (two coordinate data and one size data), so it should be set to three times the size of each item of data, that is, 3×FSIZE (the word occupied by each element in Float32Array Section number).

offset parameter

The offset parameter represents the distance between the currently considered data item and the first element, that is, the offset parameter. In the verticesSizes array, the coordinate data of the vertices is placed at the front, so it should be 0. Therefore, when we call the gl.vertexAttribArray() function, we pass in the stride parameter and offset parameter as follows

In this way, we assign the part of the vertex coordinate data in the buffer to the attribute variable a_Position in the shader and turn on the variable.

Vertex size data

Next do the same thing with the vertex size data: assign the vertex size data in the buffer object to a_PointSize. However, in this example, the buffer object is still the same, but this time the data of concern is different. We need to set the parameter to the initial position of the vertex size data in the buffer object. Among the three values about a certain vertex, the first two are vertex coordinates, and the latter one is the vertex size, so it should be set to FSIZE*2 (see the stride explanation diagram above). We call the gl.vertexAttribArray() function as follows and set the stride parameter and offset parameter correctly

After opening the a_PointSize variable of the allocated buffer object, the remaining task is to call gl.drawArrays() to perform the drawing operation.

When the vertex shader is executed again, the WebGL system will correctly extract the data from the buffer according to the stride and offset parameters, assign it to each attribute variable in the shader in turn, and draw it (as shown in the figure below).