Shiv ENOW large front end

Company official website: CVTE(Guangzhou Shiyuan Stock)

Team: ENOW team of CVTE software Platform Center for Future Education

The author:

preface

Learning WebGL programs requires learning two new things, one is the WebGL API and the other is the shader language. Shader language is the programming language used to control GPU rendering, while WebGL API is the bridge between JavaScript and shader language. JavaScript indirectly controls GPU execution through WebGL API. For example, we use interfaces such as createShader and compileShader to initialize shaders. JavaScript compiles shader code through the WebGL API and executes it on the GPU to initialize the GPU state.

This section is intended to help you follow the code examples in subsequent articles, but familiarity with GLSL ES syntax will also give you more flexibility in writing shaders. The introduction to the API and programming language will be a bit more boring, so you can skim it and come back to it later.

WebGL API based

The so-called API is a set of application programming interfaces. In order to control the graphics card hardware to create 3D scenes on the Web, the hardware details of the graphics card are encapsulated and abstracted, and a series of functions that can control the GPU rendering pipeline are provided. The collection of these functions is WebGL API.

Get context

A WebGL context CanvasRenderingContext can be obtained by using the getContext method of the Canvas Canvas object, whose methods and properties are the WebGL API. These methods and properties are defined using OpenGL ES.

gl = canvas.getContext("webgl"); gl = canvas.getContext("webgl2"); Webgl2.0, currently not enabled by default on safariCopy the code

Initialize the shader

Initialize shader, that is, the string form of GLSL ES code is compiled into the graphics card can run shader program, specific can be divided into the following steps:

  1. Create a shader objectgl.createShader(type)

Creates a vertex shader or slice shader based on the parameters passed in.

  1. Fill the shader object with the shader program’s source code gl.shaderSource(shader, source)

  2. Compile shader object gl.pileshader

GLSL ES programs need to be compiled into binary executable format, which is what WebGL systems really use.

  1. Creating program objectsgl.createProgram()

A shader object manages a vertex shader or a slice shader, while a program object is a container that manages shader objects. In WebGL, a program object must contain a vertex shader object and a slice shader object.

  1. Assign shaders to program objects gl.attachShader(program, shader)

  2. LinkProgram object gl.linkProgram(Program)

After assigning a vertex shader and a slice shader to a program object, you need to concatenate the two, Ensure that the uniform variables of the same name are of the same type. Ensure that the number of attributes, UNIFORM, and VARYING variables in the shader does not exceed the upper limit of the shader. After connecting, you should check whether the connection is successful by calling getProgramParameter(program, pname) as follows:

if(! gl.getProgramParameter(program, gl.LINK_STATUS)) {console.error(gl.getProgramInfoLog(program))
}
Copy the code
  1. Working with program objectsgl.useProgram(programe)

The existence of this function gives WebGL a powerful feature of preparing multiple program objects before drawing, and then switching program objects as needed during drawing.

The above is the corresponding steps to initialize the shader, the specific implementation code is as follows:

 // Initialize the shader method
 function initShader(gl, vertexSource, fragmentSource) {
   const vertexShader = gl.createShader(gl.VERTEX_SHADER);
   const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
   // Attach the shader source code to the shader
   gl.shaderSource(vertexShader, vertexSource);
   gl.shaderSource(fragmentShader, fragmentSource);

  // Compile the shader
  gl.compileShader(vertexShader);
  gl.compileShader(fragmentShader);

  // Create a program object
  const program = gl.createProgram();
  // Appends the compiled shader to the program object
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  // Link program objects
  gl.linkProgram(program);
  // The WebGL engine uses this program object
  gl.useProgram(program);

  return program;
}
Copy the code

Pass data to the shader

Depending on the data, you can pass data to the shader through the attribute variable, which transmits data related to vertices, or the UNIFORM variable, which transmits data unrelated to vertices or identical to all vertices. Passing data to the shader, that is, binding the data to the corresponding shader variable, in WebGL, the general flow is as follows:

  1. Define the corresponding variables in the shader
