three.js реализует трехмерный динамический текст

JavaScript three.js
three.js реализует трехмерный динамический текст

предисловие

Всем привет, это волшебник CSS - alphardex.

Когда я раньше посещал иностранные веб-сайты, я обнаружил, что текст некоторых веб-сайтов выгравирован на 3D-графике и может перемещаться по графике.Визуальный эффект довольно хорош, поэтому я также хочу использовать three.js, чтобы попытаться воспроизвести это эффект.

kinetic-text.gif

Изображение выше — это лишь один из всех эффектов, давайте начнем работать вместе~

Готов к работе

Шаблон three.js, упакованный самим автором:Three.js Starter

Читатели могут нажать на вилку в правом нижнем углу, прежде чем начать этот проект.

В этом проекте необходимо использовать растровые шрифты, которые можно копировать напрямую.demoкод шрифта в HTML

Заметка:three-bmfont-textЭта библиотека зависит от глобального three.js, поэтому необходимо ввести three.js в JS, как показано ниже

Snipaste_2021-02-21_16-53-54.png

Реализовать идеи

  1. Загрузите файл растрового шрифта и преобразуйте его в форму и текстуру, необходимые для текстового объекта.
  2. Создать текстовый объект
  3. Создайте цель рендеринга, которую можно понимать как холст в холсте, потому что далее мы хотим использовать сам текстовый объект в качестве текстуры.
  4. Создайте контейнер для хранения шрифта и вставьте текстовый объект в качестве текстуры.
  5. анимация

положительный

поставить полку

<div class="relative w-screen h-screen">
  <div class="kinetic-text w-full h-full bg-blue-1"></div>
  <div class="font">
    <font>
      一坨从demo里CV而来的字体代码
    </font>
  </div>
</div>
:root {
  --blue-color-1: #2c3e50;
}

.bg-blue-1 {
  background: var(--blue-color-1);
}
import createGeometry from "https://cdn.skypack.dev/three-bmfont-text@3.0.1";
import MSDFShader from "https://cdn.skypack.dev/three-bmfont-text@3.0.1/shaders/msdf";
import parseBmfontXml from "https://cdn.skypack.dev/parse-bmfont-xml@1.1.4";

const font = parseBmfontXml(document.querySelector(".font").innerHTML);
const fontAtlas = "https://i.loli.net/2021/02/20/DcEhuYNjxCgeU42.png";

const kineticTextTorusKnotVertexShader = `(顶点着色器代码,先空着,具体见下文)`;

const kineticTextTorusKnotFragmentShader = `(片元着色器代码,先空着,具体见下文)`;

class KineticText extends Base {
  constructor(sel: string, debug: boolean) {
    super(sel, debug);
    this.cameraPosition = new THREE.Vector3(0, 0, 4);
    this.clock = new THREE.Clock();
    this.meshConfig = {
      torusKnot: {
        vertexShader: kineticTextTorusKnotVertexShader,
        fragmentShader: kineticTextTorusKnotFragmentShader,
        geometry: new THREE.TorusKnotGeometry(9, 3, 768, 3, 4, 3)
      }
    };
    this.meshNames = Object.keys(this.meshConfig);
    this.params = {
      meshName: "torusKnot",
      velocity: 0.5,
      shadow: 5,
      color: "#000000",
      frequency: 0.5,
      text: "ALPHARDEX",
      cameraZ: 2.5
    };
  }
  // 初始化
  async init() {
    this.createScene();
    this.createPerspectiveCamera();
    this.createRenderer(true);
    await this.createKineticText(this.params.text);
    this.createLight();
    this.createOrbitControls();
    this.addListeners();
    this.setLoop();
  }
  // 创建动态文字
  async createKineticText(text: string) {
    await this.createFontText(text);
    this.createRenderTarget();
    this.createTextContainer();
  }
}

Загрузка и создание шрифтов

Сначала загрузите файл шрифта и создайте форму и материал, с помощью этих двух вы можете создать объект шрифта.

