2023年9月

WEBGL实现带数字的圆点

废话不说,代码如下

// 绘制带数字的圆点
import {
  Scene,
  BufferGeometry,
  Points,
  PerspectiveCamera,
  Vector3,
  WebGLRenderer,
  CanvasTexture,
  ShaderMaterial,
  Color,
  BufferAttribute,
} from "three";

let renderer, scene, camera;
const width = window.innerWidth;
const height = window.innerHeight;

function init() {
  const container = document.getElementById("app");
  scene = new Scene();

  camera = new PerspectiveCamera(45, width / height, 1, 10000);
  camera.position.set(0, 0, 18);
  camera.lookAt(scene.position);
  camera.updateMatrix();

  const points = createPoint();
  scene.add(points);

  // const helper = new AxesHelper(5);
  // scene.add(helper);

  renderer = new WebGLRenderer();
  renderer.setSize(width, height);

  container.appendChild(renderer.domElement);
}

function createPoint() {
  const geometry = new BufferGeometry().setFromPoints([
    new Vector3(-3, 3, 0),
    new Vector3(0, 3, 0),
    new Vector3(3, 3, 0),

    new Vector3(-3, 0, 0),
    new Vector3(0, 0, 0),
    new Vector3(3, 0, 0),

    new Vector3(-3, -3, 0),
    new Vector3(0, -3, 0),
    new Vector3(3, -3, 0),

    new Vector3(0, 6, 0),
  ]);

  const numbers = [1, 2, 3, 4, 56789, 6, 7, 768, 19, 0];

  geometry.setAttribute(
    "a_number",
    new BufferAttribute(new Float32Array(numbers), 1)
  );

  const material = new createMaterial();
  const points = new Points(geometry, material);
  return points;
}

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

init();
animate();
// testAppendCanvas();

function createCanvasSpirit() {
  const SIZE = 64;
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");

  canvas.width = SIZE * 10;
  canvas.height = SIZE;

  ctx.font = SIZE + "px serif";
  ctx.fillStyle = "#000";
  // 居中
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";

  for (let i = 0; i < 10; i++) {
    ctx.fillText(i, SIZE * i + SIZE / 2, SIZE / 2);
  }

  return canvas;
}

function testAppendCanvas() {
  const canvas = createCanvasSpirit();
  canvas.style.border = "1px solid #000";
  canvas.style.marginTop = "10px";
  canvas.style.marginLeft = "10px";
  const container = document.getElementById("app");
  container.appendChild(canvas);
}

function createCanvasTexture() {
  const canvas = createCanvasSpirit();
  const texture = new CanvasTexture(canvas);
  return texture;
}

function createMaterial() {
  const vertexShader = `
    attribute float a_number;
    uniform float u_size;
    varying float v_number;


    void main(){
      v_number = a_number;

      gl_PointSize = u_size;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
  `;
  const fragmentShader = `
    uniform vec3 u_color;
    uniform vec3 u_front_color;
    uniform sampler2D u_number_spirit;

    varying float v_number;
    float numDigits(float num) {
      float digits = 1.0;
      while (num >= 10.0) {
        num = floor( num / 10.0 );
        digits += 1.0;
      }
      return floor(digits);
    }

    float getNumByDigits(float num, float digits) {
      float digit = floor( mod(num / pow(10.0, digits ), 10.0) );
      return digit;
    }

    void main() {
      float dist = distance( gl_PointCoord, vec2(0.5,0.5) );
      float discard_opacity =  1.0 - smoothstep( 0.48, 0.5, dist );
      if( discard_opacity == 0.0 ) discard;

      vec2 uv = vec2( gl_PointCoord.x,1.0- gl_PointCoord.y);

      float index = mod(v_number, pow(10.0,MAX_DIGITS) );
      float num_length = numDigits(index); // 数字长度

      uv = clamp((uv - 0.5) * (0.8 + num_length * 0.2) + 0.5,0.0,1.0);

      float num_step = 1.0 / num_length; // 每位数字占的宽度
      float current_digits = floor( uv.x / num_step ); // 当前uv渲染的是第几个数字

      float value = getNumByDigits(index, num_length - 1.0 - current_digits);  // 需要渲染的对应数字

      float x = mod(uv.x,num_step)/num_step * 0.1;
      if(num_length > 1.0){
        x = x/2.0 +  value * 0.1 + 0.025;
      }else{
        x += value * 0.1;
      }
      float y = uv.y;

      float opacity = texture2D( u_number_spirit, vec2(x,y)).a;
      opacity = step(0.1,opacity) * opacity;

      vec3 color = mix(u_color, u_front_color, opacity);

      vec4 diffuseColor = vec4( color, 1.0 * discard_opacity );
      gl_FragColor = diffuseColor;
    }

  `;

  const texture = createCanvasTexture();
  const size = 64 * window.devicePixelRatio;
  const material = new ShaderMaterial({
    uniforms: {
      u_size: { value: size }, // 点的大小
      u_color: { value: new Color(0x00ff00) }, // 背景填充色
      u_front_color: { value: new Color(0xff0000) }, // 文字颜色
      u_number_spirit: { value: texture }, // 数字纹理
    },
    defines: {
      MAX_DIGITS: "3.0", // 最大支持3位数
    },
    vertexShader,
    fragmentShader,
    transparent: true,
  });
  return material;
}