1
WebGL tutorials for the front end of the web, the tutorials on the Internet are based on the assumption of computer graphics, which is not very friendly to web developers, so this pit is opened.

final effect

https://codepen.io/chendonming/pen/xxPvqxP

Mouse click to draw a point.

How webGL displays a point

First of all, you have to know how webGL displays a point?

To draw any object in webGL, you need a 顶点着色器 and 片元着色器 ,

Vertex Shader : A program that describes the properties of a vertex (position, color, etc.).

Fragment Shader : A program that performs fragment processing.

Maybe you will be confused, and a lot of official theories will be discouraged, so I will directly show the simplest code to show a point, I believe you will understand immediately.

 <canvas id="glcanvas" width="640" height="480">
    你的浏览器似乎不支持或者禁用了HTML5 <code>&lt;canvas&gt;</code> 元素.
</canvas>

First, I need some simple wrapper functions:

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

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

  return true;
}

/**
 * Create the linked program object
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return created program object, or null if the creation has failed
 */
function createProgram(gl, vshader, fshader) {
  // Create shader object
  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }

  // Create a program object
  var program = gl.createProgram();
  if (!program) {
    return null;
  }

  // Attach the shader objects
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // Link the program object
  gl.linkProgram(program);

  // Check the result of linking
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('Failed to link program: ' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

/**
 * Create a shader object
 * @param gl GL context
 * @param type the type of the shader object to be created
 * @param source shader program (string)
 * @return created shader object, or null if the creation has failed.
 */
function loadShader(gl, type, source) {
  // Create shader object
  var shader = gl.createShader(type);
  if (shader == null) {
    console.log('unable to create shader');
    return null;
  }

  // Set the shader program
  gl.shaderSource(shader, source);

  // Compile the shader
  gl.compileShader(shader);

  // Check the result of compilation
  var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    var error = gl.getShaderInfoLog(shader);
    console.log('Failed to compile shader: ' + error);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
This is a function that initializes the shader

Initialize webgl:

 const canvas = document.querySelector("#glcanvas");
// 初始化WebGL上下文
const gl = canvas.getContext("webgl");

// 确认WebGL支持性
if (!gl) {
    alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
    return;
}
// 使用完全不透明的黑色清除所有图像
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 用上面指定的颜色清除缓冲区
gl.clear(gl.COLOR_BUFFER_BIT);

Call the initialize shader function.

 const VSHADER_SOURCE = `
    void main() {
        gl_Position = vec4(0.0 ,0.0 ,0.0 , 1.0);
        gl_PointSize = 10.0;
    }
`;
const FSHADER_SOURCE = `
    void main() {
        gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
    }
`;
 initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);
 gl.drawArrays(gl.POINTS, 0, 1);

OK, by now, you should see a red dot in the middle of the black canvas.

Parse

The most critical part is actually VSHADER_SOURCE and FSHADER_SOURCE two strings, which represent the coordinates of the point and the color of the point respectively.

VSHADER_SOURCE and FSHADER_SOURCE belong to the glsl code,

VSHADER_SOURCE in gl_Position represents the position of the point, gl_Position is a built-in variable of glsl .

You will find that the value of gl_Position is a vec4 type, and the coordinates have 4 values? In fact, this is a homogeneous coordinate.

For vec4(x, y, z, w), the real world coordinates are (x/w, y/w, z/w), so we generally set the fourth parameter of vec4 to 1 .

Why do you need homogeneous coordinates? Because in the three-dimensional world, the vector is also represented by three coordinates, so in order to distinguish the vector from the real position, a fourth parameter is introduced, and the fourth parameter of the vector is 0.

js and GLSL communication

The above code does draw a point, but it is written in a string, which is definitely inconvenient for us to operate, so it is necessary to operate the variables in glsl .

 const VSHADER_SOURCE = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position;
        gl_PointSize = 10.0;
    }
