13、webgl基本概念 + 绘制狮子座星空

基本概念

绘制多个点------缓冲区

1、建立顶点数据,两个浮点数构成一个顶点,分别代表 x、y值
javascript 复制代码
const vertices = new Float32Array([
  // x    y
  0.0, 0.1,
  -0.1, -0.1,
  0.1, -0.1
])

现在上面的这些顶点数据是存储在 js 缓存里的,着色器拿不到,所以需要建立一个着色器和 js 都能进入的公共区

2、建立缓冲对象
javascript 复制代码
const vertexBuffer = gl.createBuffer();

现在上面的这个缓冲区是独立存在的,它知识一个空着的仓库,和谁都没有关系,接下来需要让其和着色器建立连接

3、绑定缓冲对象
javascript 复制代码
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

gl.bindBuffer(target, buffer) 绑定缓冲区

target 要把缓冲区放在 webgl 系统中的什么位置

buffer 缓冲区

着色器对象在执行 初始化着色器的时候,已经被写入 webgl 上下文对象 gl 中了。

当缓冲区和着色器建立了绑定关系,我们就可以往这块空间写入数据了

4、往缓冲区对象中写入数据
javascript 复制代码
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

bufferData(target, data, usage) 将数据写入缓冲区

target 要把缓冲区放在 webgl系统中的什么位置

data 数据

usage 向缓冲区写入数据的方式,gl.STATIC_DRAW 是向缓冲区一次性写入数据,着色器绘制多次

现在着色器虽然绑定了缓冲区,可以访问里面的数据了,但是我们还得让着色器知道这个仓库是给哪个变量的,比如咱们这里用于控制点位的 attribute 变量。这样做是为了提高绘图效率。

5、将缓冲区对象分配给 attribute 变量
javascript 复制代码
const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);

gl.vertexAttrubPointer(local, size, type, normalized, stride, offset) 将缓冲区对象分配给 attribute变量

local attribute变量

size 顶点分量的个数,比如我们的 vertices 数组中,两个数据表示一个顶点,那咱们就写2

type 数据类型,比如 gl.FLOAT 浮点型

normalize 是否将顶点数据归一

stride 相邻两个顶点间的字节数,例子中写 0, 就是顶点之间是紧挨着的

offset 从缓冲区的什么位置开始存储变量,例子中写 0, 就是从头开始存储变量

到了这,着色器就能知道缓冲区的数据是给谁了,因为缓冲区里的顶点数据是数组,里面有多个顶点,所以得开启一个着色器批量处理顶点得属性,默认着色器指挥一个一个得接收顶点数据,然后一个一个绘制顶点

6、开启顶点数据得批处理功能
javascript 复制代码
gl.enableVertexAttribArray(a_Position);

gl.enableVertexAttribArray(location);

location attribute变量

7、绘制图形
javascript 复制代码
gl.drawArrays(gl.POINTS, 0, 3);

gl.drawArrays(mode, first, count) 方法可以绘制以下图形:

POINTS 可视的点

LINES 单独线段

LINE_STRIP 线条

LINE_LOOP 闭合线条

TRIANGLES 单独三角形

TRIANGLE_STRIP 三角带

TRIANGLE_FAN 三角扇

面的绘制

对于面的绘制,需要知道一个原理:

面有正反面;

面向我们的面,如果是正面,那它必然是逆时针绘制的;

面向我们的面,如果是反面,那它必然是顺时针绘制的。

三角带的绘制规律:

第一个三角形: V0>V1>V2

第偶数个三角形:以上一个三角形的第二条边+下一个点为基础,以和第二条边相反的方向绘制三角形

第奇数个三角形:以上一个三角形的第三条边+下一个点为基础,以和第二条边相反的方向绘制三角形

(V0, V1, V2),(V2, V3, V4),(V4, V3, V5)

三角扇的绘制规律:

第一个三角形: V0>V1>V2

