1. An overview of the
In the previous tutorial, WebGL Tutorial 3: Drawing a Triangle, we used buffer objects to pass data to vertex shaders. So what if this data (vertex-related data, such as normal vectors, colors, etc.) needs to continue to be passed to the slice shader?
For example, here we give each vertex of the triangle a different color and draw a colored triangle. In this case, we need to use the variable VARYING that was introduced in the WebGL Tutorial (Part 2: Transferring data to the Shader).
2. Example: Draw a triangle
Improve the code for drawing triangles (hellotriangle.js) from the previous article:
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position; \n' + // attribute variable
'attribute vec4 a_Color; \n' +
'varying vec4 v_Color; \n' +
'void main() {\n' +
' gl_Position = a_Position;\n' + // Set the vertex coordinates of the point
' v_Color = a_Color;\n' +
'}\n';
// Chip shader program
var FSHADER_SOURCE =
'precision mediump float; \n' +
'varying vec4 v_Color; \n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
// Get the
var canvas = document.getElementById('webgl');
// Get the WebGL rendering context
var gl = getWebGLContext(canvas);
if(! gl) {console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize the shader
if(! initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log('Failed to intialize shaders.');
return;
}
// Set the vertex position
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}
// specify to clear the color of
gl.clearColor(0.0.0.0.0.0.1.0);
/ / to empty < canvas >
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw a triangle
gl.drawArrays(gl.TRIANGLES, 0, n);
}
function initVertexBuffers(gl) {
// Vertex coordinates and colors
var verticesColors = new Float32Array([
0.0.0.5.1.0.0.0.0.0,
-0.5, -0.5.0.0.1.0.0.0.0.5, -0.5.0.0.0.0.1.0,]);//
var n = 3; // Number of points
var FSIZE = verticesColors.BYTES_PER_ELEMENT; // The number of bytes for each element in the array
// Create a buffer object
var vertexBuffer = gl.createBuffer();
if(! vertexBuffer) {console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to the target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Writes data to the buffer object
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
// Get the address of the attribute variable a_Position in the shader
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;
}
// Assign the buffer object to the a_Position variable
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false.5*FSIZE, 0);
// Connect the a_Position variable to the buffer object assigned to it
gl.enableVertexAttribArray(a_Position);
// Get the address of the attribute variable a_Color in the shader
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
// Assign the buffer object to the a_Color variable
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
// Connect the a_Color variable with the buffer object assigned to it
gl.enableVertexAttribArray(a_Color);
// Unbind
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return n;
}
Copy the code
1) Organization of data
Like the previous example, the data is still passed to the vertex shader through the buffer. In the vertex shader, we define two attribute variables that represent position and color information:
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position; \n' + // attribute variable
'attribute vec4 a_Color; \n'+...'}\n';
Copy the code
This means that the data needs to be passed to the vertex shader twice. The approach here is still to write all the data, including position and color, to the buffer at once, and then pass in the vertex shader in batches:
// Create a buffer object
var vertexBuffer = gl.createBuffer();
if(! vertexBuffer) {console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to the target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
// Writes data to the buffer object
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
// Get the address of the attribute variable a_Position in the shader
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;
}
// Assign the buffer object to the a_Position variable
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false.5*FSIZE, 0);
// Connect the a_Position variable to the buffer object assigned to it
gl.enableVertexAttribArray(a_Position);
// Get the address of the attribute variable a_Color in the shader
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
return -1;
}
// Assign the buffer object to the a_Color variable
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2);
// Connect the a_Color variable with the buffer object assigned to it
gl.enableVertexAttribArray(a_Color);
Copy the code
You can see that the steps of creating the buffer object, binding the buffer object to the target, and writing data to the buffer object are all consistent. However, the two steps of assigning attribute variables and joining attribute variables were carried out twice respectively. The key is the gl.vertexattribPointer () function. The previous use of this function used the default values, which are set to step and offset, respectively, to access the different data in the buffer.
The gl.vertexattribPointer () function definition shows that the data sent to the buffer is 2(size) position data and 3(size) color data, so the stride parameter is 5(size). The first time the position data is transmitted is from the initial position, so offset is 0; The second transfer of color data requires offsetting the first position data, so offfset is 2(size).
2) varying variables
As mentioned in the previous tutorial (WebGL Easy Tutorial part 2: Transferring Data to shaders), you can transfer data to the shard shader to color the scene. But here the data is passed to the vertex shader through the buffer. Therefore, we define the same varying variable for the vertex shader and the fragment shader:
// Vertex shader program
varVSHADER_SOURCE =...'varying vec4 v_Color; \n' +
'void main() {\n'+...' v_Color = a_Color;\n' +
'}\n';
// Chip shader program
varFSHADER_SOURCE =...'varying vec4 v_Color; \n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
Copy the code
The variable VARYING represents a variable that transfers data from the vertex shader to the chip shader. In the main function of the vertex shader, the attribute variable a_Color obtained from the buffer object is assigned to the predefined variable v_Color. A variable v_Color of the same type is defined in the slice shader, and the value of the variable is automatically passed into the slice shader. Finally, pass the value to gl_FragColor in the main function of the fragment shader to get the final result. The schematic diagram is as follows:
Results 3.
The final result is as follows, and you will find a smooth transition triangle with three red, green and blue angles:
4. Understand
1) Graphic assembly and rasterization
Further thinking, although each vertex is assigned a color value, why is the surface of the triangle assigned a color and smooth transition effect? The details of data transfer between vertex shaders and chip shaders — graphic assembly and rasterization — are omitted.
Points form lines, and lines form planes. The process of composing isolated points into basic graphics (primitives) is graphic assembly. The input data for graphic assembly is the value obtained by gl_Position in the vertex shader, and the value of the first argument in gl.drawarrays () determines what pixel to assemble into. In this example, the vertex shader tells the WebGL system to prepare three points, which WebGL assembles into triangles through image assembly.
It is not enough to know the assembled graphics, theoretically triangles are continuous graphics, and the general graphics display devices are discrete pieces (pixels). The image is transformed into a pixel, which is the process of rasterization.
The diagram of graphic assembly and rasterization is as follows:
2) The interpolation process
In the second section, we explained that the vertex shader and the chip shader both define the same VARIABLE VARYING v_Color, and the data is passed from the vertex shader to the chip shader. But they are not the same name. In the vertex shader, the varying variable is the value associated with the vertex, whereas, after the graphics are assembled and rasterized, the varying variable in the fragment shader is the value associated with the fragment. Also, this value is obtained by interpolation.
In this example, three vertices are assigned three different color values. WebGL interpolates the color values of each slice (pixel) in the triangle based on the color values of the three vertices and passes them to the slice shader. The interpolation process can be thought of as a gradient ribbon. Once you determine the starting and ending colors, you can get the colors anywhere in between.
Reference 5.
Some of the original code and illustrations come from the WebGL Programming Guide. Code and data addresses
The original address