Three.js realizes the 3D dynamic Logo of Facebook meta universe

background

Facebook recently renamed its parent company Meta and announced the official start of its entry into yuancosmos 🪐 Field. This paper mainly describes how to realize the cool 3D dynamic Logo of Meta company through Three.js + Blender technology stack, including basic model ring, torus kink, pipeline and model generation, model loading, adding animation, adding click events, changing materials, etc.

What is the primordial universe

The term Metaverse comes from Neil Stephenson's avalanche in 1992. The book describes a virtual world Metaverse parallel to the real world. All people in real life have a network Avatar. Wikipedia's description of the meta universe is: a 3D virtual space with the characteristics of convergence and physical persistence through virtual enhanced physical reality. It is based on the future Internet and has the characteristics of link perception and sharing.

The connotation of metauniverse is to absorb the achievements of information revolution 5G/6G, Internet revolution web3.0, artificial intelligence revolution and virtual reality technology revolution including VR, AR and MR, especially game engine, and show mankind the possibility of building a holographic digital world parallel to the traditional physical world; It triggered the interaction of information science, quantum science, mathematics and life science, and changed the scientific paradigm; Promoted the breakthrough of traditional philosophy, sociology and even humanities system; It includes all digital technologies. Just like the scene in the movie "the number one player", one day in the future, people can switch their identity anytime and anywhere, shuttle freely between the physical world and the digital world, and live and learn in the meta universe composed of virtual space and time nodes.

Realization effect

To get to the point, let's take a look at the implementation effect of this example.

🔗 Online preview: https://dragonir.github.io/3d-meta-logo (due to the large model, the loading progress may be slow, so you need to wait patiently)

Development and Implementation

📌 Note: the above example dynamic diagram shows trial 4. If you don't want to see the trial error process (Trial 1, trial 2 and trial 3), you can directly jump to trial 4 to view the detailed implementation process. Difficulties are listed in the failure process. Those who know the solution please comment in the comment area.

Before development, we first look at the Meta Logo. We can find that it is formed by folding and twisting a ring. Therefore, when implementing it, we can start with implementing the ring.

Trial 1: THREE.TorusGeometry

Three.js provides the base geometry tree.torusgeometry (doughnut), which looks like a doughnut 🍩 Simple graphics. Main parameters:

  • Radius: optional. Defines the radius dimension of the torus. The default value is 1.
  • Tube: optional. Defines the tube radius of the ring. The default value is 0.4.
  • radialSegments: optional. Defines the number of segments along the length of the torus. The default value is 8.
  • tubularSegments: optional. Defines the number of segments in the width direction of the torus. The default value is 6.
  • arc: optional. Defines the length of the torus drawn. The value range is 0 to 2 * π. The default value is 2 * π (a complete circle).

Syntax example:

THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc);

😭 Failed: no way to twist the ring was found.

Trial 2: THREE.TorusKnotGeometry

THREE.TorusKnotGeometry can be used to create three-dimensional torus kink. Torus kink is a special knot that looks like a tube rotating around itself several times. Main parameters:

  • Radius: optional. Sets the radius of the full torus. The default value is 1.
  • tube: optional. Sets the radius of the pipe. The default value is 0.4.
  • radialSegments: optional. Specify the number of segments of the pipe section. The more segments, the smoother the pipe section circle. The default value is 8.
  • tubularSegments: optional. Specify the number of segments of the pipe. The more segments, the smoother the pipe. The default value is 64.
  • p: Optional. Determines how many times the geometry will rotate about its axis of rotation. The default value is 2.
  • q: Optional. Determines how many times the geometry will rotate around its inner ring, and the default value is 3.

Syntax example:

THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments , p, q);

😭 Failed: no way to control the degree of manual twist was found.

Trial 3: three. Tubeometry

The tube geometry stretches a tube along a three-dimensional spline curve. You can specify some fixed points to define the path, and then create the pipe using the tree. Tubeometry. Main parameters:

  • Path: this attribute uses a THREE.SplineCurve3 object to specify the path that the pipe should follow.
  • Segments: this attribute specifies the number of segments used to build this tube. The default value is 64. The longer the path, the more segments should be specified.
  • Radius: this attribute specifies the radius of the tube. The default value is 1
  • radiusSegments: this attribute specifies the number of segments of the pipe circumference. The default value is 8, and the more segments, the rounder the pipe looks.
  • closed: if this property is set to true, the header and tail of the pipeline will be connected, and the default value is false.

Code example

