/WebGL 样例的解释

Created Thu, 03 Mar 2022 10:31:38 +0800 Modified Fri, 09 Aug 2024 13:25:47 +0000
1235 Words 6 min

Context

  1. Create an HTML5 canvas
  2. Get the canvas id
  3. Obtain WebGL Context

The parameter WebGLContextAttributes is not mandatory.

AttributesDescriptionDefault value
alphatrue: provide an alpha buffer to the canvas;true
depthtrue: drawing buffer contains a depth buffer of at least 16 bits;true
stenciltrue: drawing buffer contains a stencil buffer of at least 8 bits;false
antialiastrue: drawing buffer performs anti-aliasingtrue
premultipliedAlphatrue: drawing buffer contains colors with pre-multiplied alphatrue
preserveDrawingBuffertrue: buffers will not be cleared and will preserve their values until cleared or overwritten by the authorfalse
let canvas = document.getElementById("my_canvas");
let context = canvas.getContext("webgl", { antialias: false, stencil: true });

Geometry

Definition

A 2D or 3D model drawn using vertices is call a mesh.

Each facet in a mesh is called a polygon and a polygon is made of 3 or more vertices.

// create a 2D triangle which lies on the coordinates {(-5, -5), (5, -5), (5, 5)}
let vertices = [
    -0.5, -0.5   // Vertex 0
    0.5, -0.5,   // Vertex 1
    0.5, 0.5,    // Vertex 2
];

Geometry

Similarly, we can create an array for the indices follow the sequence.

let indices = [0, 1, 2];
  • drawArrays(): pass the vertices of the primitive using JavaScript arrays.
  • drawElements(): pass both vertices and indices of the primitive using JavaScript arrays.

Buffer Objects

A buffer object indicates a memory area allocated in GPU.

We can store data of the models corresponding to vertices, indices, color and etc.

There are 2 types of buffer objects:

  • Vertex Buffer Object(VBO): It holds the per-vertex data of the graphical model that is going to be rendered.
  • Index Buffer Object(IBO): It holds the indices of the graphical model that is going to be rendered.

After defining the required geometry and storing them in JavaScript arrays, we need to pass these arrays to the buffer objects, from where the data will be passed to the shader programs.

  1. Create an empty buffer.

    let vertex_buffer = gl.createBuffer();
    let index_buffer = gl.createBuffer();
    
  2. Bind an appropriate array object to the empty buffer.

    void bindBuffer(enum target, Object buffer);
    
    // ARRAY_BUFFER represents vertex data
    gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
    // ELEMENT_ARRAY_BUFFER represent index data
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
    
  3. Pass the data (vertices/indices) to the buffer using one of the typed arrays.

    void bufferData(enum target, Object data, enum usage);
    // Usage specifies how to use the buffer object data to draw shapes
    // gl.STATIC_DRAW -- Data will be specified once and used many times.
    // gl.STREAM_DRAW -- Data will be specified once and used a few times.
    // gl.DYNAMIC_DRAW -- Data will be specified repeatedly and used many times.
    
    // vertex buffer
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    // index buffer
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
    
  4. Unbind the buffer (Optional/Recommended).

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
    

Shader

Shaders are written in ES SL which has variables of its own data types, qualifiers, built-in inputs and outputs.

Data Types

TypeDescription
voidempty value
booltrue or false
intsigned integer
floatfloating scalar
vec2, vec3, vec4n-component floating point vector
bvec2, bvec3, bvec4boolean vector
ivec2, ivec3, ivec4signed integer vector
mat2, mat3, mat42x2, 3x3, 4x4 float matrix
sampler2Daccess a 2D texture
samplerCubeaccess cube mapped texture

Qualifiers

QualifierDescription
attributeacts as a link between a vertex shader and OpenGL ES for per-vertex data. Its value changes for every execution of the vertex shader.
uniformlinks shader programs and the WebGL application. Its value is read-only. It can be used for to declare a variable with any basic data types: uniform vec4 lightPosition;.
varyingforms a link between a vertex shader and fragment shader for interpolated data. It can be used with the following data types: float, vec2, vec3, vec4, mat2, mat3, mat4, arrays like: varying vec3 normal;

Vertex Shader

Vertex shader is a program code, which is called on every vertex. Programmer have to define attribute in code of vertex shader to handle data. The attribute point to a VBO written in JavaScript.

