“This is the second day of my participation in the First Challenge 2022, for more details: First Challenge 2022”.

PK creative Spring Festival, I am participating in the “Spring Festival creative submission contest”, please see: Spring Festival creative submission Contest

background

Welcome the Winter Olympics, together to the future! The 2022 Winter Olympics will start soon. This paper uses three.js + React technology stack to realize winter and Olympic elements and create a 3D page with the theme of winter Olympics full of fun and commemorative significance. The knowledge points involved in this paper mainly include: TorusGeometry Torus, MeshLambertMaterial Non-luster Surface Material, MeshDepthMaterial Depth Mesh Material, custromMaterial custom material, Points particles, PointsMaterial Point material and so on.

The effect

The realization effect is shown in the following 👇 GIF. The page is mainly composed of 2022 Winter Olympic Games mascot Bing Dwen Dwen, Olympic five rings, dancing flag 🚩, trees 🌲 and snow effect ❄️. Press and hold the left mouse button to move the camera to get different views.

👀 online preview: dragonir.github. IO /3d/#/olympi… (Deployed on GitHub, may be a bit slow to load 😓)

implementation

The introduction of resources

The libraries and external resources needed to develop the page are introduced first, OrbitControls for lens track control, TWEEN for TWEEN animation implementation, GLTFLoader for loading 3D models in GLB or GLTF format, and some other models, textures, and other resources.

import React from 'react';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { TWEEN } from "three/examples/jsm/libs/tween.module.min.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import bingdundunModel from './models/bingdundun.glb';
// ...
Copy the code

Page DOM structure

The page DOM structure is very simple, just the #container container that renders 3D elements and the.olympic_loading element that shows the loading progress.

<div>
  <div id="container"></div>
  {this.state.loadingProcess === 100 ? ' ' : (
    <div className="olympic_loading">
      <div className="box">{this.state.loadingProcess} %</div>
    </div>
  )}
</div>
Copy the code

Scene initialization

Initialize render containers, scenes, and cameras. For details about this part of the content, you can refer to my previous article, this article will not repeat.

container = document.getElementById('container');
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
scene = new THREE.Scene();
scene.background = new THREE.TextureLoader().load(skyTexture);
camera = new THREE.PerspectiveCamera(60.window.innerWidth / window.innerHeight, 0.1.1000);
camera.position.set(0.30.100);
camera.lookAt(new THREE.Vector3(0.0.0));
Copy the code

Add a light source

In this example, two main light sources have been added: DirectionalLight for creating shadows, adjusting page brightness, and AmbientLight for rendering the ambient atmosphere.

/ / direct light
const light = new THREE.DirectionalLight(0xffffff.1);
light.intensity = 1;
light.position.set(16.16.8);
light.castShadow = true;
light.shadow.mapSize.width = 512 * 12;
light.shadow.mapSize.height = 512 * 12;
light.shadow.camera.top = 40;
light.shadow.camera.bottom = -40;
light.shadow.camera.left = -40;
light.shadow.camera.right = 40;
scene.add(light);
/ / the ambient light
const ambientLight = new THREE.AmbientLight(0xcfffff);
ambientLight.intensity = 1;
scene.add(ambientLight);
Copy the code

Load Progress Management

Use THREE.LoadingManager to manage the page model load progress and perform some methods related to the load progress in its callback function. The page loading progress in this example is done in onProgress. When the page loading progress reaches 100%, the TWEEN shot TWEEN animation is performed.

const manager = new THREE.LoadingManager();
manager.onStart = (url, loaded, total) = > {};
manager.onLoad = () = > { console.log('Loading complete! ')};
manager.onProgress = (url, loaded, total) = > {
  if (Math.floor(loaded / total * 100) = = =100) {
    this.setState({ loadingProcess: Math.floor(loaded / total * 100)});// Camera tween animation
    Animations.animateCamera(camera, controls, { x: 0.y: -1.z: 20 }, { x: 0.y: 0.z: 0 }, 3600.() = > {});
  } else {
    this.setState({ loadingProcess: Math.floor(loaded / total * 100)}); }};Copy the code

Create the ground

The bumpy ground in this example was modeled using Blender and then exported to GLB format to load. Of course, you can also achieve a similar effect by just using the Three. Js flat mesh and bump map. The advantage of using your own model in Blender is that you can freely and visually adjust the rise and fall of the ground.