第偶数个三角形:以上一个三角形的第三条边+下一个点为基础,以和第三条边相反的顺序,绘制三角形

第奇数个三角形:以上一个三角形的第三条边+下一个点为基础,以和第三条边相反的顺序,绘制三角形

(V0, V1, V2),(V0, V2, V3),(V0, V3, V4),(V0, V4, V5)

绘制矩形面

可以用 TRIANGLE_STRIP 三角带拼矩形

两个三角形分别为: V0>V1>Va2, V2>V1>V3

代码实现:

1、建立顶点数据

javascript 复制代码
const vertices = new Float32Array([
  -0.2, 0.2,
  -0.2, -0.2,
  0.2, -0.2,
  0.2, -0.2
])

上面两个浮点代表一个顶点,依次是 V0, V1, V2, V3

2、绘图

javascript 复制代码
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

上面参数的意思分别是:三角带,从第0个顶点开始画,画4个

异步绘制多点

在项目实战的时候,用户交互事件是必不可少的,因为事件是异步的,所以在绘图的时候,必须要考虑异步绘图。

异步绘制线段

1、先画一个点

2、一秒后,在左下角画一个点

3、两秒后,再画一条线段

代码实现:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制星星</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  
  <script id="vertexShader" type="x-shader/x-vertex">
    // 一个属性值,将会从缓冲区中获取数据
    attribute vec4 a_Position;
    attribute float a_PointSize;
    // 所有着色器都有一个 main 方法
    void main() {
        // gl_Position 是一个顶点着色器主要设置的变量
        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 { getPosByMouse, getInnerText, initShaderProgram } from "./utils/index.js"
    const canvas = document.querySelector('#canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const gl = canvas.getContext('webgl');
    gl.clearColor(0, 0, 0, 1);
    const vertexStr = getInnerText("vertexShader");
    const fragmentStr = getInnerText("fragmentShader");
    const program = initShaderProgram(gl, vertexStr, fragmentStr )
    let a_Position = gl.getAttribLocation(program, 'a_Position');
    let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
    let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
    gl.useProgram(program);

    let vertices = [0.0, 0.1]
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    gl.vertexAttribPointer(a_Position, 1, gl.FLOAT, false, 0, 0);
        
    gl.enableVertexAttribArray(a_Position);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.vertexAttrib1f(a_PointSize, 5);
    gl.uniform4fv(u_FragColor, new Float32Array([1, 0, 1, 1]));
    gl.drawArrays(gl.POINTS, 0, 1);
    setTimeout(() =>{
      vertices.push(...[0.2, 0.3])
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
      gl.uniform4fv(u_FragColor, new Float32Array([1, 0, 1, 1]));
      gl.drawArrays(gl.POINTS, 0, 2);
    }, 1000)
    setTimeout(() => {
      gl.drawArrays(gl.POINTS, 0, 2);
      gl.drawArrays(gl.LINES, 0, 2);
    }, 2000)
 </script>
</body>
</html>

./utils/index.js

javascript 复制代码
export function getPosByMouse(event, canvas) {
  const { offsetX, offsetY } = event;
  const { width, height } = canvas;
  console.log(offsetX, offsetY, width, height, "当前数据");

  return {
    x: (offsetX * 2) / width - 1,
    y: 1 - (offsetY * 2) / height,
  };
}

export function getInnerText(id) {
  return document.getElementById(id).innerText;
}

export function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert(
      `Unable to initialize the shader program: ${gl.getProgramInfoLog(
        shaderProgram,
      )}`,
    );
    return null;
  }

  return shaderProgram;
}
export function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert(
      `An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`,
    );
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
封装多边形对象
javascript 复制代码
const defAttr = () => ({
  gl: null,
  vertices: [],
  geoData: [],
  size: 2,
  attrName: 'a_Position',
  count: 0,
  types: ['POINTS'],
  program: null,
})

export default class Poly {
  constructor(atrr) {
    Object.assign(this, defAttr(), attr);
    this.init();
  }

