Recently, I am interested in 3D visualization. I have learned that ThreeJS is a common method to achieve 3D visualization, so ON the basis of self-learning ThreeJS, I plan to write a computer room Demo to practice

Click here to preview the project GitHub

The directory structure

├ ─ ─ the font / / font file | ├ ─ ─ ─ ─ the font. / / the vera.ttf font source file | └ ─ ─ ─ ─ the font. The json / / font file after the conversion ├ ─ ─ img/images/materials | ├ ─ ─ ─ ─ xx. PNG | ├ ─ ─ ─ ─ XXX. JPG | └ ─ ─ ─ ─… ├ ─ ─ js / / write your own js file | ├ ─ ─ ─ ─ composer_fn. Js / / post processing | ├ ─ ─ ─ ─ create_fn. Js / / create various geometric | ├ ─ ─ ─ ─ init_fn. Js / / initialize the project | └ ─ ─ ─ ─ Util_fn. Js / / tools function ├ ─ ─ lib / / need to introduce the js file | ├ ─ ─ ─ ─ three. Js | ├ ─ ─ ─ ─ OrbitControls. Js | ├ ─ ─ ─ ─ RenderPass. Js | └ ─ ─ ─ ─… ├ ─ ─ model / / modeling tools export model | ├ ─ ─ ─ ─ computer. GLTF | └ ─ ─ ─ ─… └─ index.html // import file

Initialize Three: scene, camera, renderer

First we should initialize Three, get our camera and renderer ready, and set up the scene

  • Initialization Scenario
const scene = new THREE.Scene();
// Set the scene background, three types:
// 1. Plain background, one plane
scene.background = new THREE.Color("rgb(25, 35, 39)");
scene.background = new THREE.TextureLoader().load('img/back.jpg');

// 2. Cube background
scene.background = new THREE.CubeTextureLoader().setPath('img/').load(new Array(6).fill('back.jpg'));

// 3. Sphere panorama (background) image is achieved by setting up sphere and reverse magnification 100 times, where x magnification is negative
const geometry = new THREE.SphereGeometry(5.32.32);
const material = new THREE.MeshBasicMaterial({ map: new THREE.TextureLoader().load("img/back.jpg")});const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
geometry.scale(- 100.100.100);
Copy the code
  • Initializing the camera
const camera = new THREE.PerspectiveCamera(
  75.window.innerWidth / window.innerHeight,
  0.1.1000
);
camera.position.set(- 20.40.90); // Set the initial position of the camera
Copy the code
  • Initialize the renderer
const renderer = new THREE.WebGLRenderer({ alpha: true.antialias: true }); // Alpha: transparent background, antialias: anti-aliasing
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement); // Add to the body, or to any element
Copy the code
  • This is finally merged into a single function initThree in init_fn.js
