Control the position of a point with JS

Attribute The concept of variables

Attribute variables are used only by vertex shaders. JS can pass vertex-related data to vertex shaders through attribute variables.

The steps in which JS passes parameters to the attribute variable

1. Declare attribute variables in vertex shaders.

<script id="vertexShader" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    void main(){
        gl_Position = a_Position;
        gl_PointSize = 50.0;
    }
</script>
Copy the code
  • Attribute is a storage qualifier that is used to export points related objects, similar to export in ES6.

  • Vec4 is a variable type and VEC4 is a four-dimensional vector object

  • A_Position is the variable name and is a pointer to where the data is actually stored. That is, if you modify the actual data to which a_Position points outside the shader, the data corresponding to a_Position in the shader will also be modified.

2. Get the attribute variable in JS

const a_Position=gl.getAttribLocation(gl.program,'a_Position');
Copy the code

You cannot write a_Position directly in JS to get variables in shaders. Because shaders and JS are two different languages, shaders cannot expose variables globally through the window.a_position principle.

To get shader exposed variables in JS, use program objects.

  • Gl is a WebGL context object.
  • Gl.getattriblocation () is the method that gets the attribute variable in the shader. Its parameters are:
    • Gl. program is the program object that is mounted in the context object when the shader is initialized.
    • ‘a_Position’ is the name of the variable exposed by the shader

The gl context object says to the Program object, you go to the vertex shader and look for an attribute variable called ‘a_Position’.

3. Modify attribute variables

gl.vertexAttrib3f(a_Position,0.0.0.5.0.0);
Copy the code

The attribute variable is obtained by the getAttribLocation method in JS, but the variable is in GLSL ES and cannot be modified using JS syntax.

You must use the specific method vertexAttrib3f to change.

  • Gl.vertexattrib3f () is a method that changes the value of a variable. The parameters are:
    • A_Position is the shader variable obtained earlier.
    • The next three arguments are the x, y, and z positions of the vertices

Overall code:

<canvas id="canvas"></canvas>
<script id="vertexShader" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    void main(){
        gl_Position = a_Position;
        gl_PointSize = 50.0;
    }
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
    void main() {
        gl_FragColor = vec4(1.0.1.0.0.0.1.0);
    }
</script>
<script type="module">
    import {initShaders} from '.. /jsm/Utils.js';

    const canvas = document.getElementById('canvas');
    canvas.width=window.innerWidth;
    canvas.height=window.innerHeight;
    const gl = canvas.getContext('webgl');
    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;
    initShaders(gl, vsSource, fsSource);
    const a_Position=gl.getAttribLocation(gl.program,'a_Position');
    gl.vertexAttrib3f(a_Position,0.0.0.0.0.0);
    gl.clearColor(0.0.0.0.0.0.1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.POINTS, 0.1);
</script>
Copy the code

extension

VertexAttrib3f homolog function

The gl.vertexattrib3f (location,v0,v1,v2) method is one of a series of methods that modify an attribute variable in a shader. It also has many family members, such as:

gl.vertexAttrib1f(location,v0) 
gl.vertexAttrib2f(location,v0,v1)
gl.vertexAttrib3f(location,v0,v1,v2)
gl.vertexAttrib4f(location,v0,v1,v2,v3)
Copy the code

For example, the vertexAttrib1f() method defines the v0 value of a vector object. V1 and v2 default to 0.0, v3 defaults to 1.0, and its numeric type is float

Naming rules for WebGL functions

GLSL ES uses the following naming structure: < base function name >< parameter number >< parameter type >

Take vertexAttrib3f as an example:

  • VertexAttrib base function name
  • 3 Parameter Number Three
  • F Floating point I integer V number

Second, use the mouse to control the point

Gets the position of the mouse in webGL coordinates

canvas.addEventListener('click'.function(event){
    const {clientX,clientY}=event;
    const {left,top}=canvas.getBoundingClientRect();
    const [cssX,cssY]=[
        clientX-left,
        clientY-top
    ];
})
Copy the code

Canvas coordinate system is converted to WebGL coordinate system

  • Resolve differences in coordinate origin positions
const [halfWidth,halfHeight]=[width/2,height/2];// canvas the center of the canvas
const [xBaseCenter,yBaseCenter]=[cssX-halfWidth,cssY-halfHeight];// Subtract the center of the canvas from the mouse bit to get the position of the mouse based on the center of the canvas
Copy the code
  • Solve for the difference in the y direction