// Vertex shader
attribute vec4 a_Position;
void main() {
  gl_Position = a_Position;
  gl_PointSize = 10.0;
}

// Chip shader
uniform vec4 u_FragColor;
void main() {
  gl_FragColor = u_FragColor;
}
Copy the code

(Attribute and uniform are const -like declarations, described later in this article in shader language basics.)

  1. Get variable position

For attribute variables, use getAttribLocation(program, name) to get the location of variables. For uniform variables, use getUniformLocation(program, name).

  1. Assign values to variables

WebGL provides several apis for assigning values to Uniform and Attribute variables

gl.vertexAttrib1f(location, v0)
...
gl.vertexAttrib4f(location, v0, v1, v2, v3)
gl.uniform1f(location, v0)
...
gl.uniform4f(location, v0, v1, v2, v3)
Copy the code

For attribute variables, if you are drawing a graph consisting of multiple vertices, you need to pass multiple points to the vertex shader at once. This requires the use of a cache object, which is used as follows:

var arr = [-. 5.. 5.3..2.];
var vertices = new Float32Array(arr); // Use typed arrays to optimize performance
var vertexBuffer = gl.createBuffer(); // Create a buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // Specify the processing method of the buffer object (gl.array_buffer)
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // Write data to a buffer object
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false.0.0); // Assign data in the buffer to variables
gl.enableVertexAttribArray(a_Position); // Enable variables
Copy the code

The use of caches will be covered in a separate article that will not be covered here.

This is a brief introduction to the WebGL API, and for more information on the API, you can check out the corresponding WebGL specification.

Shader language basics

The shader language here refers to the GLSL ES programming language, which is formed on the basis of OpenGL Shader Language (GLSL) by deleting and simplifying some functions. GLSL ES has a similar syntax to C, with some new built-in variables, data types, and mathematical functions, but some features of C are missing.

Data types and variable declarations

Declare a variable with the following structure: [< storage qualifier >] < data type > < variable name >

1. Name variables

The variable names specified in GLSL ES are similar to those specified in C. They can only contain letters, digits, and underscores (_) and cannot start with a number. In addition, variable names cannot start with gl_, webGL_, or _webgl_ and cannot be the same as existing keywords.

2. Store qualifiers

qualifiers describe Special instructions
const As defined in JavaScript, the value of the representation variable cannot be changed
Example: const init size = 100
attribute The attribute variable can only be defined in a vertex shader. It is used to receive vertex-by-vertex data from a JavaScript program, such as vertex coordinates, colors, normals, and so on.
Example: attribute float a_PointSize; (Different vertices may receive different values). The maximum number of attribute variables that can be held in a vertex shader is device-dependent and can be obtained by accessing the gl_MaxVertexAttribs variable. Generally, at least eight are supported.
The type of an attribute variable can only be float, vec(n), or mat(n).
uniform Uniform variables can be defined in vertex shaders and slice shaders. The uniform variable is used to receive data that is passed from a JavaScript program that is vertex-independent or identical to all vertices, such as transformation matrices for all vertices.
Example: Uniform MAT4 u_Matrix; All vertices share the same u_Matrix value. Vertex shader and can accommodate in a fragment shader uniform the maximum number of variables related to equipment, and each are not identical, may be achieved through gl_MaxVertexUniformVectors and gl_MaxFragmentUniformVectors variables respectively.
Uniform variables can be of any type except arrays and structs.
varying To use the varying limit, it must be declared in both the vertex shader and the fragment shader. Its purpose is to transfer data from the vertex shader to the fragment shader, namely, declared in the vertex shader and used in the fragment shader. The variables it modifies are interpolated before being passed to the chip shader.
Example: VARYING VEC2 v_TexCood; Varying specifies the maximum number of variables. The maximum number of variables is dependent on devices. Generally, at least eight variables are supported.
Like attribute variables, varying variables can be of only float, VEC (n), or MAT (n) type.

