You are one step closer to understanding WebGL:

This time, I’ll take you into the world of WebGL by drawing a dot! And without involving WebGL libraries like D3 / ThreeJS, just draw a point using the native WebGL API!

๐Ÿ“’ preface

First of all, WebGL is not a language. It is a standard. It is a set of graphics standards based on OpenGL ES for browsers. OpenGL ES is a special version of OpenGL (doll warning ๐Ÿ‘€), ES version is widely used in mobile phones, home games and other devices. Those who want to learn more about WebGL standards can visit the Khronos Group website.

WebGL development is not that different from our normal front-end development. A web page in a browser is usually composed of: HTML, JavaScript, rendering engine, etc. If we want to develop WebGL, what else do we need? Let us think, when we learn geometry in high school, the teacher has talked about “the point moves into a line, the line moves into a surface, the surface moves into a body”, then we take the most basic point as an example, what attribute does the point have above all? The position of the point, the color of the point, the size of the point, how do we define these properties of a point? This will introduce GLSL ES(OpenGL Shader Language ES) (later called Shader), the writing method of Shader is somewhat similar to C Language syntax, from the name can also see that WebGL and OpenGL ES is “blood”! Second, we need apis from browser vendors based on the WebGL standard.

WebGL does not have the same tedious environment configuration process as OpenGL, nor does it have the system requirements, as long as there is a browser that supports WebGL!

Instead of creating a separate shader file, we will write the shader as a string

๐ŸŒ give the dot a little freedom

To use the WebGL interface provided by the browser, we need to use

to get the WebGL context:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Point</title>
</head>
<body onload="main()" style="padding: 0; margin: 0;">
  <canvas id="webgl" width="600" height="400">The browser you are using does not support WebGL!</canvas>
  <script>
  	function main() {
      // get canvas element
  		const canvas = document.getElementById("webgl");
      const gl = canvas.getContext('webgl');
    }
  </script>
</body>
</html>
Copy the code