class KineticText extends Base {
  loadFontText(text: string): any {
    return new Promise((resolve) => {
      const fontGeo = createGeometry({
        font,
        text
      });
      const loader = new THREE.TextureLoader();
      loader.load(fontAtlas, (texture) => {
        const fontMat = new THREE.RawShaderMaterial(
          MSDFShader({
            map: texture,
            side: THREE.DoubleSide,
            transparent: true,
            negate: false,
            color: 0xffffff
          })
        );
        resolve({ fontGeo, fontMat });
      });
    });
  }
  async createFontText(text: string) {
    const { fontGeo, fontMat } = await this.loadFontText(text);
    const textMesh = this.createMesh({
      geometry: fontGeo,
      material: fontMat
    });
    textMesh.position.set(-0.965, -0.525, 0);
    textMesh.rotation.set(ky.deg2rad(180), 0, 0);
    textMesh.scale.set(0.008, 0.025, 1);
    this.textMesh = textMesh;
  }
}

шейдер

вершинный шейдер

Общий шаблон, прямое резюме

varying vec2 vUv;
varying vec3 vPosition;

void main(){
    vec4 modelPosition=modelMatrix*vec4(position,1.);
    vec4 viewPosition=viewMatrix*modelPosition;
    vec4 projectedPosition=projectionMatrix*viewPosition;
    gl_Position=projectedPosition;
    
    vUv=uv;
    vPosition=position;
}

фрагментный шейдер

использоватьfractФункция для создания дубликатов текстур плюс расстояние смещенияdisplacementЗаставьте текстуру двигаться во времени, а затем используйтеclampФункция ограничения диапазона тени по размеру оси z, что означает, что чем дальше от экрана, тем тяжелее тень, и наоборот, чем ближе к экрану, тем светлее тень

uniform sampler2D uTexture;
uniform float uTime;
uniform float uVelocity;
uniform float uShadow;

varying vec2 vUv;
varying vec3 vPosition;

void main(){
    vec2 repeat=vec2(12.,3.);
    vec2 repeatedUv=vUv*repeat;
    vec2 displacement=vec2(uTime*uVelocity,0.);
    vec2 uv=fract(repeatedUv+displacement);
    vec3 texture=texture2D(uTexture,uv).rgb;
    // texture*=vec3(uv.x,uv.y,1.);
    float shadow=clamp(vPosition.z/uShadow,0.,1.);// farther darker (to 0).
    vec3 color=vec3(texture*shadow);
    gl_FragColor=vec4(color,1.);
}

Теперь текст отображается на экране

Snipaste_2021-02-21_17-01-56.png

Создание цели рендеринга

Чтобы использовать сам объект шрифта в качестве текстуры, создается цель рендеринга.

class KineticText extends Base {
  createRenderTarget() {
    const rt = new THREE.WebGLRenderTarget(
      window.innerWidth,
      window.innerHeight
    );
    this.rt = rt;
    const rtCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
    rtCamera.position.z = this.params.cameraZ;
    this.rtCamera = rtCamera;
    const rtScene = new THREE.Scene();
    rtScene.add(this.textMesh);
    this.rtScene = rtScene;
  }
}

Создайте контейнер шрифта

Создайте контейнер и вставьте сам объект шрифта в качестве текстуры, затем примените анимацию, и все готово.

class KineticText extends Base {
  createTextContainer() {
    if (this.mesh) {
      this.scene.remove(this.mesh);
      this.mesh = null;
      this.material!.dispose();
      this.material = null;
    }
    this.rtScene.background = new THREE.Color(this.params.color);
    const meshConfig = this.meshConfig[this.params.meshName];
    const geometry = meshConfig.geometry;
    const material = new THREE.ShaderMaterial({
      vertexShader: meshConfig.vertexShader,
      fragmentShader: meshConfig.fragmentShader,
      uniforms: {
        uTime: {
          value: 0
        },
        uVelocity: {
          value: this.params.velocity
        },
        uTexture: {
          value: this.rt.texture
        },
        uShadow: {
          value: this.params.shadow
        },
        uFrequency: {
          value: this.params.frequency
        }
      }
    });
    this.material = material;
    const mesh = this.createMesh({
      geometry,
      material
    });
    this.mesh = mesh;
  }
  update() {
    if (this.rtScene) {
      this.renderer.setRenderTarget(this.rt);
      this.renderer.render(this.rtScene, this.rtCamera);
      this.renderer.setRenderTarget(null);
    }
    const elapsedTime = this.clock.getElapsedTime();
    if (this.material) {
      this.material.uniforms.uTime.value = elapsedTime;
    }
  }
}

Не забудьте установить камеру подальше

this.cameraPosition = new THREE.Vector3(0, 0, 40);

Появился кокетливый динамичный текст :)

kinetic-text.gif

адрес проекта

Kinetic Text

В этой статье в демо-версии создано более одной формы, вы можете играть с ней по своему желанию.