3. Data types

Basic types of
type describe
int integer
float Single-precision floating point type
bool Boolean value (true or false)

It is worth mentioning that GLSL ES does not support string types.

An array of

GLSL ES supports array types, but only one-dimensional arrays, and array objects do not support methods such as POP or push. Arrays cannot be initialized once at declaration time, but must be explicitly initialized for each element.

// 1. Specify the size of the array when declaring
float arr[2];
arr[0] = 1.0;
arr[1] = 2.0;
Copy the code
Vectors and matrices

Vectors and matrices are very important data structures in GLSL ES, and these two types are very suitable for computer graphics. Vector and matrix variables both contain multiple elements, each of which is a numeric value (integer, floating point, or Boolean).

category The data type describe
vector Vec2, VEC3, vec4 A vector with elements of type 2, 3, 4 floating point
Ivec2, IVEC3, IVEC4 A vector with 2, 3, 4 elements of integer type
Bvec2, BVEC3, bVEC4 A vector with 2, 3, 4 Elements of Boolean type
matrix Mat2, mat3, mat4 A matrix with elements of type 2 x 2, 3 x 3, 4 x 4 floating point

The storage mode of matrix can be divided into row main sequence and column main sequence. Row main sequence refers to storing rows in memory as the priority unit, as shown in the following figure:

The column main sequence is stored in memory column by column as the priority unit, as shown in the following figure:

In GLSL ES, the matrix is stored in column main order, as shown in the figure above. In GLSL ES, it can be described as follows:

mat4 m4 = mat4 (
  0.0.1.0.2.0.3.0.4.0.5.0.6.0.7.0.8.0.9.0.10.0.11.0.12.0.13.0.14.0.15.0
)
Copy the code

GLSL ES provides a very flexible way to create vectors and matrices, as follows:

vec2 v2 = vec3(1.0.2.0);      // set v2 to (1.0, 2.0)
vec3 v3 = vec3(1.0);           // set v3 to (1.0, 1.0, 1.0)
vec4 v4 = vec4(v2, v3);        // Fill v4 with v2, fill the rest with v3, and finally v4 is (1.0, 2.0, 1.0, 1.0)

mat2 m2_1 = (
  0.0.1.0.2.0.3.0
);                             // Pass in the value of each element to construct the matrix
mat m2_2 = mat2(v2, v2);       // Construct a 2 x 2 matrix using 2 2d vectors
mat m2_3 = mat2(0.0.1.0, v2); // Use 2 floating point numbers and a two-dimensional vector to construct the matrix
mat m2_4 = mat2(2.0);          // Generate a 2 x 2 matrix where the diagonal elements are 2.0
Copy the code

The way to access vectors and elements in matrices is also very flexible, supporting the dot operator (.). Or the [] symbol.

1. Vector element access

Accessing the components of a vector can be treated as an array and accessed by subscripting.

vec4 a_Color = vec4(0.1.0.2.0.3.1.0);
a_Color[0]  // get the red channel component of vector a_Color
Copy the code

Since vectors can be used to store vertex coordinates, color, and texture coordinates, GLSL ES supports the following three component names to obtain vector components:

category describe
x, y, z, w Used to get vertex coordinate classification
r, g, b, a Used to get the color classification
s, t, p, q Used to get texture coordinate components
vec4 a_Color = vec4(0.1.0.2.0.3.1.0);
a_Color.r  // get the red channel component of vector a_Color
a_Color.rb // (0.1, 0.3)
Copy the code

In fact, the x, R, and S components of any vector return the first component, the Y, G, and T components return the second, and so on, so you can use them interchangeably, too.

2. Matrix element access uses the [] operator to get the matrix elements. The first [] operator represents a column of the matrix, and two consecutive [] operators represent an element of a column of the matrix.