Predefined Variables

OpenGL ES SL provides the following predefined variables for every vertex shader

VariablesDescription
highp vec4 gl_PositionHolds the position of the vertex
mediump float gl_PointSizeHolds the transformed point size
attribute vec2 coordinates;

void main(void) {
    gl_Position = vec4(coordinates, 0.0, 1.0);
}

gl_Position is the predefined variable which is available only in the vertex shader. It contains the vertex position. As vertex shader is a per-vertex operation, the gl_Position value is calculated for each vertex.

Fragment Shader

A mesh is formed by multiple triangles, and the surface of each triangle is known as a fragment.

Fragment shader is the code that runs on every pixel on each fragment. This is written to calculate and fill the color on individual pixels.

Predefined Variables

VariablesDescription
mediump vec4 gl_FragCoord;Holds the fragment position within the frame buffer
bool gl_FrontFacing;Holds the fragment that belongs to a front-facing primitive
mediump vec2 gl_PointCoord;Holds the fragment position within a point
mediump vec4 gp_FragColor;Holds the output fragment color value of the shader
mediump vec4 gl_FragData[n];Holds the fragment color for color attachment n
void main(void) {
    gl_FragColor = vec4(0, 0.8, 0, 1);
}

Store and Compiling

let vertCode =
  "attribute vec2 coordinates;" +
  "void main(void) {" +
  "gl_Postion = vec4(coordinates, 0.0, 1.0);" +
  "}";

let fragCode = "void main(void) {" + "gl_FragColor = vec4(0, 0.8, 0, 1);" + "}";

Compilation involves following 3 steps

  • Creating the shader object
  • Attaching the source code to the created shader object
  • Compiling the program
let vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);

Same process for fragment shader

let fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);

Combined Program

  • Create a program object
  • Attach both the shaders
  • Link both the shaders
  • Use the program
let shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);

Associating Attributes & Buffer Objects

  • Get the attribute location
  • Point the attributes to a vertex buffer object
  • Enable the attribute
// ulong getAttribLocation(Object program, string name)
let coordinatesVar = gl.getAttribLocation(shaderProgram, "coordinates");

// void vertexAttribPointer(location, int size, enum type, bool normalized, long stride, long offset)
gl.vertexAttribPointer(coordinatesVar, 3, gl.FLOAT, false, 0, 0);

gl.enableVertexAttribArray(coordinatesVar);

Drawing a Model

drawArrays()

void drawArrays(enum mode, int first, long count);
  • mode: gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRANGLE_FAN, gl.TRIANGLES.
  • first: specified the starting element in the enabled arrays. (Non-negative)
  • count: specifies the number of elements to be rendered.

WebGL will create the geometry in the order in which the vertex coordinates while rendering the shapes.

draw a triangle:

let vertices = [-0.5, -0.5, -0.25, 0.5, 0.0, -0.5];
gl.drawArrays(gl.TRIANGLES, 0, 3);

Triangle

draw two contiguous triangles:

let vertices = [
  -0.5, -0.5, -0.25, 0.5, 0.0, -0.5, 0.0, -0.5, 0.25, 0.5, 0.5, -0.5,
];
gl.drawArrays(gl.TRIANGLES, 0, 6);

Triangle 1

drawElements()

void drawElements(enum mode, long count, enum type, long offset);
  • mode: same as drawArrays();
  • count: same as drawArrays();
  • type: specifies the data type of the indices which must be UNSIGNED_BYTE or UNSIGNED_SHORT;
  • offset: specifies the starting point for rendering, usually the first element (0);

If use drawElements() to draw a model, then index buffer object should also be created along with the vertex buffer object. The vertex data will be processed once and used as many time as mentioned in the indices.

draw a triangle:

let vertices = [-0.5, -0.5, 0.0, -0.25, 0.5, 0.0, 0.0, -0.5, 0.0];
let indices = [0, 1, 2];

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

Triangle

draw two contagious triangles:

let vertices = [
  -0.5, -0.5, 0.0, -0.25, 0.5, 0.0, 0.0, -0.5, 0.0, 0.25, 0.5, 0.0, 0.5, -0.5,
  0.0,
];
let indices = [0, 1, 2, 2, 3, 4];

gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);

Triangle 1