JavaScript and WebGL

JavaScript and WebGL

This article explains JavaScript and WebGL.

YouTube Video

javascript-web-gl-texture.js
  1// Get WebGL context
  2const canvas = document.getElementById("glCanvas");
  3const gl = canvas.getContext("webgl");
  4if (!gl) {
  5  alert("WebGL is not supported by your browser.");
  6}
  7
  8// Vertex shader program
  9const vsSource = `
 10  attribute vec4 aVertexPosition;
 11  attribute vec2 aTextureCoord;
 12  varying highp vec2 vTextureCoord;
 13  void main(void) {
 14    gl_Position = aVertexPosition;
 15    vTextureCoord = aTextureCoord;
 16  }
 17`;
 18
 19// Fragment shader program
 20const fsSource = `
 21  varying highp vec2 vTextureCoord;
 22  uniform sampler2D uSampler;
 23  void main(void) {
 24    gl_FragColor = texture2D(uSampler, vTextureCoord);
 25  }
 26`;
 27
 28// Compile shader
 29function loadShader(type, source) {
 30  const shader = gl.createShader(type);
 31  gl.shaderSource(shader, source);
 32  gl.compileShader(shader);
 33  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 34    console.error("Shader compile failed: ", gl.getShaderInfoLog(shader));
 35    gl.deleteShader(shader);
 36    return null;
 37  }
 38  return shader;
 39}
 40
 41// Initialize shader program
 42function initShaderProgram(vsSource, fsSource) {
 43  const vertexShader = loadShader(gl.VERTEX_SHADER, vsSource);
 44  const fragmentShader = loadShader(gl.FRAGMENT_SHADER, fsSource);
 45  const shaderProgram = gl.createProgram();
 46  gl.attachShader(shaderProgram, vertexShader);
 47  gl.attachShader(shaderProgram, fragmentShader);
 48  gl.linkProgram(shaderProgram);
 49  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
 50    console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(shaderProgram));
 51    return null;
 52  }
 53  return shaderProgram;
 54}
 55
 56const shaderProgram = initShaderProgram(vsSource, fsSource);
 57const programInfo = {
 58  program: shaderProgram,
 59  attribLocations: {
 60    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
 61    textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
 62  },
 63  uniformLocations: {
 64    uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
 65  },
 66};
 67
 68// Define positions and texture coordinates
 69const positions = new Float32Array([
 70  -1.0,  1.0,
 71   1.0,  1.0,
 72  -1.0, -1.0,
 73   1.0, -1.0,
 74]);
 75
 76const textureCoordinates = new Float32Array([
 77  0.0, 0.0,
 78  1.0, 0.0,
 79  0.0, 1.0,
 80  1.0, 1.0,
 81]);
 82
 83// Create position buffer
 84const positionBuffer = gl.createBuffer();
 85gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 86gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
 87
 88// Create texture coordinate buffer
 89const texCoordBuffer = gl.createBuffer();
 90gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
 91gl.bufferData(gl.ARRAY_BUFFER, textureCoordinates, gl.STATIC_DRAW);
 92
 93// Create and load texture
 94const texture = gl.createTexture();
 95gl.bindTexture(gl.TEXTURE_2D, texture);
 96
 97const image = new Image();
 98image.src = "texture.png";
 99image.onload = () => {
100  gl.bindTexture(gl.TEXTURE_2D, texture);
101  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
102  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
103  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
104  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
105  drawScene();
106};
107
108// Draw function
109function drawScene() {
110  gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black
111  gl.clear(gl.COLOR_BUFFER_BIT);
112
113  gl.useProgram(programInfo.program);
114
115  // Bind vertex position buffer
116  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
117  gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
118  gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
119
120  // Bind texture coordinate buffer
121  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
122  gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, 2, gl.FLOAT, false, 0, 0);
123  gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
124
125  // Bind texture
126  gl.activeTexture(gl.TEXTURE0);
127  gl.bindTexture(gl.TEXTURE_2D, texture);
128  gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
129
130  // Draw rectangle
131  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
132}
javascript-web-gl-lighting.js
  1// Get the WebGL context
  2const canvas = document.getElementById("glCanvas");
  3const gl = canvas.getContext("webgl");
  4if (!gl) {
  5  alert("WebGL not supported");
  6  throw new Error("WebGL not supported");
  7}
  8
  9// Vertex shader
 10const vertexShaderSource = `
 11attribute vec3 aPosition;
 12attribute vec3 aNormal;
 13uniform mat4 uModelViewMatrix;
 14uniform mat4 uProjectionMatrix;
 15varying vec3 vNormal;
 16
 17void main(void) {
 18    gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
 19    vNormal = aNormal;
 20}
 21`;
 22
 23// Fragment shader (with lighting)
 24const fragmentShaderSourceWithLighting = `
 25precision mediump float;
 26uniform vec3 uLightingDirection;
 27uniform vec4 uLightColor;
 28varying vec3 vNormal;
 29
 30void main(void) {
 31    vec3 lightDirection = normalize(uLightingDirection);
 32    float directionalLightWeighting = max(dot(normalize(vNormal), lightDirection), 0.0);
 33    gl_FragColor = uLightColor * directionalLightWeighting;
 34}
 35`;
 36
 37// Shader creation helper
 38function createShader(gl, type, source) {
 39  const shader = gl.createShader(type);
 40  gl.shaderSource(shader, source);
 41  gl.compileShader(shader);
 42  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 43    console.error("Shader compile error:", gl.getShaderInfoLog(shader));
 44    gl.deleteShader(shader);
 45    return null;
 46  }
 47  return shader;
 48}
 49
 50// Create program
 51function createProgram(gl, vs, fs) {
 52  const program = gl.createProgram();
 53  gl.attachShader(program, vs);
 54  gl.attachShader(program, fs);
 55  gl.linkProgram(program);
 56  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
 57    console.error("Program link error:", gl.getProgramInfoLog(program));
 58    return null;
 59  }
 60  return program;
 61}
 62
 63const vs = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
 64const fs = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceWithLighting);
 65const program = createProgram(gl, vs, fs);
 66gl.useProgram(program);
 67
 68// Cube data
 69const vertices = new Float32Array([
 70  -1,-1,1,  1,-1,1,  1,1,1,  -1,1,1,     // Front
 71  -1,-1,-1, -1,1,-1, 1,1,-1, 1,-1,-1,    // Back
 72  -1,1,-1,  -1,1,1,  1,1,1,  1,1,-1,     // Top
 73  -1,-1,-1, 1,-1,-1, 1,-1,1, -1,-1,1,    // Bottom
 74  1,-1,-1, 1,1,-1, 1,1,1, 1,-1,1,        // Right
 75  -1,-1,-1, -1,-1,1, -1,1,1, -1,1,-1     // Left
 76]);
 77
 78const normals = new Float32Array([
 79  0,0,1, 0,0,1, 0,0,1, 0,0,1,
 80  0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1,
 81  0,1,0, 0,1,0, 0,1,0, 0,1,0,
 82  0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0,
 83  1,0,0, 1,0,0, 1,0,0, 1,0,0,
 84  -1,0,0, -1,0,0, -1,0,0, -1,0,0
 85]);
 86
 87const indices = new Uint16Array([
 88  0,1,2, 0,2,3,
 89  4,5,6, 4,6,7,
 90  8,9,10, 8,10,11,
 91  12,13,14, 12,14,15,
 92  16,17,18, 16,18,19,
 93  20,21,22, 20,22,23
 94]);
 95
 96// Buffers
 97const vertexBuffer = gl.createBuffer();
 98gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
 99gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