mat4 m4 = mat4 (
  0.0.1.0.2.0.3.0.4.0.5.0.6.0.7.0.8.0.9.0.10.0.11.0.12.0.13.0.14.0.15.0
)

m4[0] // Get the first column of the matrix, i.e. [0.0, 1.0, 2.0, 3.0]
m4[0] [1] // Get the second element of the first column of the matrix, i.e. (1.0)
Copy the code
Sampler (texture)

Sampler is a special basic data type in coloring language, which is different from C language. It is specially used for texture sampling operations. Typically, a sampler variable represents a set or set of texture maps. There are several common sampler types:

Sampler type describe
sampler2D Used to access floating – point 2d textures
sampler3D Used to access floating – point 3d textures
samplerCube Used to access floating point cubic map textures

In general, samplers are decorated with a uniform qualifier, as follows

uniform sampler2D u_Sampler;
Copy the code

Operations, program flows, functions

GLSL ES operators, program flows, and function definitions are not very different from C or JavaScript, so I won’t repeat them here. If you need them, you can refer to the OpenGL ES specification.

Preprocessing instructions and precision determiners

Preprocessing instruction

Preprocessor instructions are used to preprocess the code before it is actually compiled, as shown in the following code that limits the precision of float to mediump if the built-in GL_ES macro is defined.

#ifdef GL_ES
precision mediump float;
#endif
Copy the code

There are three preprocessing instructions that can be used in GLSL ES:

1. If the condition is true, execute the #if expression #endif 2. 2. If no macro is defined, execute # ifNdef macro #endifCopy the code

Precision determiner

Precision qualifiers are used to limit the precision with which data of a given type can be used. Their purpose is to help shader programs reduce memory overhead, such as low precision when a WebGL program needs to run on hardware with limited memory. Currently, WebGL programs support highP, Mediump and LOWP, and the declaration format is as follows:

< precision qualifier > < data type >

The shader already implements the default precision as follows:

Shader type The data type The default precision
Vertex shader int highp
float highp
sampler2D lowp
samplerCube lowp
Chip shader int mediump
float There is no
sampler2D lowp
samplerCube lowp

Float does not have a default precision, so if float is declared in the slice shader and no precision value is defined, you can catch the exception and see the following error message:

Built-in variables and built-in functions

Common built-in variables

category Built-in variables
Vertex shader built-in variable Gl_Position (vertex coordinates), gl_PointSize (vertex size), gl_Normal (vertex normals)
Chip shader built-in variable Gl_FragColor (chip color), gl_FragCoord (chip coordinate), gl_FragDepth (chip depth)

Common built-in functions

category Built-in function
The Angle function Radians (degrees), degrees (degrees)
Trigonometric functions Sine, cosine, tangent, asin arcsine, ACos arccosine, atan arctangent
Exponential function Pow (x”), exp (natural exponent), log (natural logarithm), exp2 (2″), log2 (logarithm base 2),
SQRT, inversesqrt, inverse of square root
The generic function Abs (absolute value), MIN (minimum value), Max (maximum value), MOD (remainder), sign (plus or minus sign), LOor (round down), CeiL (round up), CLAMP (limited range), MIX (linear interpolation), step (step function), Smoothstep (interpolated stepping function), Fract (get decimal part)
Geometric function Length, distance, dot, cross, nor-malize, reflect, faceForward
Matrix function matrixCmpMult
The loss function LessThan (lessThan), lessThanEqual (lessThan or equal to), greaterThan (greaterThan or equal to), greaterThanEqual (equal), note-qual (equal), Any (true if any element is true), all (true if all elements are true), not (complement by element)
Texture query function Texture2D (get textures in 2d textures), textureCube (get textures in cube textures), texture2DProj (projection version of texture2D)

So much for the syntax of the shader language. If you need more detailed information, you can check out the OpenGL ES specification.

The resources

  • OpenGL Programming Guide (version 8)
  • Introduction to WebGL and Practice provides an in-depth understanding of GLSL syntax