banner
cos

cos

愿热情永存,愿热爱不灭,愿生活无憾
github
tg_channel
bilibili

Youth Training Camp | "Basics of WebGL"

Key Content of This Lesson#

Why WebGL / Why GPU?#

  • What is WebGL?
    • GPU ≠ WebGL ≠ 3D
  • Why is WebGL not as simple as other frontend technologies?

Modern Image Systems#

  • Raster: Almost all modern graphics systems are based on raster to draw graphics, where raster refers to the array of pixels that make up an image.
  • Pixel: A pixel corresponds to a point on the image, typically storing color and other information for a specific location on the image.
  • Frame Buffer: During the drawing process, pixel information is stored in the frame buffer, which is a block of memory.
  • CPU (Central Processing Unit): Responsible for logical computation.
  • GPU (Graphics Processing Unit): Responsible for graphics computation.

image.png

  • As shown in the image, the rendering of modern images follows the process in the figure.
  1. Contour extraction/meshing
  2. Rasterization
  3. Frame buffer
  4. Rendering

The Pipeline#

image.png

GPU#

  • The GPU consists of a large number of small computing units.
  • Each computing unit is responsible for processing very simple calculations.
  • Each computing unit is independent of one another.
  • Therefore, all calculations can be processed in parallel.

Relationship between WebGL & OpenGL#

OpenGL, OpenGL ES, WebGL, GLSL, GLSL ES API Tables (umich.edu)

image.png

WebGL Drawing Steps#

Steps

  1. Create a WebGL context
  2. Create a WebGL Program
  3. Store data in the buffer
  4. Read buffer data to the GPU
  5. The GPU executes the WebGL program and outputs the result

image.png

As shown in the image, explanations for a few terms:

  • Raw Vertices & Primitives
  • Vertex Processor Vertex Shader
  • Sent to Fragment Shader for processing: Fragment Processor

Creating a WebGL Context#

const canvas = document.querySelector('canvas');
const gl = canvas.getContext('webgl');
// Create context, note compatibility
function create3DContext(canvas, options) {
    const names = ['webgl', 'experimental-webgL','webkit-3d','moz-webgl'];  // Feature detection
    if(options.webgl2) names.unshift(webgl2);
    let context = null;
    for(let ii = 0; ii < names.length; ++ii) {
        try {
            context = canvas.getContext(names[ii], options);
        } catch(e) {
            // no-empty
        }
        if(context) {
            break;
        }
    }
    return context;
}

Creating a WebGL Program (The Shaders)#

  1. Vertex Shader

    Process the position of each vertex in parallel using the typed array position.

    attribute vec2 position; // vec2 two-dimensional vector
    void main() {
        gl_PointSize = 1.0;
        gl_Position = vec4(position, 1.0, 1.0);
    }
    
  2. Fragment Shader

    Color all pixels within the area surrounded by the vertex contour.

    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Corresponds to rgba(255, 0, 0, 1.0), red
    }
    

The specific steps are as follows:

  1. Create vertex shader and fragment shader code:

    // Vertex shader program code
    const vertexShaderCode = `
    attribute vec2 position;
    void main() {
        gl_PointSize = 1.0;
        gl_Position = vec4(position, 1.0, 1.0);
    }
    `;
    // Fragment shader program code
    const fragmentShaderCode = `
    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
    `;
    
  2. Use createShader() to create a shader object.

  3. Use shaderSource() to set the shader program code.

  4. Use compileShader() to compile a shader.

    // Vertex shader
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertex);
    gl.compileShader(vertexShader);
    // Fragment shader
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragment);
    gl.compileShader(fragmentShader);
    
  5. Use createProgram() to create a WebGLProgram object.

  6. Use attachShader() to add a fragment or vertex shader to the WebGLProgram.

  7. Use linkProgram() to link the given WebGLProgram, completing the process of preparing GPU code for the program's fragment and vertex shaders.

  8. Use useProgram() to add the defined WebGLProgram object to the current rendering state.

    // Create shader program and link
    const program = gl.createProgram();
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
    gl.linkProgram(program);
    
    gl.useProgram(program);
    