// ...
var controls = new function () {
  // Position coordinates of points
  this.deafultpoints = [
    [0, 0.4, -0.4],
    [0.4, 0, 0],
    [0.4, 0.8, 0.4],
    [0, 0.4, 0.4],
    [-0.4, 0, 0],
    [-0.4, 0.8, -0.4],
    [0, 0.4, -0.4]
  ]
  this.segments = 64;
  this.radius = 1;
  this.radiusSegments = 8;
  this.closed = true;
  this.points = [];
  this.newPoints = function () {
    var points = [];
    for (var i = 0; i < controls.deafultpoints.length; i++) {
      var _x = controls.deafultpoints[i][0] * 22;
      var _y = controls.deafultpoints[i][1] * 22;
      var _z = controls.deafultpoints[i][2] * 22;
      points.push(new THREE.Vector3(_x, _y, _z));
    }
    controls.points = points;
    controls.redraw();
  };
  this.redraw = function () {
    redrawGeometryAndUpdateUI(gui, scene, controls, function() {
      return generatePoints(controls.points, controls.segments, controls.radius, controls.radiusSegments,
        controls.closed);
    });
  };
};
controls.newPoints();
function generatePoints(points, segments, radius, radiusSegments, closed) {
  if (spGroup) scene.remove(spGroup);
  spGroup = new THREE.Object3D();
  var material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: false });
  points.forEach(function (point) {
    var spGeom = new THREE.SphereGeometry(0.1);
    var spMesh = new THREE.Mesh(spGeom, material);
    spMesh.position.copy(point);
    spGroup.add(spMesh);
  });
  scene.add(spGroup);
  return new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), segments, radius, radiusSegments, closed);
}
// ...

😊 Reluctantly successful: however, the circular ring formed by the pipeline is not round enough, and accurate coordinates are required to achieve a perfect circular arc. The coordinate calculation method has not been found yet.

Trial 4: Blender + Three.js

Although the use of three.tubeometry can be achieved reluctantly, the effect is not good. To achieve a smooth ring, you need to add an accurate twisted ring curve path function for the pipe. Due to limited mathematical ability 🤕 However, the calculation method of twisted arc path has not been found yet. Therefore, it is decided to solve it from the modeling level.

success 😄: But I spent a lot of time modeling with Blender 💔.

Modeling tutorial

When visiting station B, I found the treasure video sent by the big man, which just solved my problem.

🎦 Portal: [dynamic design tutorial] how can AE+blender play? The Meta dynamic logo of Facebook Meta universe has been fully analyzed and 100% learned

Modeling with Blender

Use Blender to model and export the fbx format that can carry animation. Don't forget to check the bake animation option when exporting.

Load dependency

<script src="./assets/libs/three.js"></script>
<script src="./assets/libs/loaders/FBXLoader.js"></script>
<script src="./assets/libs/inflate.min.js"></script>
<script src="./assets/libs/OrbitControls.js"></script>
<script src="./assets/libs/stats.js"></script>

Scene initialization

var container, stats, controls, compose, camera, scene, renderer, light, clickableObjects = [], mixer, mixerArr = [], manMixer;
var clock = new THREE.Clock();
init();
animate();
function init() {
  container = document.createElement('div');
  document.body.appendChild(container);
  // scene
  scene = new THREE.Scene();
  scene.transparent = true;
  scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
  // Perspective camera: field of view, aspect ratio, near face, far face
  camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.set(0, 4, 16);
  camera.lookAt(new THREE.Vector3(0, 0, 0));
  // Hemispherical lighting: creates a more natural light source for outdoor effects
  light = new THREE.HemisphereLight(0xefefef);
  light.position.set(0, 20, 0);
  scene.add(light);
  // Parallel light
  light = new THREE.DirectionalLight(0x2d2d2d);
  light.position.set(0, 20, 10);
  light.castShadow = true;
  scene.add(light);
  // Ambient light
  var ambientLight = new THREE.AmbientLight(0xffffff, .5);
  scene.add(ambientLight);
  // grid
  var grid = new THREE.GridHelper(100, 100, 0xffffff, 0xffffff);
  grid.position.set(0, -10, 0);
  grid.material.opacity = 0.3;
  grid.material.transparent = true;
  scene.add(grid);
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.setSize(window.innerWidth, window.innerHeight);
  // Set the background color to transparent
  renderer.setClearAlpha(0);
  // Turn on shadows
  renderer.shadowMap.enabled = true;
  container.appendChild(renderer.domElement);
  // Add lens controller
  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.target.set(0, 0, 0);
  controls.update();
  window.addEventListener('resize', onWindowResize, false);
  // Initialize performance plug-in
  stats = new Stats();
  container.appendChild(stats.dom);
}
// Screen zoom
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

📌 For the detailed process of scenario initialization, please read another article of mine Realizing cool acid style 3D pages with three.js.

Load Logo model

Use fbxlloader to load the model and set the location and size of the model.

var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  mesh.rotation.y = Math.PI / 2;
  mesh.position.set(0, 1, 0);
  mesh.scale.set(0.05, 0.05, 0.05);
  scene.add(mesh);
});

Add material

The Logo in this paper uses the MeshPhysicalMaterial material, which is a PBR physical material, which can better simulate lighting calculation. Compared with the specular mesh material, the rendering effect of MeshPhongMaterial is more realistic. Use the THREE.TextureLoader to add map attributes to the material to load model maps. The following image is a texture map of metal texture.

