跳到主要内容

绘制点

attribute (顶点着色器变量)​

可以使用 attribute 存储限定符,在顶点着色器代码里声明变量,然后在 JS 代码里修改变量的值

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      // 1. 声明变量
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 30.0;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    // 2. 获取顶点着色器变量
    const aPosition = gl.getAttribLocation(program, 'aPosition');
    // 3. 修改变量值
    // gl.vertexAttrib4f(aPosition, 0.0, 0.0, 0.0, 1.0); // x y z w
    // gl.vertexAttrib3f(aPosition, 0.0, 0.0, 0.0); // x y z
    // gl.vertexAttrib2f(aPosition, 0.0, 0.0); // x y
    gl.vertexAttrib1f(aPosition, 0.5); // x
    gl.drawArrays(gl.POINTS, 0, 1);
  }, []);

  return <canvas id="glcanvas" width="200" height="200" style={{ backgroundColor: "#666" }}></canvas>
}

export default App;
预览

流程

Alt text

鼠标点击绘制单点、多点​

绘制单点​

  1. 给 canvas 设置点击事件
  2. 计算点击的 webgl 坐标
  3. 设置顶点坐标,绘制到画布上

示例代码

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');

    canvas.addEventListener("click", (e) => {
      const { clientX, clientY } = e;
      // 获取 canvas 宽高
      const clientWidth = e.target.clientWidth;
      const clientHeight = e.target.clientHeight;
      // 获取点击坐标距离 canvas 左和上的距离
      const offsetLeft = clientX - canvas.offsetLeft;
      const offsetTop = clientY - canvas.offsetTop;
      // webgl 坐标系原点在 canvas 中心,取宽高一半的值
      const halfWidth = clientWidth / 2;
      const halfHeight = clientHeight / 2;

      // x = (左边距 - 宽度/2) / 宽度/2
      const x = (offsetLeft - halfWidth) / halfWidth;
      // x = (高度/2 - 上边距) / 高度/2
      const y = (halfHeight - offsetTop) / halfHeight;
      console.log("wfer", x, y);
      gl.vertexAttrib2f(aPosition, x, y);
      gl.drawArrays(gl.POINTS, 0, 1);
    })
  }, []);

  return <canvas
    id="glcanvas"
    width="200"
    height="200"
    style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
  ></canvas>
}

export default App;
预览

绘制多点​

存储下每次点击的坐标,然后在点击时全部绘制出来

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');

    const points = [];
    canvas.addEventListener("click", (e) => {
      const { clientX, clientY } = e;
      // 获取 canvas 宽高
      const clientWidth = e.target.clientWidth;
      const clientHeight = e.target.clientHeight;
      // 获取点击坐标距离 canvas 左和上的距离
      const offsetLeft = clientX - canvas.offsetLeft;
      const offsetTop = clientY - canvas.offsetTop;
      // webgl 坐标系原点在 canvas 中心,取宽高一半的值
      const halfWidth = clientWidth / 2;
      const halfHeight = clientHeight / 2;

      // x = (左边距 - 宽度/2) / 宽度/2
      const x = (offsetLeft - halfWidth) / halfWidth;
      // x = (高度/2 - 上边距) / 高度/2
      const y = (halfHeight - offsetTop) / halfHeight;

      points.push({ x, y })
      points.forEach(({ x, y }) => {
        gl.vertexAttrib2f(aPosition, x, y);
        gl.drawArrays(gl.POINTS, 0, 1);
      })
    })
  }, []);

  return <canvas
    id="glcanvas"
    width="200"
    height="200"
    style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
  ></canvas>
}

export default App;
预览

uniform​

uniform 可以给顶点着色器和片元着色器使用,一般用于偏于着色器。

危险

uniform 变量需要指定精度

// mediump 中精度 | highp 高精度 | lowp 低精度
precision mediump float;
uniform vec4 uFragColor;

Alt text

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `;
    const fragmentShaderSource = `
      precision mediump float;
      uniform vec4 uFragColor;
      void main() {
        gl_FragColor = uFragColor;
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const uFragColor = gl.getUniformLocation(program, 'uFragColor');

    const points = [];
    canvas.addEventListener("click", (e) => {
      const { clientX, clientY } = e;
      // 获取 canvas 宽高
      const clientWidth = e.target.clientWidth;
      const clientHeight = e.target.clientHeight;
      // 获取点击坐标距离 canvas 左和上的距离
      const offsetLeft = clientX - canvas.offsetLeft;
      const offsetTop = clientY - canvas.offsetTop;
      // webgl 坐标系原点在 canvas 中心,取宽高一半的值
      const halfWidth = clientWidth / 2;
      const halfHeight = clientHeight / 2;

      // x = (左边距 - 宽度/2) / 宽度/2
      const x = (offsetLeft - halfWidth) / halfWidth;
      // x = (高度/2 - 上边距) / 高度/2
      const y = (halfHeight - offsetTop) / halfHeight;

      points.push({ x, y })
      points.forEach(({ x, y }) => {
        gl.vertexAttrib2f(aPosition, x, y);
        gl.uniform4f(uFragColor, x, y, 0.0, 1.0);
        gl.drawArrays(gl.POINTS, 0, 1);
      })
    })
  }, []);

  return <canvas
    id="glcanvas"
    width="200"
    height="200"
    style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
  ></canvas>
}