Gl is the WebGL rendering context we got ๐Ÿคฉ Let’s fill the canvas with a background color:


      
<html lang="en">
	<! -... -->
  <script>
  	function main(a) {
      // ...
      const gl = canvas.getContext('webgl');
      
      gl.clearColor(0.0.0.0.0.0.1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
    }
  </script>
	<! -... -->
</html>
Copy the code

So the canvas we want to paint has the vast universe of deep black ๐Ÿ™‚ drink to celebrate ๐Ÿบ

To explain, the gl.clearColor method sets the background color of the clear canvas, in the form of RGBA; ๐Ÿง This method inherits from OpenGL. OpenGL is based on the multi-buffer model. Clearing the drawing area is actually clearing the color buffer. Passing the parameter gl.COLOR_BUFFer_bit tells WebGL to clear the color buffer; In addition, there are depth buffers and template buffers, see this.

๐Ÿต Get a “point” closer

We will write a shader to add a bit of color to the deep canvas ๐ŸŒˆ


      
<html lang="en">
	<! -... -->
  <script>
  	function main(a) {
      // ...
      const VertexShader = `
        void main() {
          gl_Position = vec4(0.0.0.0.0.0.1.0);
          gl_PointSize = 10.0;
        }
      `;
      const FragmentShader = `
        void main() {
          gl_FragColor = vec4(0.0.0.0.1.0.1.0);
        }
      `;
    }
  </script>
	<! -... -->
</html>
Copy the code

Above we define VertexShader and FragmentShader. In WebGL there are two types of shaders: VertexShader and FragmentShader:

  • Vertex shader: A program used to describe vertex properties, such as position, size, etc. Vertex refers to a point in two-dimensional or three-dimensional space, such as the vertex or intersection of two-dimensional graphics or three-dimensional graphics;
  • Slice shader: Also known as pixel shader, performs slice by slice processing such as lighting. A slice can be understood as a pixel.

Also, each shader has a main() method that does not specify arguments and must be followed by a semicolon at the end of each line. Gl_Position, gl_PointSize, and gl_FragColor are variables built into the shader. Gl_PointSize may not be assigned a value. The default value is 1.0. Note that we assigned the value 0.0 instead of 0 because these built-in variables have their own types:

The variable name type describe
gl_Position vec4 Vertex positions
gl_PointSize float The size of the point
gl_FragColor vec4 Fragment color

Q: why pass four values when a point has only (x, y, z) coordinates?

Answer: what is used here is the form of homogeneous coordinate, understand homogeneous coordinate can check my last article “guest officer, come in to see the geometrical transformation of graph?” .

Q: What are the steps between vertex shaders and slice shaders defined above?

A: In three steps! The first step is to create shaders. Step 2, create shader program; Third, use the shader program in the WebGL context.

1๏ธ create shader

I’ve extracted a createShader() method from the shader creation step for ease of use:

function createShader (gl, type, source) {
  const shader = gl.createShader(type);
  if (shader == null) {
    console.warn('Cannot create shader');
    return null;
  }

  gl.shaderSource(shader, source);
  gl.compileShader(shader);

  const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if(! compiled) {console.log(Failed to compile shader: + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
Copy the code

Gl. shaderSource sets the source of the shader created by gl.createShader to the VertextShader or FragmentShader we defined

2๏ธ create shader program

For brevity, create shader program with createProgram() method:

function createProgram (gl, vshader, fshader) {
  const vertexShader = createShader(gl, gl.VERTEX_SHADER, vshader);
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fshader);
  if(! vertexShader || ! fragmentShader) {return null;
  }

  const program = gl.createProgram();
  if(! program) {return null;
  }

  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  gl.linkProgram(program);

  const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if(! linked) {console.warn(Link shader program failed: + gl.getProgramInfoLog(program));
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}
Copy the code

AttachShader is to attach the created shader to our shader program and then call gl.linkProgram method to integrate the program.

3๏ธ using a shader program in context

function initShaders(gl, vshader, fshader) {
  const program = createProgram(gl, vshader, fshader);
  if(! program) {console.warn('Failed to create shader program! ');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}
Copy the code

Here is very simple, will not do too much introduction! Then call this method in main() to initialize the shader:


      
<html lang="en">
	<! -... -->
  <script>
  	function main() {
      const canvas = document.getElementById('webgl');
      const gl = canvas.getContext('webgl');

      if(! initShaders(gl, VertexShader, FragmentShader)) {return alert('Failed to initialize shader');
      }

      gl.clearColor(0.0.0.0.0.0.1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.POINTS, 0.1);
    }
    // createShader
    // createProgram
    // initShaders
  </script>
	<! -... -->
</html>
Copy the code

The first argument to gl.drawArrays specifies how to draw, the second argument specifies which vertex to start drawing from, and the third argument specifies how many vertices to draw from. So we can see a blue dot right in the center of the black canvas:

But do you have a lot of question marks?

The position of the point is (0, 0, 0). Why does the point appear in the center of the canvas? The position of WebGL relative to < Canvas > is shown below:

In the middle are WebGL coordinates relative to canvas> and canvas coordinates relative to the screen! The coordinates of WebGL relative to canvas are not absolute pixel values, but relative [-1.0, 1.0]. ๐ŸŒฐ for example: If we set the coordinate of the point to (1.0, 0.0, 0.0, 1.0), then the point will appear on the far right side of the canvas. Similarly, set it to (-1.0, 1.0, 0.0, 1.0), and the point will be displayed in the upper left corner of the canvas ๐Ÿ˜›

๐Ÿ•น what happens to render this point?

๐Ÿคจ is this the end?

There’s no way this is gonna end! Let’s update the program for drawing points. Now our position and size are defined in the shader. Of course WebGL also provides a way for us to pass in values from the outside. Let’s modify the shader:

const VertexShader = ` attribute vec4 a_Position; void main() { gl_Position = a_Position; Gl_PointSize = 10.0; } `;
Copy the code

Attribute is a GLSL SE variable that is used to pass data from the outside to the vertex shader. Only the vertex shader can use it. There is also a uniform variable type, which transfers data that is identical (or independent of) all vertices. Above is the shader code where we assign the external a_Position and a_PointSize values to gl_Position and gl_PointSize, respectively. How do I use JavaScript to pass values to the attribute variable in the shader?

function main () {
  // ...
  if(! initShaders(gl, VertexShader, FragmentShader)) {return alert('Failed to initialize shader');
  }

  const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttrib3f(a_Position, 1.0.0.0.0.0);
  // ...
}
Copy the code

The vertextAttrib3f method assigns a value to the attribute obtained using getAttribLocation. The vertexAttrib3f method defaults the last value of the homogeneous coordinate to 1.0. VertexAttrib4f is also available ๐Ÿ™‚

๐Ÿคฉ add some more features

When I click on the canvas, I will show a point where I click on the canvas, which requires us to bind the method to the canvas:

canvas.onmousedown = function (e) {
  click(e, gl, canvas, a_Position);
};
Copy the code

I will not give the detailed code here. The way of canvas binding event is as above, and I will briefly explain the idea: Get the coordinate value of mouse click on canvas after clicking; Then convert the coordinates to WebGL coordinates in the form of [-1.0, 1.0] relative to canvas; Then empty the canvas at the redraw point. The code for coordinate conversion is as follows:

let x = e.clientX;
let y = e.clientY;
const rect = e.target.getBoundingClientRect();

x = (x - rect.left - canvas.width / 2) / (canvas.width / 2);
y = (canvas.height / 2 - y + rect.top) / (canvas.height / 2);
Copy the code

For example, you can also give each point a different color.

๐Ÿค  conclusion

So much for drawing a “simple point” using native WebGL ๐Ÿ™‚ I’m still learning, and there will be more WebGL articles in the future ๐Ÿ˜‹

You are welcome to follow our public account: Refactor, and we will share more interesting and useful articles with you in the future. Thanks for reading