This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

background

Facebook recently changed the name of its parent company to Meta and announced its entry into the meta-universe 🪐. In this article, we introduce Meta’s cool 3D dynamic Logo using the Three. Js + Blender stack, including basic model circles, torus kinks, pipes and model generation, model loading, adding animations, adding click events, and changing materials.

What is the meta-universe

The term Metaverse originated from Neil Stephenson’s avalanche in 1992, which described a virtual world parallel to the real world, Metaverse, in which all real-life people have an online Avatar. Wikipedia describes the metadverse as a 3D virtual space based on the Internet of the future with link awareness and sharing features that exhibit convergence and physical persistence through virtually-enhanced physical reality.

The content of meta-universe is to assimilate the achievements of information revolution 5G/6G, Internet revolution Web3.0, artificial intelligence revolution, VR, AR, MR, especially game engine, and virtual reality technology revolution, showing the possibility of building a holographic digital world parallel to the traditional physical world to mankind. The interaction of information science, quantum science, mathematics and life science has changed the scientific paradigm. Promoted the traditional philosophy, sociology and even the humanities system breakthrough; It’s all about digital technology. Just like the scene in the movie ready Player One, one day in the future, people will be able to switch identities anytime and anywhere, freely shuttled between the physical world and the digital world, living and learning in the virtual space and time nodes of the meta-universe.

Implementation effect

To get down to business, take a look at the implementation of this article’s example.

🔗 online preview: dragonir.github. IO / 3D-meta-log… (Due to the large model, the loading progress may be slow, requiring patience)

The development of implementation

📌 Note: the above example GIF shows trial 4. If you do not want to see the trial and error process (trial 1, trial 2, trial 3), you can directly jump to the section of trial 4 to view the detailed implementation process. The failure process lists the difficulties, if you know the solution please feel free to comment in the comments section.

Looking at the Meta Logo before development, we can see that it is a circle that has been folded and twisted in half, so we can implement it from the beginning.

Test one: THREE.TorusGeometry

The basic geometry provided by three.js is three.TorusGeometry, which is a simple shape that looks like a doughnut 🍩. Main parameters:

  • radius: optional. Define the radius size of the ring. The default value is1.
  • tube: optional. Define the pipe radius of the ring. The default value is0.4.
  • radialSegments: optional. Define the number of segments along the length of the ring. The default value is8.
  • tubularSegments: optional. Defines the number of segments along the width of the ring. The default value is6.
  • arc: optional. Defines the length of the circle to draw. The value range is02 * PI.. The default value is2 * PI.(A complete circle).

Syntax examples:

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

😭 failure: Failed to find a way to twist the ring.

Test two: THREE.TorusKnotGeometry

TorusKnotGeometry can be used to create a three-dimensional torus kine, a special type of knot that looks like a tube rotating around itself several times. Main parameters:

  • radius: optional. Sets the radius of the full ring. The default value is1.
  • tube: optional. Sets the radius of the pipe. The default is0.4.
  • radialSegments: optional. Specifies the number of segments in a pipe section. The more segments, the smoother the pipe section circle. Default is8.
  • tubularSegments: optional. Specifies the number of segments in a pipe. The more segments, the smoother the pipe. Default is64.
  • p: optional. Determines how many times the geometry will rotate around its axis of symmetry. The default is2.
  • q: optional. Determines how many times the geometry will be rotated around its inner ring. The default is3.

Syntax examples:

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

😭 failure: failed to find a way to control manual distortion.

Experiment THREE: The TubeGeometry

A tube is stretched out along a three-dimensional spline curve. You can specify some points to define the path, and then use THREE.TubeGeometry to create the tube. Main parameters:

  • path: This attribute uses oneTHREE.SplineCurve3Object to specify the path the pipe should follow.
  • segments: This property specifies the number of segments to build the tube. The default value is64. The longer the path, the more segments should be specified.
  • radius: This property specifies the radius of the tube. The default value is1.
  • radiusSegments: This property specifies the number of segments in the circumference of a pipe. The default value is8The more sections there are, the rounder the pipe will look.
  • closed: If the property is set totrue, the head and tail of the pipe are concatenated. The default value isfalse.

Code sample

// ...
var controls = new function () {
  // Point position coordinates
  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);
}
// ...
Copy the code

😊 barely successful: but the circle connected by the pipe is not round enough, to achieve a perfect arc needs accurate coordinates, temporarily did not find the coordinate calculation method.

Blender + three.js

Although the use of THREE.TubeGeometry can be barely achieved, but the effect is not good, to achieve a smooth ring, it is necessary to add an accurate twisted ring curve path function for the pipeline. Due to my limited mathematical ability 🤕️, I have not found the method to calculate the twisted arc path. So we decided to solve it from the modeling level.

Success 😄 : But I spent a lot of time modeling in Blender with my hands crippled 💔.

Modeling tutorial