Storing Data in the Buffer (Data to Frame Buffer)#

  • Coordinate System: The WebGL coordinate system is normalized, while the browser and canvas2D coordinate system has the top-left corner as the origin, with the y-axis down and the x-axis right, with coordinate values relative to the origin. The WebGL coordinate system has the center of the drawing canvas as the origin, following a normal Cartesian coordinate system.

Represent the vertices using a vertex array, and use createBuffer() to create and initialize a WebGLBuffer object for storing vertex data or shader data, returning bufferId. Then use bindBuffer() to bind the given bufferId to the target and return it, and finally use bufferData() to bind the data to the buffer.

// Vertex data
const points = new Float32Array([
    -1, -1,
    0, 1,
    1, -1,
]);
// Create buffer
const bufferId = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

Reading Buffer Data to GPU (Frame Buffer to GPU)#

const vPosition = gl.getAttribLocation(program, 'position'); // Get the address of the position variable in the vertex shader
gl.vertexAttribPointer(vPosition, 2, gl.FLOAT, false, 0, 0); // Set the length and type for the variable
gl.enableVertexAttribArray(vPosition); // Activate this variable

Output Result (Output)#

Output

drawArrays() draws primitives from the vector array.

// output
gl.clear(gl.COLOR_BUFFER_BIT);  // Clear the buffer data
gl.drawArrays(gl.TRIANGLES, 0, points.length / 2);

image.png

Is WebGL Too Complex? Other Methods#

Canvas 2D#

Look at how simple the canvas2D is for drawing the same triangle:

// Canvas is simple and straightforward, everything is encapsulated
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(250, 0);
ctx.lineTo(500, 500);
ctx.lineTo(0, 500);
ctx.fillStyle = 'red';
ctx.fill();

Mesh.js#

mesh-js/mesh.js: A graphics system born for visualization 😘. (github.com)

const {Renderer, Figure2D, Mesh2D} = meshjs;
const canvas = document.querySelector('canvas');
const renderer = new Renderer(canvas);

const figure = new Figure2D();
figure.beginPath();
figure.moveTo(250, 0);
figure.lineTo(500, 500);
figure.lineTo(0, 500);
const mesh = new Mesh2D(figure, canvas);
mesh.setFill({
    color: [1, 0, 0, 1],
});
renderer.drawMeshes([mesh]);

Earcut#

Use Earcut for triangulation.

const vertices = [
    [-0.7, 0.5],
    [-0.4, 0.3],
    [-0.25, 0.71],
    [-0.1, 0.56],
    [-0.1, 0.13],
    [0.4, 0.21],
    [0, -0.6],
    [-0.3, -0.3],
    [-0.6, -0.3],
    [-0.45, 0.0],
];
const points = vertices.flat();
const triangles = earcut(points);

3D Meshing#

Exported by designers for us, then extracted.

SpriteJS/next - The next generation of spritejs.

Graphic Transformations (Transforms)#

This is related to knowledge of digital image processing (everything learned is coming back.jpg).

Translation#

image.png

Rotation#

image.png

Scaling#

image.png

Linear Transformation (Rotation + Scaling)#

image.png

image.png

From linear transformations to homogeneous matrices.

image.png

Another example from the teacher: Apply Transforms

3D Matrix#

The four homogeneous matrices (mat4) of the standard 3D model:

  1. Projection Matrix (Orthographic and Perspective Projection)
  2. Model Matrix (Transforming vertices)
  3. View Matrix (3D perspective, imagine it as a camera under the camera's viewport)
  4. Normal Matrix (Normal vectors perpendicular to the surface of the object, usually used for calculating lighting on the object)

Read more#

  1. The Book of Shaders (Introduces fragment shaders, very fun)
  2. Mesh.js (Low-level library, heh)
  3. Glsl Doodle (A lightweight library for fragment shaders, with many small demos)
  4. SpriteJS (An open-source library written by Teacher Yueying, orz)
  5. Three.js (Many interesting games projects)
  6. Shadertoy BETA (Many interesting projects)

Summary and Thoughts#

In this lesson, the teacher explained WebGL drawing and its related libraries in great detail, showcasing many interesting WebGL mini-projects~

Most of the content cited in this article comes from Teacher Yueying's class and MDN! Teacher Yueying, you are awesome!

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.