var texLoader = new THREE.TextureLoader();
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      if (child.name === 'Bessel circle') {
        child.material = new THREE.MeshPhysicalMaterial({
          map: texLoader.load("./assets/images/metal.png"),
          metalness: .2,
          roughness: 0.1,
          exposure: 0.4
        });
      }
    }
  });
})

Add animation

  • AnimationMixer objects are animation players for specific objects in the scene. When multiple objects in the scene are animated independently, you can use an AnimationMixer for each object.
  • The clipAction method of the AnimationMixer object generates instances that control the execution of animation.
loader.load('assets/models/meta.fbx', function (mesh) {
  mesh.animations.map(item => {
    mesh.traverse(child => {
      // Because there are multiple objects in the model and they have different animations, only the mesh of Bezier circle is animated in the example
      if (child.name === 'Bessel circle') {
        let mixer = new THREE.AnimationMixer(child);
        mixerArr.push(mixer);
        let animationClip = item;
        animationClip.duration = 8;
        let clipAction = mixer.clipAction(animationClip).play();
        animationClip = clipAction.getClip();
      }
    })
  })
});

After adding the animation, don't forget to update the animation in the requestAnimationFrame.

function animate() {
  renderer.render(scene, camera);
  // Obtain the time interval between the first and second execution of the method
  let time = clock.getDelta();
  // Update logo animation
  mixerArr.map(mixer => {
    mixer && mixer.update(time);
  });
  // Update character animation
  manMixer && manMixer.update(time);
  stats.update();
  requestAnimationFrame(animate);
}

Show loading progress

Fbxlloader returns two callback functions at the same time, which can be used as follows to show the model loading process and the logical implementation of loading failure.

<div class="loading" id="loading">
  <p class="text">Loading progress<span id="progress">0%</span></p>
<div>
var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx', mesh => {
}, res => {
  // Load process
  let progress = (res.loaded / res.total * 100).toFixed(0);
  document.getElementById('progress').innerText = progress;
  if (progress === 100) {
    document.getElementById('loading').style.display = 'none';
  }
}, err => {
  // Loading failed
  console.log(err)
});

Realization effect

Click change material

Listen to the click events on the page and get the current click object through HREE.Raycaster. In order to show an example, I replaced the click object with a material THREE.MeshStandardMaterial and gave it random color, metalness, metal texture and roughness.

//Declare raymaster and mouse variables
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
  // Calculate the position of the point required by raymaster by clicking the position of the mouse. Take the center of the screen as the origin, and the range of values is - 1 to 1
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  // The raymaster is calculated by the position of the mouse point and the matrix of the current camera
  raycaster.setFromCamera(mouse, camera);
  // Gets a collection of arrays where raymaster lines intersect all models
  let intersects = raycaster.intersectObjects(clickableObjects);
  if (intersects.length > 0) {
    console.log(intersects[0].object)
    let selectedObj = intersects[0].object;
    selectedObj.material = new THREE.MeshStandardMaterial({
      color: `#${Math.random().toString(16).slice(-6)}`,
      metalness: Math.random(),
      roughness: Math.random()
    })
  }
}
window.addEventListener('click', onMouseClick, false);

📌 For more information about mesh materials, refer to the link at the end of the article.

Load character model

The loading process of character model is the same as that of Logo model. I added a character who is performing turtle Qigong. Unexpectedly, it fits very well with the rotation animation of the Logo model 😂 .

loader.load('assets/models/man.fbx', function (mesh) {
  mesh.traverse(function (child) {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });
  mesh.rotation.y = Math.PI / 2;
  mesh.position.set(-14, -8.4, -3);
  mesh.scale.set(0.085, 0.085, 0.085);
  scene.add(mesh);
  manMixer = new THREE.AnimationMixer(mesh);
  let animationClip = mesh.animations[0];
  let clipAction = manMixer.clipAction(animationClip).play();
  animationClip = clipAction.getClip();
}, res => {
  let progress = (res.loaded / res.total * 100).toFixed(0);
  document.getElementById('progress').innerText = progress + '%';
  if (Number(progress) === 100) {
    document.getElementById('loading').style.display = 'none';
  }
}, err => {
  console.log(err)
});

The example character model in this article comes from mixamo.com , the website has hundreds of characters and thousands of actions, which can be freely combined and downloaded for free. You can choose your favorite characters and animation actions to practice Three.js.

summary

The main knowledge points involved in this paper include:

  • THREE.TorusGeometry: torus.
  • THREE.TorusKnotGeometry: torus kink.
  • Three.tubeometry: pipe.
  • Blender: modeling.
  • Fbxlloader: load the model and display the loading progress.
  • TextureLoader: loads materials.
  • THREE.AnimationMixer: loads animation.
  • THREE.Raycaster: capture the click model.

🔗 Full code: https://github.com/dragonir/3d-meta-logo

reference material

Author: dragonir address: https://www.cnblogs.com/drago...

Keywords: Javascript Front-end html5 Three.js 3d

Added by vcarter on Thu, 18 Nov 2021 15:24:56 +0200