var loader = new THREE.GLTFLoader(manager);
loader.load(landModel, function (mesh) {
  mesh.scene.traverse(function (child) {
    if (child.isMesh) {
      child.material.metalness = 1.;
      child.material.roughness = 8.;
      / / the ground
      if (child.name === 'Mesh_2') {
        child.material.metalness = . 5;
        child.receiveShadow = true; }}); mesh.scene.rotation.y =Math.PI / 4;
  mesh.scene.position.set(15, -20.0);
  mesh.scene.scale.set(9..9..9.);
  land = mesh.scene;
  scene.add(land);
});
Copy the code

Create the Winter Olympics mascot, Bing Dwen Dwen

Now add the lovely Winter Olympics mascot panda Bing Dwen Dwen 🐼, bing Dwen Dwen is also loaded using GLB format model. The original model came from here, which is free from this website. Now the original model was built using 3D Max and I found that it can’t be used directly on the web. I need to convert the model format in Blender and adjust the map normals of the model to restore the rendering effect.

The original model:

Ice block map:

Convert to a model supported by Blender and adjust the model map normals and add maps in Blender:

Export to GLB format:

📖 Adding texture to models in Blender Tutorial Portal: How to Map models in Blender

A closer look at Ice Dun 🐼 shows that it has a transparent plastic or glass texture shell on the outside. This effect can be achieved by modifying the transparency, metallicity, roughness and other material parameters of the model. Finally, the effect as shown in 👆 banner can be rendered, as shown in the code below.

loader.load(bingdundunModel, mesh= > {
  mesh.scene.traverse(child= > {
    if (child.isMesh) {
      / / internal
      if (child.name === 'oldtiger001') {
        child.material.metalness = . 5
        child.material.roughness = 8.
      }
      // Translucent shell
      if (child.name === 'oldtiger002') {
        child.material.transparent = true;
        child.material.opacity = . 5
        child.material.metalness = 2.
        child.material.roughness = 0
        child.material.refractionRatio = 1
        child.castShadow = true; }}}); mesh.scene.rotation.y =Math.PI / 24;
  mesh.scene.position.set(-8, -12.0);
  mesh.scene.scale.set(24.24.24);
  scene.add(mesh.scene);
});
Copy the code

Create the Olympic rings

The five Olympic rings are realized by the basic geometric model TorusGeometry, which creates five torus and adjusts their material colors and positions to form a blue, black, red, yellow and green five-ring structure. The fifth ring material is MeshLambertMaterial.

const fiveCycles = [
  { key: 'cycle_0'.color: 0x0885c2.position: { x: -250.y: 0.z: 0 }},
  { key: 'cycle_1'.color: 0x000000.position: { x: -10.y: 0.z: 5 }},
  { key: 'cycle_2'.color: 0xed334e.position: { x: 230.y: 0.z: 0 }},
  { key: 'cycle_3'.color: 0xfbb132.position: { x: -125.y: -100.z: -5 }},
  { key: 'cycle_4'.color: 0x1c8b3c.position: { x: 115.y: -100.z: 10}}]; fiveCycles.map(item= > {
  let cycleMesh = new THREE.Mesh(new THREE.TorusGeometry(100.10.10.50), new THREE.MeshLambertMaterial({
    color: new THREE.Color(item.color),
    side: THREE.DoubleSide
  }));
  cycleMesh.castShadow = true;
  cycleMesh.position.set(item.position.x, item.position.y, item.position.z);
  meshes.push(cycleMesh);
  fiveCyclesGroup.add(cycleMesh);
});
fiveCyclesGroup.scale.set(036..036..036.);
fiveCyclesGroup.position.set(0.10, -8);
scene.add(fiveCyclesGroup);
Copy the code

💡TorusGeometry toroid

TorusGeometry A class for generating ring geometry.

Constructor:

TorusGeometry(radius: Float, tube: Float, radialSegments: Integer, tubularSegments: Integer, arc: Float)
Copy the code
  • radius: Radius of the ring, from the center of the ring to the center of the pipe (cross section). The default value is1.
  • tube: Radius of the pipe. The default value is0.4.
  • radialSegments: Indicates the number of segments of a ring. The default value is8.
  • tubularSegments: The number of segments for a pipe. The default value is6.
  • arc: Central Angle of a ring, in radians. The default value isMath.PI * 2.

💡MeshLambertMaterial Non gloss surface material

A non – shiny surface material without specular highlights. The material uses a non-physical Lambertian model to calculate reflectivity. This mimics some surfaces well (such as untreated wood or stone), but not glossy surfaces with specular highlights (such as painted wood).

Constructor:

MeshLambertMaterial(parameters : Object)
Copy the code
  • parameters(Optional) An object that defines the appearance of a material and has one or more properties. Any attributes of the material can be passed in from here.

Create a banner

The flag model was downloaded from Sketchfab, and you need a flag pole. You can add a column cube to Blender and adjust the length, width and height to fit the flag.

Flag map:

The flag adds an animation that needs to be performed in the code to play the animation frame.

loader.load(flagModel, mesh= > {
  mesh.scene.traverse(child= > {
    if (child.isMesh) {
      child.castShadow = true;
      / / the flag
      if (child.name === 'mesh_0001') {
        child.material.metalness = 1.;
        child.material.roughness = 1.;
        child.material.map = new THREE.TextureLoader().load(flagTexture);
      }
      / / the pole
      if (child.name === 'cylinder') {
        child.material.metalness = 6.;
        child.material.roughness = 0;
        child.material.refractionRatio = 1;
        child.material.color = new THREE.Color(0xeeeeee); }}}); mesh.scene.rotation.y =Math.PI / 24;
  mesh.scene.position.set(2, -7, -1);
  mesh.scene.scale.set(4.4.4);
  / / animation
  let meshAnimation = mesh.animations[0];
  mixer = new THREE.AnimationMixer(mesh.scene);
  let animationClip = meshAnimation;
  let clipAction = mixer.clipAction(animationClip).play();
  animationClip = clipAction.getClip();
  scene.add(mesh.scene);
});
Copy the code

Create trees

In order to enrich the picture and create a wintry atmosphere, several pine trees 🌲 were added as decorations. It is important to use a trick when adding pine trees: we know that because the tree model is very complex and has a very large number of faces, too many faces can slow down page performance and cause lag. In this article, we use two intersecting faces as the base of the tree, as shown in 👇 below, so that the tree has only two faces. Using this technique can greatly optimize the page performance, and the tree 🌲 also looks 3D.

Texture map:

In order to make the tree transparent only in the transparent part of the map and opaque elsewhere, and to create tree shadows instead of rectangular shadows, we need to add the following two materials to the tree model: MeshPhysicalMaterial and MeshDepthMaterial. Both materials use the same texture map. Where MeshDepthMaterial is added to the custromMaterial property of the model.

 let treeMaterial = new THREE.MeshPhysicalMaterial({
  map: new THREE.TextureLoader().load(treeTexture),
  transparent: true.side: THREE.DoubleSide,
  metalness: 2..roughness: 8..depthTest: true.depthWrite: false.skinning: false.fog: false.reflectivity: 0.1.refractionRatio: 0});let treeCustomDepthMaterial = new THREE.MeshDepthMaterial({
  depthPacking: THREE.RGBADepthPacking,
  map: new THREE.TextureLoader().load(treeTexture),
  alphaTest: 0.5
});
loader.load(treeModel, mesh= > {
  mesh.scene.traverse(child= >{
    if(child.isMesh) { child.material = treeMaterial; child.custromMaterial = treeCustomDepthMaterial; }}); mesh.scene.position.set(14, -9.0);
  mesh.scene.scale.set(16.16.16);
  scene.add(mesh.scene);
  // Clone two other trees
  let tree2 = mesh.scene.clone();
  tree2.position.set(10, -8, -15);
  tree2.scale.set(18.18.18);
  scene.add(tree2)
  // ...
});
Copy the code

The effect can also be seen in the Banner image above 👆. I removed the tree shadow display to make the image look better.

📌 In 3D functional development, some non-important decorative models can adopt this strategy to optimize.

💡MeshDepthMaterial Depth mesh material

A material that draws geometry by depth. Depth is based on the camera’s near and far plane, with white nearest and black farthest.

Constructor:

MeshDepthMaterial(parameters: Object)
Copy the code
  • parameters(Optional) An object that defines the appearance of a material and has one or more properties. Any attributes of the material can be passed in from here.

Special attributes:

  • .depthPacking[Constant]:depth packingCoding. The default isBasicDepthPacking.
  • .displacementMap[Texture]Displacement maps affect the position of vertices in the mesh. Unlike other maps, which only affect the lighting and shadows of the material, displaced vertices can cast shadows, block other objects, and act as real geometry.
  • .displacementScale[Float]: The effect of displacement map on mesh (black is no displacement, white is maximum displacement). If no displacement map is set, this value is not applied. The default value is1.
  • .displacementBias[Float]: Displacement Map offset at mesh vertices. If no displacement map is set, this value is not applied. The default value is0.

💡CustromMaterial Custom material

Add the custromMaterial custom material attribute to the mesh to shadow the content area of the transparent outer PNG image map.

Create snow

To create snowflakes ❄️, we need to use particle knowledge. THREE.Points is a class for creating Points and also for batch managing particles. In this example, 1,500 snowflake particles are created and assigned random coordinates in three dimensional space and random movement speeds in horizontal and vertical directions.

// Snowflake map
let texture = new THREE.TextureLoader().load(snowTexture);
let geometry = new THREE.Geometry();
let range = 100;
let pointsMaterial = new THREE.PointsMaterial({
  size: 1.transparent: true.opacity: 0.8.map: texture,
  // Background fusion
  blending: THREE.AdditiveBlending,
  // Depth-of-field fades
  sizeAttenuation: true.depthTest: false
});
for (let i = 0; i < 1500; i++) {
  let vertice = new THREE.Vector3(Math.random() * range - range / 2.Math.random() * range * 1.5.Math.random() * range - range / 2);
  // Longitudinal speed
  vertice.velocityY = 0.1 + Math.random() / 3;
  // Lateral speed
  vertice.velocityX = (Math.random() - 0.5) / 3;
  // Add to geometry
  geometry.vertices.push(vertice);
}
geometry.center();
points = new THREE.Points(geometry, pointsMaterial);
points.position.y = -30;
scene.add(points);
Copy the code

💡Points particles

In three. js, common particles in life such as rain 🌧️, snow ❄️, cloud ☁️ and star ️ can be simulated using Points.

Constructor:

new THREE.Points(geometry, material);
Copy the code
  • The constructor can take two parameters, a geometry and a material. The geometry parameter is used to specify the position of the particle, and the material parameter is used to format the particle.
  • Can be based on simple geometry objects such asBoxGeometry,SphereGeometryEtc as parameters of the particle system;
  • In general, you need to specify vertices to determine the positions of particles.

💡PointsMaterial point material

Using three. PointsMaterial you can set the particle property parameter, which is the default material used by Points.

Constructor:

PointsMaterial(parameters : Object)
Copy the code
  • parameters(Optional) An object that defines the appearance of a material and has one or more properties. Any attributes of the material can be passed in from here.

💡Material properties. Blending

The. Blending theory attribute mainly controls the stacking mode of texture blending. The values of the. Blending theory attribute include:

  • THREE.NormalBlendingDefault value:
  • THREE.AdditiveBlending: Add fusion mode
  • THREE.SubtractiveBlending: Subtraction fusion mode
  • THREE.MultiplyBlending: Multiplication fusion mode
  • THREE.CustomBlending: Custom fusion mode, and.blendSrc..blendDst.blendEquationAttribute combination

💡Material property.sizeProfile

Whether particle size is attenuated by camera depth. Default is true (perspective camera only).

💡Three. Js vector

The multi-dimensional vector has several components, the two-dimensional vector Vector2 has x and Y components, the three-dimensional vector Vector3 has X, Y and Z components, and the four-dimensional vector Vector4 has X, Y, Z and W components.

The API:

  • Vector2: two-dimensional vector
  • Vector3: three-dimensional vector
  • Vector4: four dimensional vector

Camera control, zoom adaptation, animation

controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0.0.0);
controls.enableDamping = true;
// Disable panning
controls.enablePan = false;
// Disable scaling
controls.enableZoom = false;
// Vertical rotation Angle limit
controls.minPolarAngle = 1.4;
controls.maxPolarAngle = 1.8;
// Horizontal rotation Angle limit
controls.minAzimuthAngle = -6.;
controls.maxAzimuthAngle = 6.;
Copy the code
window.addEventListener('resize'.() = > {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
Copy the code
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
  controls && controls.update();
  // Flag animation update
  mixer && mixer.update(new THREE.Clock().getDelta());
  // Camera animation
  TWEEN && TWEEN.update();
  // Rotation of the five rings
  fiveCyclesGroup && (fiveCyclesGroup.rotation.y += .01);
  // The vertex changes need to be updated, otherwise raindrop effects cannot be implemented
  points.geometry.verticesNeedUpdate = true;
  // Snowflake animation update
  let vertices = points.geometry.vertices;
  vertices.forEach(function (v) {
    v.y = v.y - (v.velocityY);
    v.x = v.x - (v.velocityX);
    if (v.y <= 0) v.y = 60;
    if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1;
  });
}
Copy the code

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

conclusion

💡 The main new knowledge points included in this article include:

  • TorusGeometrytori
  • MeshLambertMaterialNon-shiny surface material
  • MeshDepthMaterialDepth mesh material
  • custromMaterialCustom material
  • PointsThe particle
  • PointsMaterialSome material
  • The material properties.blending,.sizeAttenuation
  • Three.jsvector

Further optimized space:

  • Add more interactive functions and further optimize the interface style;
  • The mascot Ice Dwen Dwen adds a skeleton animation and can control its movement and interaction with mouse and keyboard.

Next period:

  • MetahumanYuan human!Three.jsPortrait Optimization –

To learn more about scene initialization, lighting, shadows, base geometry, meshes, materials, and more about three.js, read my previous articles. If you think the article is helpful to you, don’t forget a key three link oh 👍.

The appendix

  • [1]. 1000 powder! Create an exclusive 3D medal using three.js 🥇
  • [2].three.js to achieve the Year of the Tiger Spring Festival 3D creative page
  • [3].three.js to implement the 3D dynamic Logo of facebook metasomes
  • [4].three.js to implement 3D panoramic detective game
  • [5].three.js to achieve cool acid style 3D pages
  • [6].3DX model conversion to Blender supported format