100
101const normalBuffer = gl.createBuffer();
102gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
103gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
104
105const indexBuffer = gl.createBuffer();
106gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
107gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
108
109// Attribute locations
110const aPosition = gl.getAttribLocation(program, "aPosition");
111const aNormal = gl.getAttribLocation(program, "aNormal");
112
113// Uniforms
114const uProjectionMatrix = gl.getUniformLocation(program, "uProjectionMatrix");
115const uModelViewMatrix = gl.getUniformLocation(program, "uModelViewMatrix");
116const uLightingDirection = gl.getUniformLocation(program, "uLightingDirection");
117const uLightColor = gl.getUniformLocation(program, "uLightColor");
118
119// Projection
120function perspectiveMatrix(fov, aspect, near, far) {
121  const f = 1.0 / Math.tan((fov / 2) * Math.PI / 180);
122  return new Float32Array([
123    f/aspect,0,0,0,
124    0,f,0,0,
125    0,0,(far+near)/(near-far),-1,
126    0,0,(2*far*near)/(near-far),0
127  ]);
128}
129
130function identityMatrix() {
131  return new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);
132}
133
134gl.uniformMatrix4fv(uProjectionMatrix, false, perspectiveMatrix(45, canvas.width / canvas.height, 0.1, 100.0));
135gl.uniform3fv(uLightingDirection, [0.5, 0.7, 1.0]);
136gl.uniform4fv(uLightColor, [1.0, 1.0, 1.0, 1.0]);
137
138// Clear settings
139gl.clearColor(0.1, 0.1, 0.1, 1.0);
140gl.enable(gl.DEPTH_TEST);
141
142// Draw loop
143let angle = 0;
144function draw() {
145  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
146
147  // Bind position buffer
148  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
149  gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
150  gl.enableVertexAttribArray(aPosition);
151
152  // Bind normal buffer
153  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
154  gl.vertexAttribPointer(aNormal, 3, gl.FLOAT, false, 0, 0);
155  gl.enableVertexAttribArray(aNormal);
156
157  // Compute rotation
158  const mv = identityMatrix();
159  mv[0] = Math.cos(angle);
160  mv[2] = Math.sin(angle);
161  mv[8] = -Math.sin(angle);
162  mv[10] = Math.cos(angle);
163  mv[14] = -6.0;
164  gl.uniformMatrix4fv(uModelViewMatrix, false, mv);
165
166  // Draw
167  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
168  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
169
170  angle += 0.01;
171  requestAnimationFrame(draw);
172}
173
174draw();
javascript-web-gl.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: inline;
 61        margin: 0.25em;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <button id="executeTextureBtn">Execute Texture</button>
