sdf 渲染文字在three.js中

sdf 渲染文字在three.js中

最終效果:
上面是 SDF文字貼圖,下面是在webgl中渲染。
在這裏插入圖片描述

實現

使用tiny-sdf製作sdf文字貼圖。

在場景中渲染,核心類:

/**
 * 生成SDF標籤
 */
class LabelSDF {
   
   
  constructor({
   
    height, content }) {
   
   
    const bufferF = 8;

    // 影響生成文字圖片的分辨率
    const fontSize = 256;

    const fontWidth = fontSize + (fontSize / bufferF) * 2;
    const width = height * content.length;
    this.textInfo = this.getSDF(content, fontSize, bufferF, fontWidth);
    const geo = this.getGeo(width, height, fontWidth, this.textInfo, content);
    const mat = this.getMat(this.textInfo);
    const mesh = new THREE.Mesh(geo, mat);
    this.mesh = setMeshAnchor({
   
    anchorX: 1, anchorY: 0.5, mesh: mesh });
  }

  getMesh() {
   
   
    return this.mesh;
  }

  /**
   * 生成sdf文字
   */
  getSDF(content, fontSize, bufferF, fontWidth) {
   
   
    var chars = content || "礦工";
    let sdfs = {
   
   };
    // debug
    var canvas = document.getElementById("canvas");
    // var canvas = document.createElement('canvas');
    canvas.width = content.length * fontWidth;
    canvas.height = fontWidth;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    var fontSize = fontSize || 32;
    var fontWeight = 500;
    var buffer = fontSize / (bufferF || 8);
    // var radius = fontSize / 3;
    var radius = 0;
    var sdf = new TinySDF(fontSize, buffer, radius, null, null, fontWeight);

    for (
      var y = 0, i = 0;
      y + sdf.size <= canvas.height && i < chars.length;
      y += sdf.size
    ) {
   
   
      for (
        var x = 0;
        x + sdf.size <= canvas.width && i < chars.length;
        x += sdf.size
      ) {
   
   
        ctx.putImageData(
          makeRGBAImageData(sdf.draw(chars[i]), sdf.size, ctx),
          x,
          y
        );
        sdfs[chars[i]] = {
   
    x: x, y: y };
        i++;
      }
    }
    // console.error(sdfs);
    return {
   
   
      ctx: ctx,
      canvas: canvas,
      sdfs,
    };
  }

  getShader() {
   
   
    return {
   
   
      vs: `
        attribute vec2 a_texcoord;
        varying vec2 v_texcoord;

        void main() {
            v_texcoord = a_texcoord;
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }`,
      fs: `
        uniform sampler2D u_texture;
        uniform vec4 u_color;
        uniform float u_weigth;

        varying vec2 v_texcoord;


        void main() {
            const vec3 glyphcolor = vec3(0.0, 1., 1.0);
            const vec3 glowcolor = vec3(0.2, 0, 1.0);
            float dist = texture2D(u_texture, v_texcoord).r;
            float delta = 0.1;
            float finalalpha = smoothstep(0.5-delta, 0.5+delta, dist);
            vec4 clr = vec4(glyphcolor, dist);

            // 開啓描邊
            int glow = 1;
            if(glow == 1) {
              clr.rgb = mix(glowcolor, glyphcolor, finalalpha);
              float alpha = smoothstep(0.0, 0.5+(0.6*(1.*0.5)), sqrt(dist));
              clr.a = alpha;
            }
            gl_FragColor = clr;
        }`,
    };
  }

  getMat(textInfo) {
   
   
    const Shader = this.getShader();
    const texture = new THREE.Texture(textInfo.canvas);
    texture.needsUpdate = true;
    let material = new THREE.ShaderMaterial({
   
   
      side: THREE.DoubleSide,
      // todo: 不明白爲什麼必須開啓透明或者改變混合模式
      transparent: true,
      uniforms: {
   
   
        u_texsize: {
   
   
          value: new THREE.Vector2(
            textInfo.canvas.width,
            textInfo.canvas.height
          ),
        },
        u_color: {
   
    value: new THREE.Vector4(0, 1, 0, 1) },
        u_texture: {
   
    value: texture, type: "t" },
        u_weigth: {
   
    value: 0.1, type: "f" },
      },
      vertexShader: Shader.vs,
      fragmentShader: Shader.fs,
    });

    return material;
  }

  getGeo(width, height, fontWidth, textInfo, content) {
   
   
    const count = content.length;
    const cHeight = textInfo.canvas.height;
    const cWidth = textInfo.canvas.width;
    let geometry = new THREE.BufferGeometry();
    geometry.setAttribute(
      "position",
      new THREE.Float32BufferAttribute(
        [0, height, 0, 0, 0, 0, width, 0, 0, width, height, 0],
        3
      )
    );
    geometry.setIndex([1, 2, 0, 2, 3, 0]);

    const textureElements = [];
    textureElements.push(
      0,
      1,

      0,
      1 - fontWidth / cHeight,

      (fontWidth * count) / cWidth,
      1 - fontWidth / cHeight,

      (fontWidth * count) / cWidth,
      1
    );
    geometry.setAttribute(
      "a_texcoord",
      new THREE.BufferAttribute(new Float32Array(textureElements), 2)
    );
    return geometry;
  }
}

使用:

class App {
   
   
  constructor() {
   
   
    this.textInfo = null;
    this.stage = new Stage("#app");
    this.stage.run();
    // todo,對英文間距大
    let label = new LabelSDF({
   
    height: 100, content: "你好" });
    this.stage.scene.add(label.getMesh());
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章