const yBaseCenterTop=-yBaseCenter;// Since the Y-axis in WebGL is the opposite of the Y-axis in Canvas 2D, this is negative
Copy the code
  • Resolve differences in coordinate bases
const [x,y]=[xBaseCenter/halfWidth,yBaseCenterTop/halfHeight]
Copy the code

Since the two components of canvas 2D coordinate base are the width and height of a pixel respectively, while the two components of WebGL coordinate base are the width and height of the canvas, a ratio is required.

Modifying attribute Variables

Steps:

  • Get the attribute variable
  • Modify the attribute variable while retrieving the position of the mouse in the WebGL canvas
  • Clean up the canvas
  • drawing
 <canvas id="canvas"></canvas>
  <script id="vertexShader" type="x-shader/s-vertex">
    attribute vec4 a_Position;
    void main(){
      gl_Position = a_Position;
      gl_PointSize = 50.0;
    }
  </script>
  <script id="fragmentShader" type='x-shader/x-fragment'>
   void main(){
     gl_FragColor = vec4(1.0.1.0.0.0.1.0);
   }
 </script>
  <script type="module">
    import { initShaders } from '. /.. /001/jsm/Utils.js'
    const canvas = document.getElementById('canvas');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const gl = canvas.getContext('webgl');

    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;

    initShaders(gl,vsSource,fsSource);
    const a_Position = gl.getAttribLocation(gl.program,'a_Position');
    / / gl. VertexAttrib3f (a_Position, 0.0, 0.0, 0.0);
    gl.clearColor(0.0.0.0.0.0.1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    canvas.addEventListener('click'.(event) = >{
      const { clientX ,clientY} = event;
      const {left,top,width,height} = canvas.getBoundingClientRect();
      const [cssX,cssY] = [clientX-left,clientY-top];
      const [halfWidth,halfHeight] = [width/2,height/2];
      const [xBaseCenter,yBaseCenter] = [cssX-halfWidth,cssY-halfHeight];
      const yBaseCenterTop = -yBaseCenter;
      const [x,y] = [xBaseCenter/halfWidth,yBaseCenterTop/halfHeight];
      console.log(x,y);
      gl.vertexAttrib2f(a_Position,x,y);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.POINTS,0.1)
    })
    gl.drawArrays(gl.POINTS,0.1);
  </script>
Copy the code

Webgl’s synchronous drawing principle

In the example above, each click creates a dot and the last one disappears.

The gl.drawarrays (gl.point,0,1) method is different from the ctx.draw() method in canvas 2d. Ctx.draw () really covers the image layer by layer, just like drawing.

The gl.drawarrays () method just draws synchronously, and after the main JS thread is finished, it starts all over again when it draws again. That is, the gl.drawArrays method, executed asynchronously, brushes all images off the canvas.

Solution:

Use an array to store the initial vertices and draw them together asynchronously.

  <canvas id="canvas"></canvas>
  <script id="vertexShader" type="x-shader/s-vertex">
    attribute vec4 a_Position;
    void main(){
      gl_Position = a_Position;
      gl_PointSize = 50.0;
    }
  </script>
  <script id="fragmentShader" type='x-shader/x-fragment'>
   void main(){
     gl_FragColor = vec4(1.0.1.0.0.0.1.0);
   }
 </script>
  <script type="module">
    import { initShaders } from '. /.. /001/jsm/Utils.js'
    const canvas = document.getElementById('canvas');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const gl = canvas.getContext('webgl');

    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;

    initShaders(gl,vsSource,fsSource);
    const a_Position = gl.getAttribLocation(gl.program,'a_Position');
    / / gl. VertexAttrib3f (a_Position, 0.0, 0.0, 0.0);
    gl.clearColor(0.0.0.0.0.0.1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    const g_points = [] // A collection of points
    canvas.addEventListener('click'.(event) = >{
      const { clientX ,clientY} = event;
      const {left,top,width,height} = canvas.getBoundingClientRect();
      const [cssX,cssY] = [clientX-left,clientY-top];
      const [halfWidth,halfHeight] = [width/2,height/2];
      const [xBaseCenter,yBaseCenter] = [cssX-halfWidth,cssY-halfHeight];
      const yBaseCenterTop = -yBaseCenter;
      const [x,y] = [xBaseCenter/halfWidth,yBaseCenterTop/halfHeight];
      g_points.push({x,y});
      gl.clear(gl.COLOR_BUFFER_BIT);
      g_points.forEach(({x,y}) = >{
        gl.vertexAttrib2f(a_Position,x,y);
        gl.drawArrays(gl.POINTS,0.1)})})</script>
Copy the code

Conclusion:

The phenomenon of webGL synchronous drawing is actually caused by the color buffer built into webGL.

“Have a plan,” you know? This color buffer is the “chest of confidence,” and it takes up a chunk of memory in the computer. When we use WebGL drawing, we draw the image in the color buffer first, so that the image is still in the chest, so that outsiders can not see, only the WebGL system knows.

When we want to display the image, we draw the image in the color buffer. This step is automatically done in WebGL, we just execute the drawing command.

The image stored in the color buffer is only valid for the current thread. For example, we draw in the main thread of JS, and when the main thread ends, we execute the asynchronous thread in the message queue. When the asynchronous thread is executed, the color buffer is reset by the WebGL system, and the “safe” we once had in the main thread is gone, so we can’t draw the image at that time.

Three, control vertex size

1. Expose an attribute variable in the shader that controls the size of vertices.

  <script id="vertexShader" type="x-shader/s-vertex">
    attribute vec4 a_Position;
    attribute float a_PointSize; // Define variables
    void main(){
      gl_Position = a_Position;
      gl_PointSize = a_PointSize;
    }
  </script>
Copy the code

2. Get the attribute variable in JS

 const a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize'); 
Copy the code

3. Modify attribute variables

gl.vertexAttrib1f(a_PointSize,100.0);
Copy the code

Overall code:

<body>
  <canvas id="canvas"></canvas>
  <script id="vertexShader" type="x-shader/s-vertex">
    attribute vec4 a_Position;
    attribute float a_PointSize; // Define variables
    void main(){
      gl_Position = a_Position;
      gl_PointSize = a_PointSize;
    }
  </script>
  <script id="fragmentShader" type='x-shader/x-fragment'>
   void main(){
     gl_FragColor = vec4(1.0.1.0.0.0.1.0);
   }
 </script>
  <script type="module">
    import { initShaders } from '. /.. /001/jsm/Utils.js'
    const canvas = document.getElementById('canvas');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const gl = canvas.getContext('webgl');

    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;

    initShaders(gl,vsSource,fsSource);
    const a_Position = gl.getAttribLocation(gl.program,'a_Position');
    const a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
    / / gl. VertexAttrib3f (a_Position, 0.0, 0.0, 0.0);
    gl.clearColor(0.0.0.0.0.0.1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    const g_points = [] // A collection of points
    canvas.addEventListener('click'.(event) = >{
      const { clientX ,clientY} = event;
      const {left,top,width,height} = canvas.getBoundingClientRect();
      const [cssX,cssY] = [clientX-left,clientY-top];
      const [halfWidth,halfHeight] = [width/2,height/2];
      const [xBaseCenter,yBaseCenter] = [cssX-halfWidth,cssY-halfHeight];
      const yBaseCenterTop = -yBaseCenter;
      const [x,y] = [xBaseCenter/halfWidth,yBaseCenterTop/halfHeight];
      g_points.push({x,y,size:Math.random()*100});
      gl.clear(gl.COLOR_BUFFER_BIT);
      g_points.forEach(({x,y,size}) = >{
        gl.vertexAttrib2f(a_Position,x,y);
        gl.vertexAttrib1f(a_PointSize,size);
        gl.drawArrays(gl.POINTS,0.1)})})</script>
</body>
Copy the code

Control the color of vertices

The qualifier for color variables is uniform.

Steps:

1. Expose variables that control the vertex color in the slice shader.

 <script id="fragmentShader" type='x-shader/x-fragment'>
    precision mediump float;// The definition of the precision of floating point numbers
    uniform vec4 u_FragColor;// UNIFORM is the qualifier vec4 is a 4-dimensional variable type
    void main(){
      gl_FragColor = u_FragColor;
    }
 </script>
Copy the code

Get uniform variables exposed by slice shaders in JS

 const u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');
Copy the code

The getUniformLocation method is used to get uniform variables exposed by the slice shader. The first argument is the program object and the second argument is the variable name.

3. Modify uniform variables

gl.uniform4fv(u_FragColor,1.0.1.0.0.0.1.0);
Copy the code

The Uniform4fV method can write parameters one by one, as shown in the preceding example. You can also pass an array of types.

  • Four is four pieces of data, f is a float and V is a vector
Gl. Uniform4f (u_FragColor, 1.0, 1.0, 0.0, 1.0); // Const color=new Float32Array([1.0,1.0,0.0,1.0]); gl.uniform4fv(u_FragColor,color);Copy the code
  • Methods 4 of Uniform4FV can be 1, 2, and 3
  • The second parameter of Uniform4FV must be a Float32Array Array, not a common Array object.

Float32Array is a 32-bit floating-point Array that runs much more efficiently in browsers than regular arrays.

The complete code

<body>
  <canvas id="canvas"></canvas>
  <script id="vertexShader" type="x-shader/s-vertex">
    attribute vec4 a_Position;
    attribute float a_PointSize; // Define variables
    void main(){
      gl_Position = a_Position;
      gl_PointSize = a_PointSize;
    }
  </script>
  <script id="fragmentShader" type='x-shader/x-fragment'>
    precision mediump float;
    uniform vec4 u_FragColor;
    void main(){
      gl_FragColor = u_FragColor;
    }
 </script>
  <script type="module">
    import { initShaders } from '. /.. /001/jsm/Utils.js'
    const canvas = document.getElementById('canvas');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const gl = canvas.getContext('webgl');

    const vsSource = document.getElementById('vertexShader').innerText;
    const fsSource = document.getElementById('fragmentShader').innerText;

    initShaders(gl,vsSource,fsSource);
    const a_Position = gl.getAttribLocation(gl.program,'a_Position');
    const a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
    const u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');

    / / gl. VertexAttrib3f (a_Position, 0.0, 0.0, 0.0);
    gl.clearColor(0.0.0.0.0.0.1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    const g_points = [] // A collection of points
    canvas.addEventListener('click'.(event) = >{
      const { clientX ,clientY} = event;
      const {left,top,width,height} = canvas.getBoundingClientRect();
      const [cssX,cssY] = [clientX-left,clientY-top];
      const [halfWidth,halfHeight] = [width/2,height/2];
      const [xBaseCenter,yBaseCenter] = [cssX-halfWidth,cssY-halfHeight];
      const yBaseCenterTop = -yBaseCenter;
      const [x,y] = [xBaseCenter/halfWidth,yBaseCenterTop/halfHeight];
      const color = new Float32Array([
        Math.random(),
        Math.random(),
        Math.random(),
        1.0
      ]);
      console.log(color);
      g_points.push({x,y,size:Math.random()*50,color});
      gl.clear(gl.COLOR_BUFFER_BIT);
      g_points.forEach(({x,y,size,color}) = >{
        gl.vertexAttrib2f(a_Position,x,y);
        gl.vertexAttrib1f(a_PointSize,size);
        gl.uniform4fv(u_FragColor,color);
        gl.drawArrays(gl.POINTS,0.1)})})</script>
</body>
Copy the code

Draw the vertex of the circle

 <script id="fragmentShader" type='x-shader/x-fragment'>
    precision mediump float;
    uniform vec4 u_FragColor;
    void main(){
      float dist = distance(gl_PointCoord,vec2(0.5.0.5));
      if(dist < 0.5){
        gl_FragColor = u_FragColor;
      }else{
        discard;
      }
    }
 </script>
Copy the code
  • Distance (p1,p2) computes the distance between two points

  • Gl_PointCoord The position of a slice in a point that is normalized.

  • The discard discarded

Shader syntax reference

Six, case: drawing the sky with the mouse

1. Draw stars with random transparency

First give canvas a starry background.

#canvas {
    background: url("./images/sky.jpg");
    background-size: cover;
    background-position: right bottom;
}
Copy the code

Brush with a transparent background. So you can see the CANVAS CSS background.

gl.clearColor(0.0.0.0);
Copy the code

Random transparency color:

const arr = new Float32Array([0.87.0.91.1, a]);
gl.uniform4fv(u_FragColor, arr);
Copy the code

Enable the color composition function of the chip.

gl.enable(gl.BLEND)
Copy the code

Sets the composition mode of the slice.

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
Copy the code

2. Make twinkling stars

Tween animation involves several concepts:

  • Synthesis: A collection of multiple time tracks
  • Time track: Interpolate the state of the target object through the key frame
  • Tween animation: Through two key frames, interpolate the state of an object between these two key frames, so as to realize the smooth transition of the object between two key frames

1. Create a composite object

export default class Compose{
  constructor(){
    this.parent = null // Parent objects. Composite objects can be nested with each other
    this.children = [] // A collection of children whose collection elements can be time tracks or composite objects
  }
  // Add child objects
  add(obj){
    obj.parent = this;
    this.children.push(obj)
  }
  // A method to update the state of a child object based on the current time
  update(t){
    this.children.forEach(ele= > {
      ele.update(t)
    })
  }
}
Copy the code

2. Establish a time track

export default class Track{
  constructor(target){
    this.target = target; // The target object in the time orbit
    this.parent = null; // The parent object is only a composite object
    this.start = 0; // Start time is the establishment time of the time track
    this.timeLen = 5; // Total time orbit length
    this.loop = false; // Whether to loop
    this.keyMap = new Map(a)// Keyframe collection
  }
  // Updates the status of the target object based on the current time
  // Calculate the local time first, i.e. the time of the world relative to the starting time of the time track
  // If the time track is played in a loop, the local time is mod based on the length of the time track
  // Iterate over the keyframe collection
  // If the local time is less than the time of the first keyframe, the state of the target object is equal to the state of the first keyframe
  // If the local time is greater than the last keyframe time, the state of the target object is equal to the state of the last keyframe
  // Otherwise, calculate the local time corresponding tween state between the left and right keyframes
  update(t){
    const {start,target,timeLen,loop,keyMap} = this;
    // Local time
    let time = t - this.start;
    if(loop){
      time = time % timeLen
    }
    for(const [k,fms] of keyMap){
      const last = fms.length - 1;
      if(time< fms[0] [0]){
        target[k] = fms[0] [1]}else if(time>fms[last][0]){
        target[k] = fms[last][1]}else{
        target[k] = getValBetweenFms(time,fms,last)
      }
    }
  }
}
Copy the code

KeyMap a collection of key frames. The structure is as follows:

[[' Object properties1', [[time1, attribute value],/ / key frames[time2, attribute value],/ / key frames], [' Object properties2', [[time1, attribute value],/ / key frames[time2, attribute value],/ / key frames]],]Copy the code

3. Method of obtaining the tween state between two key frames

/** The method to get the tween state between two keyframes *@time Local time *@ms A collection of keyframes * for an attribute@last Based on the time and state of these two key frames, calculate the state corresponding to the local time based on the point slope type */ 
function getValBetweenFms(time,fms,last){
  for(let i = 0; i<last; i++){const fm1 = fms[i]
    const fm2 = fms[i+1]
    if(time>=fm1[0]&&time<=fm2[0]) {const delta = {
        x:fm2[0]-fm1[0].y:fm2[1]-fm[1]}const k = delta.y/delta.x
      const b = fm1[1] - fm1[0] * k
      return k*time + b
    }
  }
}
Copy the code

Animate tween using composite objects and orbit objects

1. Create objects related to the animation

const compose=new Compose() // Synthesize object instantiation
const stars=[] // Store a collection of vertex data
canvas.addEventListener('click'.function(event){
    const {x,y}=getPosByMouse(event,canvas)
    const a=1
    const s=Math.random()*5+2
    const obj={x,y,s,a}
    stars.push(obj)

    const track=new Track(obj) // Instantiate the time orbit object
    track.start=new Date()
    track.keyMap=new Map([['a'The [[500,a],
            [1000.0],
            [1500,a],
        ]]
    ])
    track.timeLen=2000
    track.loop=true
    compose.add(track)
})
Copy the code

2. Use the request animation frame to drive the animation, continuously update the data and render the view

! (function ani(){
    compose.update(new Date())
    render()
    requestAnimationFrame(ani)
})()
Copy the code

Rendering method:

function render(){
    gl.clear(gl.COLOR_BUFFER_BIT);
    stars.forEach(({x,y,s,a}) = >{
        gl.vertexAttrib2f(a_Position,x,y);
        gl.vertexAttrib1f(a_PointSize,s);
        gl.uniform4fv(u_FragColor,new Float32Array([0.87.0.92.1,a]));
        gl.drawArrays(gl.POINTS, 0.1); })}Copy the code

3. Set some background music

#audio{
    position: absolute;
    right: 20px;
    bottom: 20px;
    opacity: 10%;
    transition: opacity 200ms;
    z-index: 20;
}
#audio:hover{
	opacity: 90%;
}
<audio id="audio" controls loop autoplay>
    <source src="./audio/cef.mp3" type="audio/mpeg">
</audio>
Copy the code