108        <button id="executeLightingBtn">Execute Lighting</button>
109        <div id="output"></div>
110    </div>
111    <div class="container">
112        <h1>WebGL Canvas</h1>
113        <canvas id="glCanvas"></canvas>
114    </div>
115
116    <script>
117        // Override console.log to display messages in the #output element
118        (function () {
119            // Override console.log
120            const originalLog = console.log;
121            console.log = function (...args) {
122                originalLog.apply(console, args);
123                const message = document.createElement('div');
124                message.textContent = args.map(String).join(' ');
125                output.appendChild(message);
126            };
127
128            // Override console.error
129            const originalError = console.error;
130            console.error = function (...args) {
131                originalError.apply(console, args);
132                const message = document.createElement('div');
133                message.textContent = args.map(String).join(' ');
134                message.style.color = 'red'; // Color error messages red
135                output.appendChild(message);
136            };
137        })();
138
139        document.getElementById('executeBtn').addEventListener('click', () => {
140            // Prevent multiple loads
141            if (document.getElementById('externalScript')) return;
142
143            const script = document.createElement('script');
144            script.src = 'javascript-web-gl.js';
145            script.id = 'externalScript';
146            document.body.appendChild(script);
147        });
148
149        document.getElementById('executeTextureBtn').addEventListener('click', () => {
150            // Prevent multiple loads
151            if (document.getElementById('externalScript')) return;
152
153            const script = document.createElement('script');
154            script.src = 'javascript-web-gl-texture.js';
155            script.id = 'externalScript';
156            document.body.appendChild(script);
157        });
158
159        document.getElementById('executeLightingBtn').addEventListener('click', () => {
160            // Prevent multiple loads
161            if (document.getElementById('externalScript')) return;
162
163            const script = document.createElement('script');
164            script.src = 'javascript-web-gl-lighting.js';
165            script.id = 'externalScript';
166            document.body.appendChild(script);
167        });
168    </script>
169</body>
170</html>

JavaScript and WebGL

JavaScript and WebGL are powerful tools in modern web development for delivering interactive, high-quality 3D graphics in the browser. By using WebGL, you can directly leverage the GPU for efficient rendering and build expressive web applications such as games, data visualizations, and VR/AR applications.

What is WebGL?

WebGL (Web Graphics Library) is a low-level graphics API provided as part of HTML5, used to perform 3D rendering directly in the browser. It is based on the OpenGL ES 2.0 specification and controls the GPU from JavaScript. By using WebGL, you can display advanced 3D graphics in nearly all modern browsers without the need for special plugins.

Fundamentals of WebGL

To perform rendering with WebGL, you need to understand several key concepts. Like other graphics APIs (such as OpenGL and DirectX), WebGL processes data through a graphics pipeline and generates images on the GPU.

Getting the context

WebGL renders using the HTML5 <canvas> element. In JavaScript, you first need to obtain the WebGL context.

1const canvas = document.getElementById("glCanvas");
2const gl = canvas.getContext("webgl");
3if (!gl) {
4    console.error("WebGL not supported, falling back on experimental-webgl");
5    gl = canvas.getContext("experimental-webgl");
6}
7if (!gl) {
8    alert("Your browser does not support WebGL");
9}
  • Obtain a WebGL context from the <canvas> element. This allows you to control GPU rendering through JavaScript.

Defining shaders

To render with WebGL, you use two types of shaders: a vertex shader and a fragment shader. Vertex shaders calculate the position of each vertex of an object, and fragment shaders calculate the color of each pixel. These shaders are programs that run on the GPU. Shaders are written in a language called GLSL.

1const vertexShaderSource = `
2attribute vec4 aVertexPosition;
3void main(void) {
4    gl_Position = aVertexPosition;
5}`;
6const fragmentShaderSource = `
7void main(void) {
8    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Red color
9}`;
  • The vertex shader processes vertex positions, and the fragment shader sets the per-pixel color. Here we output red.

Compiling and linking shaders