export default App;
预览

缓冲区绘制多点​

缓冲区对象是 WebGL 中一块内存区域,可以一次性的存储大量的顶点数据,供顶点着色器使用

步骤​

  1. 定义顶点
  2. 创建 buffer 缓冲区对象
  3. 绑定缓冲区对象
  4. 将数据写入缓冲区对象
  5. 将缓冲区对象分配给一个 attribute 变量
  6. 开启 attribute 变量

Alt text

API​

  • bindBuffer(target,buffer):绑定缓冲区对象
    • target
      • gl.ARRAY_BUFFER:标识缓冲区存储的是顶点数据
      • gl.ELEMENT_ARRAY_BUFFER:标识缓冲区存储的是顶点的索引
    • buffer:缓冲区对象
  • bufferData(target, data, type):将数据写入缓冲区对象
    • target: 同 bindBuffer target
    • data:顶点数据
    • type 如何绘制缓冲区中的数据
      • gl.STATIC_DRAW:写入一次,多次绘制
      • gl.STREAM_DRAW:写入一次,绘制若干次
      • gl.DYNAMIC_DRAW:多次写入,多次绘制
  • vertexAttribPointer(location, size, type, normalized, stride, offset):将缓冲区对象分配给 attribute 变量,告诉 GPU 如何解释缓冲区对象中的数据
    • location:attribute 变量存储地址
    • size:每个顶点需要数据个数
    • type:顶点数据类型
      • gl.FLOAT:浮点型
      • gl.UNSIGNED_BYTE:无符号字节
      • gl.SHORT:短整型
      • gl.UNSIGNED_SHORT:无符号短整型
      • gl.INT:整型
      • gl.UNSIGNED_INT:无符号整型
    • normalized:是否将数据归一化到[0,1][-1,1]这个区间
    • stride:两个相邻顶点之间的字节数
    • offset:数据偏移量,表示读取数据的开始位置
  • enableVertexAttribArray(location):启用顶点属性数组,开启后 GPU 将根据缓冲区的对象使用顶点数据
    • location:attribute 变量存储地址
    • gl.disableVertexAttribArray(aPosition); 使⽤此⽅法禁⽤

单缓冲区多点​

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = 10.0;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');

    // 1. 定义顶点
    const points = new Float32Array([
      -0.5, -0.5,
      0.5, -0.5,
      0.0, 0.5
    ]);

    // 2. 创建 buffer 缓冲区对象
    const glBuffer = gl.createBuffer();

    // 3. 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);

    // 4. 将数据写入缓冲区对象
    gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

    // 5. 将缓冲区对象分配给一个 attribute 变量 
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

    // 6. 开启 attribute 变量
    gl.enableVertexAttribArray(aPosition);

    // 7. 绘制
    gl.drawArrays(gl.POINTS, 0, 3);
  }, []);

  return <canvas
    id="glcanvas"
    width="200"
    height="200"
    style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
  ></canvas>
}

export default App;
预览

多缓冲区多点​

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      attribute float aSize;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = aSize;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const aSize = gl.getAttribLocation(program, 'aSize');

    // 1. 定义顶点
    const points = new Float32Array([
      -0.5, -0.5,
      0.5, -0.5,
      0.0, 0.5
    ]);
    const glBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aPosition);

    const sizes = new Float32Array([
      10.0,
      20.0,
      30.0
    ]);
    const sizeBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW);
    gl.vertexAttribPointer(aSize, 1, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(aSize);

    gl.drawArrays(gl.POINTS, 0, 3);
  }, []);

  return <canvas
    id="glcanvas"
    width="200"
    height="200"
    style={{ backgroundColor: "#666", margin: "20px auto", display: "block" }}
  ></canvas>
}

export default App;
预览

单缓冲区偏移量绘制多点​

import { useEffect } from "react";
import { initShader } from "./libs/webgl.js"

function App() {
  useEffect(() => {
    const canvas = document.querySelector("#glcanvas");
    const gl = canvas.getContext("webgl2");

    const vertexShaderSource = `
      attribute vec4 aPosition;
      attribute float aSize;
      void main() {
        gl_Position = aPosition;
        gl_PointSize = aSize;
      }
    `;
    const fragmentShaderSource = `
      void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
      }
    `;

    const program = initShader(gl, vertexShaderSource, fragmentShaderSource);

    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const aSize = gl.getAttribLocation(program, 'aSize');

    const points = new Float32Array([
      -0.5, -0.5, 10.0,
      0.5, -0.5, 20.0,
      0.0, 0.5, 30.0,
    ]);

    const glBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

    const BYTES = points.BYTES_PER_ELEMENT;

    gl.vertexAttribPointer(aSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2);
    gl.enableVertexAttribArray(aSize);

    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0);
    gl.enableVertexAttribArray(aPosition);

    gl.drawArrays(gl.POINTS, 0, 3);
  }, []);

  return <canvas id="glcanvas" width="200" height="200"></canvas>
}

export default App;
预览