Hello, I'm dialogue.
With the Bing dwen dwen's opening of the first two days of Winter Olympic Games, the mascot ice pier was also successfully carried out. Bing dwen dwen's cute panda image has been robbed of the ice cube pier solid doll and key chain. Many netizens call for the "hard to get" now.
Bing dwen dwen, a domestic programmer dragonir, built a ice pier with front-end + modeling technology, and opened the code to GitHub.
Let's take a look at the specific technical implementation details.
Original address: https://segmentfault.com/a/1190000041363089
background
This article uses {three JS + react # technology stack realizes winter and Olympic elements, and makes a Winter Olympic theme # 3D # page full of interest and commemorative significance.
The knowledge Points involved in this paper mainly include TorusGeometry ^ torus, MeshLambertMaterial ^ non glossy surface material, MeshDepthMaterial ^ depth mesh material, custromMaterial ^ custom material, Points ^ particles, PointsMaterial ^ point material, etc.
effect
The effect is as follows: 👇 Bing dwen dwen shows that the page is mainly composed of 2022 Winter Olympic mascots, ice pier, Olympic rings and dancing flags. 🚩, Trees 🌲 And snow effect ❄️ Etc.
Hold down the left mouse button and move to change the camera position to obtain different views.
👀 Online preview: https://dragonir.github.io/3d/#/olympic (deployed in GitHub, the loading speed may be a little slow 😓)
realization
Introduce resources
Firstly, the library and external resources required for developing the page are introduced. OrbitControls is used for lens track control, TWEEN is used for making up animation implementation, GLTFLoader is used to load 3D models in glb or gltf format, as well as some other models, maps 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'; // ...
Page DOM structure
The "DOM" structure of the page is very simple, with only the #container "container that renders the" 3D "element and the container that displays the loading progress olympic_loading element.
<div> <div id="container"></div> {this.state.loadingProcess === 100 ? '' : ( <div className="olympic_loading"> <div className="box">{this.state.loadingProcess} %</div> </div> )} </div>
Scene initialization
Initialize render container, scene, camera.
For the detailed knowledge of this part, you can refer to my previous articles, which will not be repeated in this article.
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));
Add light
In this example, two kinds of light sources are mainly added: DirectionalLight is used to generate shadows, adjust the brightness of the page, and AmbientLight is used to render the environment 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); //Ambient light const ambientLight = new THREE.AmbientLight(0xcfffff); ambientLight.intensity = 1; scene.add(ambientLight);
Loading schedule management
Use # three Loadingmanager manages the loading progress of the page model and executes some methods related to the loading progress in its callback function.
The page loading progress in this example is completed in "onProgress". When the page loading progress is "100%, execute the" TWEEN "shot gap animation.
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) }); //Shot gap 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) }); } };
Create ground
In this example, the bumpy ground is created by building the model with , Blender , and then loading in , glb , format.
Of course, you can only use {three JS , its own plane mesh plus bump map can also achieve a similar effect.
The advantage of using "Blender" self built model is that it can freely and visually adjust the fluctuation effect 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; //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); });
Bing dwen dwen Winter Olympic mascot
Now add the Bing dwen dwen Winter Olympic mascot. 🐼, Bing dwen dwen is also loaded by glb format.
Its original model comes from here:
After the current model is free from this website, the original model is built using , 3D max , and I found that it can not be directly used in the web page. I need to convert the model format in , Blender , and adjust the map normal of the model to restore the rendering effect.
Original model:
Map of Bing dwen dwen:
Convert to the model supported by Blender, adjust the model map normals in Blender, and add the map:
Export glb format:
Bing dwen dwen 🐼 It can be found that there is a layer of transparent plastic or glass shell outside it. This effect can be achieved by modifying the transparency, metallicity, roughness and other material parameters of the model. Finally, it can be rendered as 👆 The effect shown in the banner diagram is shown in the following code.
loader.load(bingdundunModel, mesh => { mesh.scene.traverse(child => { if (child.isMesh) { //Inside 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); });
Olympic rings
The five Olympic rings are realized by TorusGeometry, a basic geometric model. Five circular rings are created and their material, color and position are adjusted to form a five ring structure in the order of blue, black, red, yellow and green. The five rings 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);
💡 TorusGeometry torus
TorusGeometry is a class used to generate torus geometry.
Constructor:
TorusGeometry(radius: Float, tube: Float, radialSegments: Integer, tubularSegments: Integer, arc: Float)
- Radius: 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: the number of segments of the torus. The default value is {8.
- tubularSegments: the number of pipe segments. The default value is {6.
- arc: the center angle of the ring (in radians). The default value is , math PI * 2.
💡 MeshLambertMaterial matte surface material
A non glossy surface material without specular highlights.
The material uses a non physical based Lambertian model to calculate reflectivity. This can well simulate some surfaces (such as untreated wood or stone), but it cannot simulate shiny surfaces with specular highlights (such as painted wood).
Constructor:
MeshLambertMaterial(parameters : Object)
- parameters: (optional) an object that defines the appearance of a material, with one or more attributes. Any property of the material can be passed in from here.
Create flag
The flag surface model is downloaded from sketchfab and needs a flag pole. You can add a cylindrical cube in Blender and adjust the appropriate length, width and height to combine with the flag surface.
Flag map:
Animation has been added to the flag surface, and the animation frame playback needs to be performed in the code.
loader.load(flagModel, mesh => { mesh.scene.traverse(child => { if (child.isMesh) { child.castShadow = true; //Flag if (child.name === 'mesh_0001') { child.material.metalness = .1; child.material.roughness = .1; child.material.map = new THREE.TextureLoader().load(flagTexture); } //Flagpole 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); });
Create trees
In order to enrich the picture and create a winter atmosphere, several pine trees were added 🌲 As a decoration.
A skill is very important when adding pine trees: we know that because the tree model is very complex and has a lot of faces, too many faces will reduce the page performance and cause jamming.
Two are used in this article, as shown in the following figure 👇 The two intersecting faces shown are used as the base of the tree. In this way, the tree has only two faces. Using this technique can greatly optimize the page performance, and the tree 🌲 It also looks like a 3D} sense.
Texture mapping:
In order to make the tree transparent only in the transparent part of the map and opaque in other places, and can produce tree shadow instead of box shadow, you need to add the following two materials to the tree model: MeshPhysicalMaterial and MeshDepthMaterial. The two materials use the same texture map, in which MeshDepthMaterial is added to the "custromMaterial" attribute 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 the other two trees let tree2 = mesh.scene.clone(); tree2.position.set(10, -8, -15); tree2.scale.set(18, 18, 18); scene.add(tree2) // ... });
The effect can also be achieved from 👆 As can be seen from the "Banner" picture above, I canceled the shadow display of the tree in order to make the picture better.
📌 In the development of # 3D # function, some unimportant decoration models can adopt this strategy to optimize.
💡 MeshDepthMaterial depth mesh material
A material that draws geometry by depth. The depth is based on the near and far plane of the camera. White is the closest and black is the farthest. In addition, search for official account technology community background reply "wallpaper" to get a surprise gift package.
Constructor:
MeshDepthMaterial(parameters: Object)
- parameters: (optional) an object that defines the appearance of a material, with one or more attributes. Any property of the material can be passed in from here.
Special properties:
- . depthPacking[Constant]: the code of depth packing #. The default is {BasicDepthPacking.
- . displacementMap[Texture]: displacement maps affect the position of mesh vertices. Unlike other maps that only affect the lighting and shadows of materials, displaced vertices can cast shadows, block other objects, and act as real geometry.
- . displacementScale[Float]: the degree of influence of the displacement map on the mesh (black is no displacement, white is the 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 mesh vertices. If no displacement map is set, this value is not applied. The default value is 0.
💡 custromMaterial custom material
Adding # custromMaterial # custom material attribute to the mesh can realize the shadow of the content area of # png # picture map.
Create snowflakes
Create snowflakes ❄️, You need particle knowledge. THREE.Points , is a class used to create points. It is also used to manage particles in batch. In this example, 1500 snowflake particles are created, and they are set with random coordinates defining three-dimensional space and horizontal and vertical random moving speeds.
//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 attenuation 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 velocity vertice.velocityY = 0.1 + Math.random() / 3; //Lateral velocity 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);
💡 Points particles
Three.js, rain 🌧️, Snow ❄️, Cloud ☁️, Stars ✨ And other common particles in life can be simulated and realized by using {Points}.
Constructor:
new THREE.Points(geometry, material);
- The constructor can accept two parameters, a geometry and a material. The geometry parameter is used to determine the position coordinates of particles, and the material parameter is used to format particles;
- Simple geometry objects such as BoxGeometry and sphereometry can be used as the parameters of the particle system;
- Generally speaking, you need to assign vertices to determine the position of particles.
💡 PointsMaterial point material
Through # three Pointsmaterial , which can set the attribute parameters of particles, is the default material used by , Points ,.
Constructor:
PointsMaterial(parameters : Object)
- parameters: (optional) an object that defines the appearance of a material, with one or more attributes. Any property of the material can be passed in from here.
💡 Material properties blending
Material Blending attribute mainly controls the superposition mode of texture fusion The values of the blending attribute include:
- THREE.NormalBlending: default
- THREE. Additive blending: additive blending mode
- THREE.SubtractiveBlending: subtractive blending mode
- THREE. Multiplicyblending: multiplicative fusion mode
- THREE.CustomBlending: custom blending mode, and blendSrc, . Blendst or blendEquation attribute combination
💡 Material properties sizeAttenuation
Whether the size of particles will be attenuated by the camera depth. The default is true (perspective cameras only).
💡 Three.js vector
Several dimensional vectors have several components. The two-dimensional vector # Vector2 # has two components # x # and # y # and the three-dimensional vector # Vector3 # has three components # x, y and z # and the four-dimensional vector # Vector4 # has four components # x, y, z and w #.
Related API:
- Vector2: 2D vector
- Vector3: 3D vector
- Vector4: four dimensional vector
Lens control, zoom adaptation, animation
controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.enableDamping = true; //Disable translation 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; 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(); //Flag animation update mixer && mixer.update(new THREE.Clock().getDelta()); //Lens animation TWEEN && TWEEN.update(); //Five ring rotation fiveCyclesGroup && (fiveCyclesGroup.rotation.y += .01); //After the vertex changes, it needs to be updated, otherwise the raindrop effect cannot be realized 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; }); }
summary
💡 The new knowledge points mainly included in this paper include:
- TorusGeometry
- MeshLambertMaterial # non gloss surface material
- MeshDepthMaterial = depth mesh material
- custromMaterial custom material
- Points: particles
- PointsMaterial: point material
- Material properties blending,. sizeAttenuation
- Three.js vector
Space for further optimization:
- Add more interactive functions and interface styles to further optimize;
- Mascot Bing dwen dwen adds skeletal animation, and controls its movement and interaction through mouse and keyboard.