// init_fn.js
function initThree(selector) {
  const scene = new THREE.Scene();
  scene.background = new THREE.Color("rgb(25, 35, 39)");
  
  const camera = new THREE.PerspectiveCamera(
    75.window.innerWidth / window.innerHeight,
    0.1.1000
  );
  camera.position.set(- 20.40.90);

  const renderer = new THREE.WebGLRenderer({ alpha: true.antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.querySelector(selector).appendChild(renderer.domElement);
  
  return { scene, camera, renderer };
}
Copy the code

Add track Controller

In order to increase user interaction, we need to add a controller that can control the size of the model through the zoom wheel, the left mouse button rotation, and the right mouse button pan

  • Start by importing the required files in index.html
<! -- index.html -->
<script src="lib/OrbitControls.js"></script>
Copy the code
  • Then create the track controller
// init_fn.js
function initControls() {
  const controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.addEventListener('change'.function () {... });// Add events
  return controls;
}
Copy the code

Add the Stats

Stats monitors FPS changes in real time to monitor the performance of rendered scenes

  • Start by importing the required files in index.html
<! -- index.html -->
<script src="lib/stats.min.js"></script>
Copy the code
  • And then create Stats
// init_fn.js
function initStats() {
  const stats = new Stats();
  document.body.appendChild(stats.dom);
  return stats;
}
Copy the code

Initialize light

Lights are used to color objects, objects without lights will be dark, we usually add a natural light first to make sure everything is represented, and then add any lights as needed

// init_fn.js
function initLight() {
  const ambientLight = new THREE.AmbientLight(0xffffff); // Natural light, light on every surface of every geometry
  const pointLight = new THREE.PointLight(0xff0000.10); / / the point light source
  pointLight.position.set(0.50.0); // Adjust the position of point light source
  scene.add(ambientLight);
  scene.add(pointLight);
  return [ambientLight, pointLight];
}
Copy the code

Writing entry files

Write index. HTML and introduce the init_fn.js function we wrote earlier to initialize Three


      
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Three-Demo</title>
    <style type="text/css">
      body {
        margin: 0;
      }

      #canvas-frame {
        border: none;
        background-color: #eeeeee; /* Sets the background color */
      }
    </style>
  </head>

  <body>
    <div id="canvas-frame"></div>
    
	<! -- Introduce ThreeJS -->
    <script src="lib/three.js"></script>
    <! -- Add controller -->
    <script src="lib/OrbitControls.js"></script>
    <! -- introducing FPS -->
    <script src="lib/stats.min.js"></script>
    <! -- self-wrapped function -->
    <script src="js/init_fn.js"></script>
    
    <script>
      / / initialization
      const { scene, camera, renderer } = initThree(
        "#canvas-frame"
      );
      const lights = initLight();
      const controls = initControls();
      const stats = initStats();
      // camera.lookAt(10, 10, 10);

	  function animate(time) {
        stats.update(); // After initializing stats, you need to execute the update method here to implement real-time FPS monitoring
        renderer.render(scene, camera); // The scene needs to be rendered, without which nothing will be displayed
        requestAnimationFrame(animate); // Use the browser API -- requestAnimationFrame, each frame is rendered, renderer.render(...) methods
      }
      animate();
    </script>
  </body>
</html>
Copy the code

Create a Machine (I) – Create geometry

The above steps are just the initialization of “Three”, which is the preliminary preparation. At this time, the scene is still empty and empty. We need to add various geometers to the scene and render them

Effect:

// create_fn.js
// Create a machine (sphere)
function createEarth() {
  const geometry = new THREE.SphereBufferGeometry(5.64.64); // Construct a spherical Geometry with BufferGeometry performance better than Geometry
  const texture = new THREE.TextureLoader().load("./img/earth.png"); // Create a texture map and paste it onto a surface
  const material = new THREE.MeshBasicMaterial({ map: texture }); // To create a material, the map property is passed in the newly created texture map
  const mesh = new THREE.Mesh(geometry, material); // Use Mesh to connect geometry and material to form the final object
  mesh.position.x = - 15; 
  mesh.position.y = - 1; // Change the position of the geometry
  return mesh;
}
Copy the code

Add geometry to the scene and render


      
<html>
  <head>.</head>

  <body>
    <div id="canvas-frame"></div>
    
	<! -- Some JS introduced -->
    <script src="lib/three.js"></script>
    <script src="lib/OrbitControls.js"></script>
    <script src="lib/stats.min.js"></script>
    
    <! -- self-wrapped function -->
    <script src="js/init_fn.js"></script>
    <script src="js/create_fn.js"></script>
    
    <script>
      const { scene, camera, renderer } = initThree(
        "#canvas-frame"
      );
      const lights = initLight();
      const controls = initControls();
      const stats = initStats();
      
      // The newly added code
      const mesh = createEarth(); 
      scene.add(mesh); // Add the object to the scene

      function animate(time) {
         stats.update();
         renderer.render(scene, camera);
         requestAnimationFrame(animate);
       }
       animate();
    </script>
  </body>
</html>
Copy the code

Create a Machine (2) – Create multi-material geometry

Effect:

// create_fn.js
// Create a machine (cylinder) with path as the upper and lower map picture path
function createMachine(path, conf) {
  const geometry = new THREE.CylinderBufferGeometry(5.5.2.64); 
  const texture = createTexture(path); // Since maps are often used, a function is removed to create texture maps
  const bottomMaterial = new THREE.MeshBasicMaterial({ map: texture });
  const sideMaterial = new THREE.MeshBasicMaterial({ color: "#1296DB" });
  const materials = [sideMaterial, bottomMaterial, bottomMaterial]; /* Material can be a single value or an array. If an array is used, it means that a different material is applied to each face. In this case, array is used
  const mesh = new THREE.Mesh(geometry, materials);
  initConfig(mesh, conf); // Write a separate function because you often need to transform an object (change position, size, etc.)
  return mesh;
}

// Create a texture map with path as the map image path
function createTexture(path, conf) {
  const texture = new THREE.TextureLoader().load(path);
  initConfig(texture, conf);
  return texture;
}

// Process the incoming conf, since most geometry can be set to position, rotation, scale, etc
InitConfig (mesh, {position: {x: -15, y: -1}})
The first parameter doesn't need to be passed to the mesh, it can also be passed to the Texture
function initConfig(mesh, conf) {
  if (conf) {
    const { position, rotation, scale, repeat } = conf;
    if (position) {
      const { x, y, z } = position;
      x ? (mesh.position.x = x) : null;
      y ? (mesh.position.y = y) : null;
      z ? (mesh.position.z = z) : null;
    }
    if (rotation) {
      const { x, y, z } = rotation;
      x ? (mesh.rotation.x = x) : null;
      y ? (mesh.rotation.y = y) : null;
      z ? (mesh.rotation.z = z) : null;
    }
    if (scale) {
      const { x, y, z } = scale;
      x ? (mesh.scale.x = x) : null;
      y ? (mesh.scale.y = y) : null;
      z ? (mesh.scale.z = z) : null;
    }
    if (repeat) {
      const { x, y } = repeat;
      // Handle the repeat of the Texture
      if (x) {
        // Set the number of repetitions in the x direction
        mesh.wrapS = THREE.RepeatWrapping;
        mesh.repeat.x = x;
      }
      if (y) {
        // Set the number of repeats in the y directionmesh.wrapT = THREE.RepeatWrapping; mesh.repeat.y = y; }}}}Copy the code

Create a machine (3) – Import the model

Usually, in actual projects, objects created by ThreeJS alone cannot meet our needs. In this case, modeling software such as 3DS MAX and Blender should be used for modeling, and then introduced into Three

  • Import the required loader files
<! -- index.html -->
<! -- Import GLTF model, need both loaders -->
<script src="lib/DRACOLoader.js"></script>
<script src="lib/GLTFLoader.js"></script>
Copy the code
  • Into the model
// create_fn.js
// Create an imported model with path as the model path
function createImportModel(path, conf) {
  // Since GLTFLoader can only fetch geometry in the form of callback functions, we add promises to make it easier to fetch geometry later
  return new Promise((res) = > {
    const dracoLoader = new THREE.DRACOLoader().setDecoderPath(".. /js/draco/"); // ThreeJS source code has an example folder, where js directory has a Draco directory, also want to import this directory
    const loader = new THREE.GLTFLoader().setDRACOLoader(dracoLoader);
    loader.load(path, function (gltf) {
      // GLTF objects have many properties. Gltf.scene is the geometry we need
      initConfig(gltf.scene, conf);
      res(gltf.scene); // Pass the object out
    });
  });
}
Copy the code

Effect:

// create_fn.js
// Create an imported model
function createImportModel(path, conf) {
  return new Promise((res) = > {
    const dracoLoader = new THREE.DRACOLoader().setDecoderPath(".. /js/draco/");
    const loader = new THREE.GLTFLoader().setDRACOLoader(dracoLoader);
    loader.load(path, function (gltf) {
      const colorArr = [
        "# 999"."rgb(110, 105, 112)"."#7fffd4"."#ffe4c4"."#faebd7"."#a9a9a9"."#5f9ea0"."#6495ed",];// There is a traverse method on the scene to traverse its children and determine if the child belongs to the Mesh class. If it does, it means that the child is a geometry and you can do something about it: add a material
      gltf.scene.traverse(function (child) {
        if (child instanceof THREE.Mesh) {
          // Add different colors for different parts of the model (i.e. different geometry).
          child.material = new THREE.MeshBasicMaterial({
            color: colorArr.pop(), }); }}); initConfig(gltf.scene, conf); res(gltf.scene);// Pass the object out
    });
  });
}
Copy the code

New model:

// create_fn.js
// Create an imported model
function createImportModel(path, conf) {
  return new Promise((res) = > {
    const dracoLoader = new THREE.DRACOLoader().setDecoderPath(".. /js/draco/");
    const loader = new THREE.GLTFLoader().setDRACOLoader(dracoLoader);
    loader.load(path, function (gltf) {
      const colorArr = [
        "# 999"."rgb(110, 105, 112)"."#7fffd4"."#ffe4c4"."#faebd7"."#a9a9a9"."#5f9ea0"."#6495ed",]; gltf.scene.traverse(function (child) {
        if (child instanceof THREE.Mesh) {
          child.material = new THREE.MeshBasicMaterial({ color: colorArr.pop() });
          
          // Add wire frames for different parts of the model (i.e. different geometry) to make each part angular and more realistic
          const geometry = new THREE.EdgesGeometry(child.geometry); // Edge geometry
          const material = new THREE.LineBasicMaterial({ color: "#dcdcdc" }); // Wireframes material
          // material.depthTest = false; // Depth test, if enabled, border transparent effect
          const mesh = new THREE.LineSegments(geometry, material);
          child.add(mesh); // Must be added to the child, not to the scene, to ensure that the position relative to the geometry is always the same}}); initConfig(gltf.scene, conf); res(gltf.scene);// Pass the object out
    });
  });
}
Copy the code

At this point, all our work is done, and we’re done! Final effect:

The words in the back

This is my first time to study 3D visualization and learn from ThreeJS and WebGL. Please point out any mistakes or questions in the comments section

The articles referred to in this article:

  • Codepen. IO/rachsmith/p… The official recommended introductory tutorial
  • Blog.csdn.net/qq_37540004… ThreeJs does smart city projects