Strolling B station found this big man hair treasure video, just solved their own problem.

🎦 Portal: How to play After Effects + Blender? Facebook Meta Universe Meta Dynamic logo has been fully resolved, 100% learned

Using Blender modeling

Model in Blender and export the portable animation in FBX format without forgetting to check the Bake Animation option.

Load depends on

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

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);
  / / the scene
  scene = new THREE.Scene();
  scene.transparent = true;
  scene.fog = new THREE.Fog(0xa0a0a0.200.1000);
  // Perspective camera: field of view, aspect ratio, near side, far side
  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));
  // Hemisphere light source: Create a more natural outdoor light source
  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);
  / / the 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 the 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);
}
Copy the code

📌 To learn more about the process of scene initialization, read my article “Implementing cool acid-style 3D pages with Three.js”.

Loading the Logo Model

Load the model using FBXLoader 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);
});
Copy the code

Add material

The Logo used in this paper is MeshPhysicalMaterial, which is a PBR physical material, which can better simulate lighting calculation. Compared with the high-light mesh material MeshPhongMaterial, the rendering effect is more realistic. Use three.TextureLoader to add the map property to the material to load the model map. Below is a metallic texture map.

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

Add animation

  • AnimationMixerAn object is an animation player for a specific object in the scene. When multiple objects in a scene are animated independently, you can use one for each objectAnimationMixer.
  • AnimationMixerThe object’sclipActionMethod to generate instances that control the execution of the animation.
loader.load('assets/models/meta.fbx'.function (mesh) {
  mesh.animations.map(item= > {
    mesh.traverse(child= > {
      // Since there are multiple objects in the model and each has a different animation, the example only animates the mesh of Bezier circles
      if (child.name === 'Bezier circle') {
        let mixer = new THREE.AnimationMixer(child);
        mixerArr.push(mixer);
        let animationClip = item;
        animationClip.duration = 8;
        letclipAction = mixer.clipAction(animationClip).play(); animationClip = clipAction.getClip(); }})})});Copy the code

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

function animate() {
  renderer.render(scene, camera);
  // Get the interval between executing the method before and after
  let time = clock.getDelta();
  // Update the logo animation
  mixerArr.map(mixer= > {
    mixer && mixer.update(time);
  });
  // Update the character animation
  manMixer && manMixer.update(time);
  stats.update();
  requestAnimationFrame(animate);
}
Copy the code

Show the loading progress

FBXLoader returns two callback functions at the same time, which can be used as follows to show the model loader presentation and the logical implementation of the load failure.

<div class="loading" id="loading">
  <p class="text">Loading schedule<span id="progress">0%</span></p>
<div>
Copy the code
var loader = new THREE.FBXLoader();
loader.load('assets/models/meta.fbx'.mesh= >{},res= > {
  // Load the 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= > {
  // Load failed
  console.log(err)
});
Copy the code

Implementation effect

Click change material

Listening page click event, through HREE. Raycaster get current click the object, in order to show, for example, I click on the object to replace a material. THREE MeshStandardMaterial, Give it a random color, metalness, and roughness.

Declare rayCaster and Mouse variables
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
  // Calculate the location of the raycaster points from the mouse click position, starting at the center of the screen, with values ranging from -1 to 1.
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  // Calculate the RayCaster from the mouse position and the current camera matrix
  raycaster.setFromCamera(mouse, camera);
  // Get the array of rayCaster lines that 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: ` #The ${Math.random().toString(16).slice(-6)}`.metalness: Math.random(),
      roughness: Math.random()
    })
  }
}
window.addEventListener('click', onMouseClick, false);
Copy the code

For more information about grid materials, see the link at the end of this article at 📌.

Loading character model

The loading process for the character model is the same as the loading process for the Logo model. I added a character who is performing turtle school Qigong, but it is very suitable for the rotating animation of 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 + The '%';
  if (Number(progress) === 100) {
    document.getElementById('loading').style.display = 'none'; }},err= > {
  console.log(err)
});
Copy the code

The sample character models for this article come from Mixamo.com, which has hundreds of characters and thousands of actions that can be freely combined and downloaded for free. You can choose your favorite characters and animations to practice three.js.

conclusion

The main knowledge points involved in this article include:

  • THREE.TorusGeometry: the ring.
  • THREE.TorusKnotGeometry: Torus kink.
  • THREE.TubeGeometry: the pipe.
  • Blender: modeling.
  • FBXLoader: Loads the model and displays the loading progress.
  • TextureLoader: Loads the material.
  • THREE.AnimationMixer: Loads the animation.
  • THREE.Raycaster: Capture click model.

🔗 Full code: github.com/dragonir/3d…

The resources

  • [1]. Use three.js to create cool acid style 3D pages
  • [2]. ThreeJs recognizes materials
  • [3]. Initial impression of Three Animation
  • [4]. What is the meta-universe?