With the official opening of the winter Olympic Games two days ago, also successfully brought the fire of the mascot Ice Dwen Dwen. Simple and honest and lovely panda image, let ice dun dun entity doll, key chain were snatched up, many netizens call now really is “a pier hard to find”!

In order to realize everyone’s dream of “a pier”, a domestic programmer Dragonir, with front-end + modeling technology to achieve an ice pier pier, and open source code to GitHub.

Let’s look at the details of the specific technology implementation.

The original address: segmentfault.com/a/119000004…

background

This paper uses three.js + React technology stack to realize winter and Olympic elements, and makes a 3D page with the theme of winter Olympics full of interest 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… 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.

// 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); // 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) }); 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; 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:

The original model was built using 3D Max for free from this website and I found that it cannot be directly used in web pages. 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:

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) {// inner if (child.name === 'oldtiger001') { Child.material. Metalness =.5 child.material. Roughness =.8} // 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 torus

TorusGeometry A class for generating ring geometry.

Constructor:

TorusGeometry(radius: Float, tube: Float, radialSegments: Integer, tubularSegments: Integer, arc: Float)

Copy the code
  • Radius: indicates the radius of the ring from the center of the ring to the center of the pipe (cross section). The default value is 1.

  • Tube: The radius of the pipe. The default value is 0.4.

  • RadialSegments: indicates the number of segments of a ring. The default value is 8.

  • TubularSegments: number of pipeline segments. The default value is 6.

  • Arc: Central Angle of a ring (in radians). The default value is math.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; If (child.name === 'mesh_0001') {child.material. Metalness =.1; child.material.roughness = .1; child.material.map = new THREE.TextureLoader().load(flagTexture); } if (child.name === '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); 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); 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 Deep 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 packing code. The default is BasicDepthPacking.

  • DisplacementMap [Texture] : the displacementMap affects the position of the vertices of the mesh. Unlike other maps, which only affect the light and shadows of the material, the displaced vertices can cast shadows, block other objects, and act as real geometry.

  • .displacementScale[Float] : The extent to which the displacement map affects the mesh (black is no displacement, white is maximum displacement). If no displacement map is set, this value is not applied. The default value is 1.

  • DisplacementBias [Float] : The offset of the displacement map on the vertices of the grid. If no displacement map is set, this value is not applied. The default value is 0.

💡 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.

// Let texture = new three.textureLoader ().load(snowTexture); let geometry = new THREE.Geometry(); let range = 100; Opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0 Texture, // Background blending blending: THREE.AdditiveBlending blending: 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); // Vertice.velocityy = 0.1 + math.random () / 3; // Vertice.velocityx = (math.random () -0.5) / 3; // Add 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.

  • The parameters of particle system can be based on simple geometry objects such as BoxGeometry and heregeometry.

  • 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.

💡 Texture properties. Blending

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

  • THREE.NormalBlending: Default

  • AdditiveBlending: Add blending mode

  • THREE. SubtractiveBlending: subtraction fusion model

  • MultiplyBlending: MultiplyBlending mode

  • CustomBlending mode: Blending with. BlendSrc,. BlendDst, or. BlendEquation properties

💡 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: A two-dimensional vector

  • Vector3: A three-dimensional vector

  • Vector4: a four-dimensional vector

Camera control, zoom adaptation, animation

controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.enableDamping = true; // Disable pan controls. EnablePan = false; // Disable zoom controls. EnableZoom = false; // Controls. MinPolarAngle = 1.4; Controls. MaxPolarAngle = 1.8; // Controls. MinAzimuthAngle = -.6; controls.maxAzimuthAngle = .6; window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }, false); function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); controls && controls.update(); Mixer.update (new three.clock ().getdelta ()); // TWEEN && TWEEN. Update (); / / the rings rotation fiveCyclesGroup && (fiveCyclesGroup. Rotation. Y + =. 01); / / vertex after changes need to be updated, otherwise unable to realize rain effects points. The geometry. VerticesNeedUpdate = true; // 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:

  • TorusGeometry toroid

  • MeshLambertMaterial Non gloss surface material

  • MeshDepthMaterial Depth mesh material

  • CustromMaterial Custom material

  • Points particles

  • PointsMaterial point material

  • Material properties.blending,.sizeprofile

  • Three. Js vector

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.

Click “Read the original” to view the website directly, 3D visualization students can also look at GitHub source code to learn.

Welcome to visit GitHub and recommend a fun and interesting open source project every day. GitHub project:

Source address: https://github.com/Wechat-ggGitHub/Awesome-GitHub-RepoCopy the code

Recommended reading

1. What are the fun projects on GitHub?

2. Recommend a NetEase cloud player with high appearance level

3. Baidu Cloud High imitation project based on Spring Boot

4. Check baidu’s 4 awesome open source projects