WebGL Month. Day 1

lesnitsky

Andrei Lesnitsky

Posted on July 1, 2019

WebGL Month. Day 1

WebGL month

Hi šŸ‘‹ My name is Andrei. I have some fun experience with WebGL and I want to share it. I'm starting a month of WebGL, each day I will post a WebGL related tutorial. Not Three.js, not pixi.js, WebGL API itself.

Follow me on twitter to get WebGL month updates or join WebGL month mailing list

Day 1. Intro

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo


Welcome to day 1 of WebGL month. In this article we'll get into high level concepts of rendering which are improtant to understand before approaching actual WebGL API.

WebGL API is often treated as 3D rendering API, which is a wrong assumption. So what WebGL does?
To answer this question let's try to render smth with canvas 2d.

We'll need simple html

šŸ“„ index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>WebGL Month</title>
  </head>
  <body></body>
</html>

Enter fullscreen mode Exit fullscreen mode

and canvas

šŸ“„ index.html

      <meta http-equiv="X-UA-Compatible" content="ie=edge" />
      <title>WebGL Month</title>
    </head>
-   <body></body>
+   <body>
+     <canvas></canvas>
+   </body>
  </html>

Enter fullscreen mode Exit fullscreen mode

Don't forget beloved JS

šŸ“„ index.html

    </head>
    <body>
      <canvas></canvas>
+     <script src="./src/canvas2d.js"></script>
    </body>
  </html>

Enter fullscreen mode Exit fullscreen mode

šŸ“„ src/canvas2d.js

console.log('Hello WebGL month');
Enter fullscreen mode Exit fullscreen mode

Let's grab a reference to canvas and get 2d context

šŸ“„ src/canvas2d.js

- console.log('Hello WebGL month');+ console.log('Hello WebGL month');
+ 
+ const canvas = document.querySelector('canvas');
+ const ctx = canvas.getContext('2d');

Enter fullscreen mode Exit fullscreen mode

and do smth pretty simple, like drawing a black rectangle

šŸ“„ src/canvas2d.js


  const canvas = document.querySelector('canvas');
  const ctx = canvas.getContext('2d');
+ 
+ ctx.fillRect(0, 0, 100, 50);

Enter fullscreen mode Exit fullscreen mode

Ok, this is pretty simple right?
But let's think about what this signle line of code actually did.
It filled every pixel inside of rectangle with black color.

Are there any ways to do the same but w/o fillRect?
The answer is yes

Let's implement our own version of

šŸ“„ src/canvas2d.js

  const canvas = document.querySelector('canvas');
  const ctx = canvas.getContext('2d');

- ctx.fillRect(0, 0, 100, 50);
+ function fillRect(top, left, width, height) {
+ 
+ }

Enter fullscreen mode Exit fullscreen mode

So basically each pixel is just a color encoded in 4 integers. R, G, B channel and Alpha.
To store info about each pixel of canvas we'll need a Uint8ClampedArray.
The size of this array is canvas.width * canvas.height (pixels count) * 4 (each pixel has 4 channels).

šŸ“„ src/canvas2d.js

  const ctx = canvas.getContext('2d');

  function fillRect(top, left, width, height) {
- 
+     const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
  }

Enter fullscreen mode Exit fullscreen mode

Now we can fill each pixel storage with colors. Note that alpha component is also in range unlike CSS

šŸ“„ src/canvas2d.js


  function fillRect(top, left, width, height) {
      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+ 
+     for (let i = 0; i < pixelStore.length; i += 4) {
+         pixelStore[i] = 0; // r
+         pixelStore[i + 1] = 0; // g
+         pixelStore[i + 2] = 0; // b
+         pixelStore[i + 3] = 255; // alpha
+     }
  }

Enter fullscreen mode Exit fullscreen mode

But how do we render this pixels? There is a special canvas renderable class

šŸ“„ src/canvas2d.js

          pixelStore[i + 2] = 0; // b
          pixelStore[i + 3] = 255; // alpha
      }
+ 
+     const imageData = new ImageData(pixelStore, canvas.width, canvas.height);
+     ctx.putImageData(imageData, 0, 0);
  }
+ 
+ fillRect();

Enter fullscreen mode Exit fullscreen mode

Whoa šŸŽ‰ We filled canvas with a color manually iterating over each pixel! But we're not taking into account passed arguments, let's fix it.

Calculate pixel indices inside rectangle

šŸ“„ src/canvas2d.js

  const canvas = document.querySelector('canvas');
  const ctx = canvas.getContext('2d');

+ function calculatePixelIndices(top, left, width, height) {
+     const pixelIndices = [];
+ 
+     for (let x = left; x < left + width; x++) {
+         for (let y = top; y < top + height; y++) {
+             const i =
+                 y * canvas.width * 4 + // pixels to skip from top
+                 x * 4; // pixels to skip from left
+ 
+             pixelIndices.push(i);
+         }
+     }
+ 
+     return pixelIndices;
+ }
+ 
  function fillRect(top, left, width, height) {
      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);


Enter fullscreen mode Exit fullscreen mode

and iterate over these pixels instead of the whole canvas

šŸ“„ src/canvas2d.js


  function fillRect(top, left, width, height) {
      const pixelStore = new Uint8ClampedArray(canvas.width * canvas.height * 4);
+     
+     const pixelIndices = calculatePixelIndices(top, left, width, height);

-     for (let i = 0; i < pixelStore.length; i += 4) {
+     pixelIndices.forEach((i) => {
          pixelStore[i] = 0; // r
          pixelStore[i + 1] = 0; // g
          pixelStore[i + 2] = 0; // b
          pixelStore[i + 3] = 255; // alpha
-     }
+     });

      const imageData = new ImageData(pixelStore, canvas.width, canvas.height);
      ctx.putImageData(imageData, 0, 0);
  }

- fillRect();
+ fillRect(10, 10, 100, 50);

Enter fullscreen mode Exit fullscreen mode

Cool šŸ˜Ž We've just reimplemented fillRect! But what does it have in common with WebGL?

Everything

That's exactly what WebGL API does ā€“ it calculates color of each pixel and fills it with calculated color

What's next?

In next article we'll start working with WebGL API and render a WebGL "Hello world". See you tomorrow

GitHub stars
Twitter Follow

Join mailing list to get new posts right to your inbox

Source code available here

Built with

Git Tutor Logo


Homework

Extend custom fillRect to support custom colors

šŸ’– šŸ’Ŗ šŸ™… šŸš©
lesnitsky
Andrei Lesnitsky

Posted on July 1, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

WebGL Month. Day 1
webgl WebGL Month. Day 1

July 1, 2019