To incorporate shader source code into a WebGL program, compile the shaders and link them to create a program.

 1function createShader(gl, type, source) {
 2    const shader = gl.createShader(type);
 3    gl.shaderSource(shader, source);
 4    gl.compileShader(shader);
 5    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
 6        console.error("An error occurred compiling the shaders:", gl.getShaderInfoLog(shader));
 7        gl.deleteShader(shader);
 8        return null;
 9    }
10    return shader;
11}
12
13const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
14const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
15
16const shaderProgram = gl.createProgram();
17gl.attachShader(shaderProgram, vertexShader);
18gl.attachShader(shaderProgram, fragmentShader);
19gl.linkProgram(shaderProgram);
20if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
21    console.error("Unable to initialize the shader program:", gl.getProgramInfoLog(shaderProgram));
22}
  • Compile each shader and link them into a program. This builds a rendering pipeline executable on the GPU.

Preparing buffers and vertex data

Next, prepare the geometry data to be drawn and send it to a WebGL buffer. This data includes vertex positions, colors, texture coordinates, and so on.

 1const vertices = new Float32Array([
 2    -1.0, -1.0,
 3     1.0, -1.0,
 4     1.0,  1.0,
 5    -1.0,  1.0,
 6]);
 7
 8const vertexBuffer = gl.createBuffer();
 9gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
10gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  • Define the vertex coordinates of a rectangle and upload them to a GPU buffer. STATIC_DRAW indicates that the data does not change frequently.

Rendering process

Finally, have WebGL perform the actual rendering using the shaders and data. Configure the WebGL rendering pipeline correctly and issue draw commands to the GPU. Here, we clear the background to black and draw a red rectangle using the defined vertex data and shader program.

 1gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black
 2gl.clear(gl.COLOR_BUFFER_BIT);
 3
 4// Use the shader program
 5gl.useProgram(shaderProgram);
 6
 7// Bind vertex buffer
 8const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "aVertexPosition");
 9gl.enableVertexAttribArray(positionAttributeLocation);
10gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
11gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
12
13// Draw
14gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
  • In this code, we set up the WebGL rendering pipeline and render a rectangle. By initializing the screen with gl.clear() and sending draw commands to the GPU with gl.drawArrays(), the data processed by the shaders is actually displayed on the screen.

Advanced Features of WebGL

With WebGL, you can utilize various advanced features beyond basic rendering. Here, we introduce some of those features.

Texture Mapping

WebGL supports texture mapping, which allows you to apply images or patterns to 3D objects. This allows you to give objects a more complex and realistic appearance instead of simple colors.

 1/* Fragment of source code */
 2const texture = gl.createTexture();
 3gl.bindTexture(gl.TEXTURE_2D, texture);
 4
 5// Load an image as the texture
 6const image = new Image();
 7image.src = "texture.png";
 8image.onload = () => {
 9    gl.bindTexture(gl.TEXTURE_2D, texture);
10    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
11
12    // Set texture parameters
13    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
14    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
15    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
16
17    // Draw the scene again with the texture applied
18    drawScene();
19};
  • Load images as textures and apply them to object surfaces to achieve a more realistic appearance.

Lighting and Shading

You can use shading models to apply realistic lighting effects to 3D objects. Calculate how the surface of an object reflects light based on different shading models such as Phong shading and Gouraud shading.

 1/* Fragment of source code */
 2const fragmentShaderSourceWithLighting = `
 3precision mediump float;
 4uniform vec3 uLightingDirection;
 5uniform vec4 uLightColor;
 6void main(void) {
 7    vec3 lightDirection = normalize(uLightingDirection);
 8    float directionalLightWeighting = max(dot(vNormal, lightDirection), 0.0);
 9    gl_FragColor = uLightColor * directionalLightWeighting;
10}`;
  • Compute brightness based on the angle between the light direction and the normals. This is the basis of shading techniques such as the Phong and Gouraud models.

Optimization and Performance

Since WebGL operates directly on the GPU, performance optimization is crucial. Using a large amount of vertex data or complex shaders may lead to a decrease in performance. The following are common approaches for performance optimization.

  • Efficient use of vertex buffers Rather than updating vertex buffers frequently, reuse buffers once created as much as possible.
  • Adjusting the frame rate Use requestAnimationFrame to control animation rendering and maintain an efficient frame rate.
  • Occlusion culling A technique that omits rendering objects that are not within the view. This can reduce rendering load.
 1function render() {
 2    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 3
 4    // Logic to update objects in the scene
 5
 6    // Use requestAnimationFrame for smooth rendering
 7    requestAnimationFrame(render);
 8}
 9
10render();
  • Control the rendering loop with requestAnimationFrame and efficiently update frames in sync with the browser's rendering timing.

Summary

By combining JavaScript and WebGL, advanced 3D graphics that operate in real-time can be achieved in the browser. WebGL encompasses many elements from the basic concepts of shaders and buffer management, applying textures, to performance optimization, but by effectively utilizing these, you can develop interactive and visually rich applications.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video