`;

As shown above, we add an attribute to the vertex shader code, and then assign the attribute a_Position to the glsl built-in variable gl_Position. Does this mean that if I change the value of a_Position, gl_Position will also change?

js gets and modifies attribute

Required API:

 gl.getAttribLocation(gl.program, attribute);
gl.vertexAttrib3f(index, x, y, z);

The getAttribLocation method returns the subscripted location of an attribute in the given WebGLProgram object

vertexAttrib3f can assign values to vertex attibute variables

Now just modify the attribute before gl.drawArrays(gl.POINTS, 0, 1);

 var a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

So far, the complete code is as follows:

     const canvas = document.querySelector("#glcanvas");
    // 初始化WebGL上下文
    const gl = canvas.getContext("webgl");

    // 确认WebGL支持性
    if (!gl) {
        alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
        return;
    }
    // 使用完全不透明的黑色清除所有图像
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    // 用上面指定的颜色清除缓冲区
    gl.clear(gl.COLOR_BUFFER_BIT);

    const VSHADER_SOURCE = `
        attribute vec4 a_Position;
        void main() {
            gl_Position = a_Position;
            gl_PointSize = 10.0;
        }
    `;
    const FSHADER_SOURCE = `
        void main() {
             gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
         }
    `;

  //初始化着色器
  initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

  var a_Position = gl.getAttribLocation(gl.program, "a_Position");
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

  //画点
  gl.drawArrays(gl.POINTS, 0, 1);

draw multiple points

 gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);

gl.vertexAttrib3f(a_Position, 0.5, 0.5, 0.0);
gl.drawArrays(gl.POINTS, 0, 1);

You will notice that there are two red dots on the screen.

drawArrays method is used to draw primitives from the vector array, and each time it is executed, the GPU is notified to render the primitives.

Two points are fine now, what if there are thousands of points? We need to draw multiple points at once in order to maintain performance.

Typed Array TypedArray

For multiple points, we need to store the position of the point in a variable, we choose TypedArray,
It has several advantages over ordinary Array: performance performance is also performance.
For typedArray Introduction see the following code:

 // 下面代码是语法格式,不能直接运行,
// TypedArray 关键字需要替换为底部列出的构造函数。
new TypedArray(); // ES2017中新增
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

// TypedArray 指的是以下的其中之一:

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();

Then how to choose, see the following list:

type range of individual element values size (bytes) describe Web IDL type Equivalent types in C
Int8Array -128 to 127 1 8-bit binary signed integer byte int8_t
Uint8Array 0 to 255 1 8-bit unsigned integer (loop from another boundary after going out of range) octet uint8_t
Uint8ClampedArray 0 to 255 1 8-bit unsigned integer (out of range bounds) octet uint8_t
Int16Array -32768 to 32767 2 16-bit binary signed integer short int16_t
Uint16Array 0 to 65535 2 16-bit unsigned integer unsigned short uint16_t
Int32Array -2147483648 to 2147483647 4 32-bit binary signed integer long int32_t
Uint32Array 0 to 4294967295 4 32-bit unsigned integer unsigned long uint32_t
Float32Array 1.2 × 10^-38 to 3.4 × 10^38 4 32-bit IEEE floating point number (7 significant digits, eg 1.1234567 ) unrestricted float float
Float64Array 5.0 × 10^-324 to 1.8 × 10^308 8 64-bit IEEE floating point number (16 significant digits, eg 1.123...15 ) unrestricted double double
BigInt64Array -2^63 to 2^63-1 8 64-bit binary signed integer bigint int64_t (signed long long)
BigUint64Array 0 to 2^64 - 1 8 64-bit unsigned integer bigint uint64_t (unsigned long long)

For this tutorial, because we will use floating point numbers later, and because the data is not large, we choose Float32Array .

try to draw two points

  • Store the coordinates of the two points in a variable

     const verties = new Float32Array([0.0, 0.5, -0.5, -0.5]);
  • Hang the data to a certain memory location in the buffer and write the data

       const vertexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
      // verties就是我们自己创建的Float32Array数据
      gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);
  • Read data and modify attribute

      const a_Position = gl.getAttribLocation(gl.program, "a_Position");
      gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
      gl.enableVertexAttribArray(a_Position);
    To understand the vertexAttribPointer function, you can see this note of mine
    https://note.youdao.com/s/c5EG9usH
  • Modify the attribute, the next step is to call the drawing command

     // 因为是绘制两个点,第三个参数输入2
    gl.drawArrays(gl.POINTS, 0, 2);

full code

Remove the initialization webGL and tool functions initShaders , because there is no change every time you write...

   initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

  const verties = new Float32Array([0.0, 0.5, -0.5, -0.5]);

  const vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);

  const a_Position = gl.getAttribLocation(gl.program, "a_Position");

  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Position);
  gl.drawArrays(gl.POINTS, 0, 2);

Mouse monitor coordinates and write

If the above is understood, the third step is the easiest (for web developers).
The specific functions of the above code are all implemented, only need:

  • Convert screen coordinates to webGL coordinates when clicking
  • Store coordinates in Float32Array data
  • Modify attribute, render.

Convert to webGl coordinates

 const x = (e.offsetX - 320) / 320;
const y = -(e.offsetY - 240) / 240;

where 320 = 640/2
240 = 480/2

320 represents the width of the canvas element, 240 represents the height of the canvas element

Store Float32Array data

First of all, Float32Array is of fixed length and cannot be modified dynamically, so you need to create a new one Float32Array

 const newArr = new Float32Array(length + 2)
for (let i = 0; i < arrayBuffer.length; i++) {
    newArr[i] = arrayBuffer[i]
}
newArr[arrayBuffer.length] = x;
newArr[arrayBuffer.length + 1] = y;

final code

The code can be viewed in codePen. If it cannot be opened, I will show the code:

 <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>
</head>

<body onload="main()">
    <canvas id="glcanvas" width="640" height="480">
        你的浏览器似乎不支持或者禁用了HTML5 <code>&lt;canvas&gt;</code> 元素.
    </canvas>
</body>
<script src="utils/cuon-utils.js"></script>
<script>
    let arrayBuffer = new Float32Array()
    function main() {
        const canvas = document.querySelector("#glcanvas");
        // 初始化WebGL上下文
        const gl = canvas.getContext("webgl");

        // 确认WebGL支持性
        if (!gl) {
            alert("无法初始化WebGL,你的浏览器、操作系统或硬件等可能不支持WebGL。");
            return;
        }
        // 使用完全不透明的黑色清除所有图像
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        // 用上面指定的颜色清除缓冲区
        gl.clear(gl.COLOR_BUFFER_BIT);

        const VSHADER_SOURCE = `
            attribute vec4 a_Position;
            void main() {
                gl_Position = a_Position;
                gl_PointSize = 10.0;
            }
        `;
        const FSHADER_SOURCE = `
            void main() {
                 gl_FragColor = vec4(1.0 ,0.0 ,0.0 ,1.0);
             }
        `;
        initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE);

        // 监听点击事件
        document.getElementById('glcanvas').addEventListener('mousedown', e => {
            clear(gl);
            // 左上角原点坐标
            const x = (e.offsetX - 320) / 320;
            const y = -(e.offsetY - 240) / 240;
            let length = arrayBuffer.length;
            const newArr = new Float32Array(length + 2)
            for (let i = 0; i < arrayBuffer.length; i++) {
                newArr[i] = arrayBuffer[i]
            }
            newArr[arrayBuffer.length] = x;
            newArr[arrayBuffer.length + 1] = y;
            const len = initVertexBuffer(gl, newArr);
            gl.drawArrays(gl.POINTS, 0, len);
            arrayBuffer = newArr;
        })
    }

    function initVertexBuffer(gl, verties) {
        const vertexBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, verties, gl.STATIC_DRAW);

        const a_Position = gl.getAttribLocation(gl.program, "a_Position");
        const FSIZE = verties.BYTES_PER_ELEMENT
        gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 2 * FSIZE, 0);
        gl.enableVertexAttribArray(a_Position);
        return verties.length / 2;
    }

    function clear(gl) {
        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
    }
</script>

</html>

Among them cuon-utils.js is a small tool function encapsulated

 // cuon-utils.js (c) 2012 kanda and matsuda
/**
 * Create a program object and make current
 * @param gl GL context
 * @param vshader a vertex shader program (string)
 * @param fshader a fragment shader program (string)
 * @return true, if the program object was created and successfully made current 
 */
 function initShaders(gl, vshader, fshader) {
    var program = createProgram(gl, vshader, fshader);
    if (!program) {
      console.log('Failed to create program');
      return false;
    }
  
    gl.useProgram(program);
    gl.program = program;
  
    return true;
  }
  
  /**
   * Create the linked program object
   * @param gl GL context
   * @param vshader a vertex shader program (string)
   * @param fshader a fragment shader program (string)
   * @return created program object, or null if the creation has failed
   */
  function createProgram(gl, vshader, fshader) {
    // Create shader object
    var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
    var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
    if (!vertexShader || !fragmentShader) {
      return null;
    }
  
    // Create a program object
    var program = gl.createProgram();
    if (!program) {
      return null;
    }
  
    // Attach the shader objects
    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);
  
    // Link the program object
    gl.linkProgram(program);
  
    // Check the result of linking
    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
    if (!linked) {
      var error = gl.getProgramInfoLog(program);
      console.log('Failed to link program: ' + error);
      gl.deleteProgram(program);
      gl.deleteShader(fragmentShader);
      gl.deleteShader(vertexShader);
      return null;
    }
    return program;
  }
  
  /**
   * Create a shader object
   * @param gl GL context
   * @param type the type of the shader object to be created
   * @param source shader program (string)
   * @return created shader object, or null if the creation has failed.
   */
  function loadShader(gl, type, source) {
    // Create shader object
    var shader = gl.createShader(type);
    if (shader == null) {
      console.log('unable to create shader');
      return null;
    }
  
    // Set the shader program
    gl.shaderSource(shader, source);
  
    // Compile the shader
    gl.compileShader(shader);
  
    // Check the result of compilation
    var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
    if (!compiled) {
      var error = gl.getShaderInfoLog(shader);
      console.log('Failed to compile shader: ' + error);
      gl.deleteShader(shader);
      return null;
    }
  
    return shader;
  }
  
  /** 
   * Initialize and get the rendering for WebGL
   * @param canvas <cavnas> element
   * @param opt_debug flag to initialize the context for debugging
   * @return the rendering context for WebGL
   */
  function getWebGLContext(canvas, opt_debug) {
    // Get the rendering context for WebGL
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) return null;
  
    // if opt_debug is explicitly false, create the context for debugging
    if (arguments.length < 2 || opt_debug) {
      gl = WebGLDebugUtils.makeDebugContext(gl);
    }
  
    return gl;
  }
happy

陈东民
2.1k 声望269 粉丝

坚持自我 纯粹的技术