preface

Hello, this is CSS magic – Alphardex.

When I visited foreign websites before, I found that the text of some websites was engraved on 3D graphics and could move on the graphics, which had quite good visual effect. Therefore, the author also wanted to use Three.js to try to reproduce this effect

The image above is just one of the effects. Let’s do it together

The preparatory work

The author’s own encapsulation of three. Js template: three. Js Starter

Readers can start the project by clicking on the bottom right fork

This project needs to use the font map, you can directly copy the FONT code in the HTML demo

This library relies on the global three.js, so we need to introduce three.js once more, as shown in the figure below

Implementation approach

  1. Load the bitmap font file and convert it to the shape and material required for the text object
  2. Creating a text object
  3. Create the render target, which can be understood as a canvas within a canvas, because next we will use the text object itself as a map
  4. Create a container to hold the font and paste the text object as a paste
  5. animation

positive

Take a good shelf

<div class="relative w-screen h-screen">
  <div class="kinetic-text w-full h-full bg-blue-1"></div>
  <div class="font">
    <font>A pile of font code CV from demo</font>
  </div>
</div>
Copy the code
:root {
  --blue-color-1: #2c3e50;
}

.bg-blue-1 {
  background: var(--blue-color-1);
}
Copy the code
import createGeometry from "https://cdn.skypack.dev/[email protected]";
import MSDFShader from "https://cdn.skypack.dev/[email protected]/shaders/msdf";
import parseBmfontXml from "https://cdn.skypack.dev/[email protected]";

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

const kineticTextTorusKnotVertexShader = '(vertex shader code, left blank, see below)';

const kineticTextTorusKnotFragmentShader = '(fragment shader code, left blank, see below);

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
    };
  }
  / / initialization
  async init() {
    this.createScene();
    this.createPerspectiveCamera();
    this.createRenderer(true);
    await this.createKineticText(this.params.text);
    this.createLight();
    this.createOrbitControls();
    this.addListeners();
    this.setLoop();
  }
  // Create dynamic text
  async createKineticText(text: string) {
    await this.createFontText(text);
    this.createRenderTarget();
    this.createTextContainer(); }}Copy the code

Load and create fonts

First load the font file and create the shape and material from which you can create the font object

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; }}Copy the code

shader

Vertex shader

General template, CV can be directly

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;
}
Copy the code

Chip shader

The fract function is used to create repetitive textures, and displacement distance displacement makes textures move over time. Clamp function is also used to limit the shadow range according to the Z-axis size, meaning that the farther away from the screen the shadow will be heavier, and vice versa the closer to the screen the shadow will be lighter

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.);
}
Copy the code

The text appears on the screen

Create render target

To use the font object itself as a map, a render target is created

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; }}Copy the code

Creating a font container

Create a container, and paste the font object itself as a paste, and then apply the animation to complete

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; }}}Copy the code

Don’t forget to turn the camera further away

this.cameraPosition = new THREE.Vector3(0.0.40);
Copy the code

Coquettish dynamic text appeared 🙂

The project address

Kinetic Text

There are more shapes in the demo than the ones created in this article, so feel free to play with them.