  init() {
    const {attrName, size, gl, program} = this;
    if(!gl) {
      return;
    }
    const vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    this.updateBuffer(gl);
    const a_Position = gl.getAttribLocation(program, attrName);
    gl.vertexAttribPointer(a_Position, size, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(a_Position);
  }

  addVertice(...params) {
    this.vertices.push(...params);
    this.updateBuffer();
  }

  popVertice() {
    const {vertices, size} = this;
    const len = vertices.length;
    vertices.splice(len - size, len);
    this.updateCount();
  }

  setVertice(ind, ...params) {
    const {vertices, size} = this;
    const i = ind * size;
    params.forEach((param, paramId) => {
      vertices[i + paramId] = param;
    });
  }

  updateBuffer() {
    const {gl, vertices} = this;
    this.updateCount();
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  }

  updateCount() {
    this.count = this.vertices.length / this.size;
  }

  updateVertices(params) {
    const {geoData} = this;
    const vertices = [];
    geoData.forEach(data =>{
      params.forEach(key => {
        vertices.push(data[key]);
      });
    });
    this.vertices = vertices;
  }

  draw(types = this.types){
    const {gl, count} = this;
    for(let type of types) {
      gl.drawArrays(gl[type], 0, count);
    }
  }
}

属性:

gl:webgl上下文对象

vertices: 顶点数据集合,再被赋值的时候会做两件事

更新count 顶点数量,数据运算尽量不放渲染方法里

向缓冲区内写入顶点数据

geoData:模型数据,对象数组,可解析出 vertices 顶点数据

size:顶点分量的数目

positionName:代表顶点位置的 attribute 变量名

count: 顶点数量

types: 绘图方式,可以用多种方式绘图

方法:

init(): 初始化方法,建立缓冲对象,并将其绑定到webgl上下文对象上,然后向其中写入顶点数据。将缓冲对象交给 attribute变量,并开启 attribute变量的批处理功能

addVertice: 添加顶点

popVertice:删除最后一个顶点

setVertice: 根据索引位置设置顶点

updateBuffer:更新缓冲区数据,同时更新顶点数量

updateCount:更新顶点数量

updateVertices:基于 geoData解析出 vertices 数据

draw:绘图方法

封装多边形代码实例化
html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制多边形</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  
  <script id="vertexShader" type="x-shader/x-vertex">
    // 一个属性值,将会从缓冲区中获取数据
    attribute vec4 a_Position;
    attribute float a_PointSize;
    // 所有着色器都有一个 main 方法
    void main() {
        // gl_Position 是一个顶点着色器主要设置的变量
        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 Poly from "./utils/Poly.js"
    import { getPosByMouse, getInnerText, initShaderProgram } from "./utils/index.js"
    const canvas = document.querySelector('#canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const gl = canvas.getContext('webgl');
  
    const vertexStr = getInnerText("vertexShader");
    const fragmentStr = getInnerText("fragmentShader");
    const program = initShaderProgram(gl, vertexStr, fragmentStr )
    let a_Position = gl.getAttribLocation(program, 'a_Position');
    let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
    let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
    gl.useProgram(program);
    const drawPoly = new Poly({
      gl, 
      program, 
      vertices: [0.0, 0.1],
      
    })
  
    gl.vertexAttrib1f(a_PointSize, 5);
    canvas.addEventListener('click', function(event) {
      const {x, y} = getPosByMouse(event, canvas);
      drawPoly.addVertice(x, y);
      gl.clearColor(0, 0, 0, 1);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.uniform4fv(u_FragColor, new Float32Array([1, 0, 1, 1]));
      drawPoly.draw(['POINTS', 'LINES', 'LINE_STRIP'])
    })
 </script>
</body>
</html>

绘制多线

绘制多线,需要有个容器来承载它们,这样方便管理

建立容器对象

建立一个 Sky 对象,作为承载多边形的容器

javascript 复制代码
export default class Sky{
  constructor(gl) {
    this.gl = gl;
    this.children = [];
  }

  add(obj) {
    obj.gl = this.gl;
    this.children.push(obj);
  }

  updateVertices(params) {
    this.children.forEach(el =>{
      el.updateVertices(params)
    })
  }

  draw() {
    this.children.forEach(el => {
      el.init();
      el.draw();
    })
  }
}

属性:

gl:webgl 上下文对象

children:子级

方法:

add:添加子级

updateVertices:更新子级对象的顶点数据

draw:遍历子对象绘图,每个子对象对应一个buffer对象绘图之前要先初始化

需求

鼠标点击画布,绘制多边形路径

鼠标右击,取消绘制

鼠标再次点击,绘制新的多边形

javascript 复制代码
// 夜空
const sky = new Sky(gl);
// 当前正在绘制的多边形
let poly = null;
// 取消右击提示
canvas.oncontextmenu = () => {
  return false;
}

// 鼠标点击事件
canvas.addEventListener('mousedown', (event) => {
  if(event.button === 2) {
    popVertice()
  } else {
    const {x, y} = getPosByMouse(event, canvas);
    if(poly) {
      poly.addVertice(x, y);
    } else {
      createPoly(x, y);
    }
  }
  render();
})

// 鼠标移动
canvas.addEventListener('mousemove', (event) => {
  if(poly) {
    const {x, y} = getPosByMouse(event, canvas);
    poly.setVertice(poly.count - 1, x, y);
    render();
  }
})
// 新建多边形对象
function createPoly(x, y) {
  poly = new Poly({
    gl, 
    program, 
    vertices: [x, y, x, y],
    types: ['POINTS', 'LINE_STRP']
  })
  sky.add(poly)
}
// 删除最后一个顶点
function popVertice() {
  poly.popVertice();
  poly = null;
}
// 渲染
function render() {
  gl.clear(gl.COLOR_BUFFER_BIT);
  sky.draw()
}

代码实现绘制多线

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制多线</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  
  <script id="vertexShader" type="x-shader/x-vertex">
    // 一个属性值,将会从缓冲区中获取数据
    attribute vec4 a_Position;
    attribute float a_PointSize;
    // 所有着色器都有一个 main 方法
    void main() {
        // gl_Position 是一个顶点着色器主要设置的变量
        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 Poly from "./utils/Poly.js"
    import Sky from './utils/Sky.js'
    import { getPosByMouse, getInnerText, initShaderProgram } from "./utils/index.js"
    const canvas = document.querySelector('#canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const gl = canvas.getContext('webgl');
  
    const vertexStr = getInnerText("vertexShader");
    const fragmentStr = getInnerText("fragmentShader");
    const program = initShaderProgram(gl, vertexStr, fragmentStr )
    let a_Position = gl.getAttribLocation(program, 'a_Position');
    let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
    let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
    gl.useProgram(program);
    gl.vertexAttrib1f(a_PointSize, 5);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.uniform4fv(u_FragColor, new Float32Array([1, 0, 1, 1]));
    let sky = new Sky(gl), poly = null;
    canvas.oncontextmenu = () => {
      return false;
    }
    // 鼠标点击事件
    canvas.addEventListener('mousedown', (event) => {
      if(event.button === 2) {
        popVertice()
      } else {
        const {x, y} = getPosByMouse(event, canvas);
        if(poly) {
          poly.addVertice(x, y);
        } else {
          createPoly(x, y);
        }
      }
      render();
    })

    // 鼠标移动
    canvas.addEventListener('mousemove', (event) => {
      if(poly) {
        const {x, y} = getPosByMouse(event, canvas);
        poly.setVertice(poly.count - 1, x, y);
        render();
      }
    })

    function createPoly(x, y) {
      poly = new Poly({
        gl, 
        program, 
        vertices: [x, y, x, y],
        types: ['POINTS', 'LINE_STRIP']
      })
      sky.add(poly)
    }
    // 删除最后一个顶点
    function popVertice() {
      poly.popVertice();
      poly = null;
    }
    // 渲染
    function render() {
      gl.clear(gl.COLOR_BUFFER_BIT);
      sky.draw()
    }
 </script>
</body>
</html>

狮子座图案绘制

1、基本绘图需求

1、鼠标第一次点击画布时:

创建多边形

绘制2个点

2、鼠标移动时:

当前多边形最后一个顶点随鼠标移动

3、鼠标接下来点击画布时:

新建一个点

4、鼠标右击时:

删除最后一个随鼠标移动的点

2、优化需求

1、顶点要有闪烁动画

2、建立顶点的时候,如果鼠标点击了其他顶点,就不要再显示新的顶点

代码实现

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>绘制多线</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
     #canvas {
      background: url("../images/lionsky.png");
      background-size: cover;
      background-position: right bottom;
      overflow: hidden;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>

  <script id="vertexShader" type="x-shader/x-vertex">
    attribute vec4 a_Attr;
    varying float v_Alpha;
    void main(){
      gl_Position = vec4(a_Attr.x, a_Attr.y, 0, 1);
      gl_PointSize = a_Attr.z;
      v_Alpha = a_Attr.w;
    }
  </script>
  <script id="fragmentShader" type="x-shader/x-fragment">
    precision mediump float;
    varying float v_Alpha;
    void main(){
      float dist = distance(gl_PointCoord, vec2(0.5, 0.5));
      if(dist < 0.5) {
        gl_FragColor = vec4(0.87, 0.91, 1, v_Alpha);
      } else {
        discard;
      }
    }
  </script>
  <script type="module">
    import Poly from "../utils/Poly.js";
    import Sky from '../utils/Sky.js';
     import Compose from "../utils/Compose.js";
    import Track from "../utils/Track.js";
    import { getPosByMouse, getInnerText, initShaderProgram, glToCssPos } from "../utils/index.js";
    const canvas = document.querySelector('#canvas');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
   const gl = canvas.getContext('webgl');
    gl.clearColor(0, 0, 0, 0);
    const vertexStr = getInnerText("vertexShader");
    const fragmentStr = getInnerText("fragmentShader");
    const program = initShaderProgram(gl, vertexStr, fragmentStr );
    gl.enable(gl.BLEND);
    // 设置混合公式(正常透明模式)
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.useProgram(program);
    gl.clearColor(0, 0, 0, 0);
    let sky = new Sky(gl), 
        poly = null, 
        point = null, 
        compose = new Compose();
    canvas.oncontextmenu = () => {
      return false;
    }
    // 鼠标点击事件
    canvas.addEventListener('mousedown', (event) => {
      if(event.button === 2) {
        popVertice()
      } else {
        const {x, y} = getPosByMouse(event, canvas);
        if(poly) {
          addVertice(x, y);
        } else {
          createPoly(x, y);
        }
      }
      render();
    })

    // 鼠标移动
    /**
     * 鼠标移动:基于鼠标是否划上顶点,设置鼠标的状态
     * 设置正在绘制的多边形的最后一个顶点点位
    */
    canvas.addEventListener('mousemove', (event) => {
      const {x, y} = getPosByMouse(event, canvas);
      point = hoverPoint(x, y);
      canvas.style.cursor = point ? 'pointer' : 'default';
      if(poly) {
        const obj = poly.geoData[poly.geoData.length - 1]
        obj.x = x;
        obj.y = y;
      }
    })
    /**
     * 遍历 sky中的所有顶点数据
     * 忽略绘图时随鼠标移动的点
     * 获取鼠标和顶点的像素距离
     * 若此距离小于10像素,返回此点,否则返回null
     * 
    */
    function hoverPoint(mx, my) {
      for(let {geoData} of sky.children) {
        for(let obj of geoData) {
          if(poly && obj === poly.geoData[poly.geoData.length - 1]) {
            continue;
          }
          const delta = {
            x: mx - obj.x,
            y: my - obj.y
          }
          const {x, y} = glToCssPos(delta, canvas), dist = x * x + y * y;
          if(dist < 100) {
            return obj;
          }
        }
      }
      return null;
    }

    function addVertice(x, y) {
      let {geoData} = poly;
      if(point) {
        geoData[geoData.length - 1] = point;
      }
      let obj = {x, y, pointSize: getRandom(), alpha: 1};
      geoData.push(obj);
      createTrack(obj);
    }

    function createPoly(x, y) {
      let o1 = point ? point: {x, y, pointSize: getRandom(), alpha: 1},
      o2 = {x, y, pointSize: getRandom(), alpha: 1};
      poly = new Poly({
        gl, 
        program, 
        size: 4,
        attrName: 'a_Attr',
        geoData: [o1, o2],
        types: ['POINTS', 'LINE_STRIP']
      })
      sky.add(poly);
      createTrack(o1);
      createTrack(o2);
    }
    function createTrack(obj) {
      const {pointSize} = obj;
      const track = new Track(obj);
      track.start = Date.now();
      track.keyMap = new Map([
        [
          'pointSize',
          [
            [500, pointSize],
            [1000, 0],
            [1500, pointSize]
          ]
        ],
        [
          'alpha',
          [
            [500, 1],
            [1000, 0],
            [1500, 1],
          ]
        ]
      ])
      track.timeLen = 2000;
      track.loop = true;
      compose.add(track);
    }
    function getRandom() {
      return Math.random()* 8 + 3;
    }
    // 删除最后一个顶点
    function popVertice() {
      poly?.geoData?.pop();
      compose.children.pop();
      poly = null;
    }
    // 渲染
    function render() {
      gl.clear(gl.COLOR_BUFFER_BIT);
      sky.draw()
    }
    !(function ani(){
      compose.update(Date.now());
      sky.updateVertices(['x', 'y', 'pointSize', 'alpha'])
      render();
      requestAnimationFrame(ani);
    })()
 </script>
</body>
</html>

.../utils/index.js

javascript 复制代码
export function getPosByMouse(event, canvas) {
  const { offsetX, offsetY } = event;
  const { width, height } = canvas;
  return {
    x: (offsetX * 2) / width - 1,
    y: 1 - (offsetY * 2) / height,
  };
}
/**
 * 将 gl 的坐标位置转换成像素位置,精确度更高
 * @param {*} param0
 * @param {*} canvas
 * @returns
 */
export function glToCssPos({ x, y }, canvas) {
  const { width, height } = canvas;
  return {
    x: ((x + 1) * width) / 2,
    y: 1 - (y * height) / 2,
  };
}
export function getInnerText(id) {
  return document.getElementById(id).innerText;
}

export function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert(
      `Unable to initialize the shader program: ${gl.getProgramInfoLog(
        shaderProgram,
      )}`,
    );
    return null;
  }

  return shaderProgram;
}
export function loadShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert(
      `An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`,
    );
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
相关推荐
之歆1 小时前
Day03_HTML 列表、表格、表单完整指南(上)
前端·html
吴声子夜歌1 小时前
Vue3——组件基础
前端·javascript·vue.js
恋猫de小郭1 小时前
Jetpack Compose 1.11 正式版发布,下一代的全新控件和样式 API,你必须知道
android·前端·flutter
孩子 你要相信光1 小时前
前端 Canvas 导出带水印图片跨域问题
前端
zxna2 小时前
前端直连oss分片上传文件,断点续传
前端
Southern Wind2 小时前
Vue 3 + Socket.io 实时聊天项目完整开发文档
前端·javascript·vue.js
甄心爱学习2 小时前
【项目实训(个人4)】
前端·vue.js·python
轮子大叔2 小时前
HTML入门
前端·html
skilllite作者2 小时前
SkillLite 技术演进笔记:Workspace、沙箱与进化
java·开发语言·前端·笔记·安全·agentskills