preface

Is the so-called: no toss, no front end. Do not do WebGL, and salted fish what difference!

In official terms: three.js – Javascript 3D Library.

Today, let’s get familiar with the design concept and thought of Three.js.

Cartesian right hand coordinate system

In 3D, we first need to understand the basic principle: the three-dimensional coordinate system.

We all know that in THREE dimensions of CSS3 it’s left handed. (If you don’t know, you can read my previous article “CSS3 3D Transformation”)

However, in three. js, our space is presented based on the right-handed Cartesian coordinate system. As follows:

Once we know the coordinate system, we can create the scene we want in this three-dimensional space.

Create a scenario

If you want to use three-dimensional space, you first have to create a three-dimensional space container. To create a three-dimensional space, you only need to instantiate the object three. Scene.

var scene = new THREE.Scene();
Copy the code

A scene is a three-dimensional space where you can place objects, cameras and lights, like the universe, with no boundaries, no light, and endless darkness.

The components of a scene can be roughly divided into three categories: camera, light source and object.

Before we look at the components in thee.js, let’s take a look at a photo:

This is a studio shot of merchandise. This photo can basically illustrate our 3D design mode of Three.js: after we have a space, we need to put our objects into it. Once we have the object we also need to set up at least one light source so that we can see our subject. Finally, what we present to the customer is a series of animations generated by the continuous playback of the photos taken by the camera. The parameters, position and Angle of the camera directly affect the pictures we take.

The subject

Before using the object, let’s explain the design mode for creating the object with three.js:

First three.js deconstructs any subject into small triangles. The triangle can be used as the minimum unit of structure for both two-dimensional and three-dimensional graphics. And the structure is a grid of our subjects.

The grid structure of two-dimensional plane is shown as follows:

The three-dimensional sphere grid structure is shown below:

It can be seen that the triangle is the smallest partition unit in three.js. This is the grid structure.

Of course, having a grid structure is not enough. Just like the human body, because the grid structure is like a skeleton, it needs material on its exterior. The material is the skin of the object, which determines the appearance of the geometry.

Geometry model

In three.js, there are a lot of geometry grid structures preset for us:

  • 2 d:

    • THREE.PlaneGeometry

      This geometry has been shown above.

    • THREE. CircleGeometry (round)

    • THREE. RingGeometry (ring)

  • The three dimensional

    • BoxGeometry (rectangular)

    • The geometry of the spheresphere

      This geometry has been shown above.

    • THREE.CylinderGeometry

    • THREE.Torus

The above is just a part of the built-in geometry. When we use these assemblies, we simply instantiate the corresponding geometry objects.

Specifically, we take instantiating a cube as an example:

var geometry = new THREE.BoxGeometry(4.4.4);
Copy the code

Here we declare and instantiate a BoxGeometry object. When we created the object, we set the length, width, and height to 4.

So I’ve created a cube. But having such a grid framework is not enough. The next step is to add materials to it.

Material

In the material component, three. js also presets several material objects for us. Here we briefly introduce the two most commonly used:

  1. MeshBasicMaterial

    This material is the base material of three.js. Mesh structure used to give a simple color to a mesh of geometry or to display geometry. (It works even without a light source.)

  2. MeshLambertMaterial

    This is a material that takes into account the influence of light. Used to create dim, unshiny objects.

It is worth noting that multiple materials can be superimposed on the same grid structure.

Here we successively use MeshBasicMaterial and MeshLambertMaterial to prepare two different materials for the cube created above:

var geometryMeshBasicMaterial = new THREE.MeshBasicMaterial({
  color: 0xff0000.wireframe: true
});
var geometryMeshLambertMaterial = new THREE.MeshLambertMaterial({
  color: 0x242424
});
Copy the code

The WireFrame property, when set to true, will render the material to line width. Picture frames can be interpreted as grid lines. For example, the line frame of a cube is as follows:

Mesh

Once we have the geometry mesh model and materials we need to combine the two to create the object we are shooting.

Here we introduce two different camera object constructors:

  • new THREE.Mesh(geometry, material)
  • THREE.SceneUtils.createMultiMaterialObject(geometry,[materials…] )

These two methods are both ways to create objects, and the first parameter is the Geometry model, the only difference is the second parameter. The former can only be created with one material, while the latter can be created with multiple materials (passing in an array of multiple materials).

Here we will create a multi-material object.

var cube = THREE.SceneUtils.createMultiMaterialObject(geometry, [
  geometryMeshBasicMaterial,
  geometryMeshLambertMaterial
]);
Copy the code

Now that we have an object, we need to add our object to the scene, just like we do with merchandise, by placing our merchandise in the space.

In three.js, adding an object to a scene can be done directly by calling the Add method on the scene object. The concrete implementation is as follows:

scene.add(cube);
Copy the code

We pass in the add() method the objects we want to add, which can be one or more, separated by commas.

The light source

Just like the logic in real life, objects themselves don’t emit light. Without the sun as a source of light, the Earth would be plunged into endless darkness, unable to see anything. So we will also add a light source object to our scene.

In three. js, there are several kinds of light sources. Next, we will briefly introduce some of them.

  1. THREE.AmbientLight

    This is a basic light source that will be superimposed on the colors of existing objects in the scene.

    The light source has no specific direction of origin and does not produce shadows.

    We often use it alongside other light sources to soften shadows or add some extra color to the scene.

  2. THREE.SpotLight

    This light source has the effect of concentrating light, similar to lamps, flashlights, stage spotlights.

    This light source can cast shadows.

  3. THREE.DirectionalLight

    This source is also called infinite light, similar to sunlight.

    The light from this source can be regarded as parallel.

    This light source can also cast shadows.

In our example, we will create our light with SpotLight.

We’ll start by instantiating a SpotLight object as we usually do before, and passing in a hexadecimal color value as the color of our light.

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0.20.20);
spotLight.intensity = 5;
scene.add(spotLight);
Copy the code

Once we have the light source object, we call position.set() to set the position in 3d space.

The intensity attribute sets the intensity of the light source. The default value is 1.

Finally, we also need to put the light source into our scene space. So we have a SpotLight in our scene.

The camera

There are two types of cameras in three.js:

  • PerspectiveCamera

    It fits the general principle of near big far small. Render the scene from a perspective that approximates the real world.

  • THREE.OrthographicCamera

    Provides a pseudo-3d effect.

You can see it: Perspective cameras are closer to the world we see in real life, while orthogonal cameras render results regardless of how far away the object is from the camera.

PerspectiveCamera:

Let’s start with a picture:

For a perspective camera, we need to set the following parameters:

  • Fov isVertical directionThe Angle of tension on (in degrees rather than radians)
  • Aspect ratio is the ratio of the camera’s horizontal to vertical length
  • Near The closest distance of the camera to the view object
  • Far The farthest distance from the camera to the view object
  • Zoom

Here we will also create a perspective camera of our own.

var camera = new THREE.PerspectiveCamera(
  75.window.innerWidth / window.innerHeight,
  0.1.1000
);
camera.position.x = 5;
camera.position.y = 10;
camera.position.z = 10;
camera.lookAt(cube.position);
Copy the code

First, when instantiating the perspective camera object, we passed several parameters inside it: the vertical Angle was 75 degrees, the aspect ratio was the same as the window, and the closest and furthest distances from the camera to the view body were 0.1 and 1000, respectively.

Finally, we let the camera lookAt the location of the cube we created earlier by calling lookAt(). (By default, the camera points to the origin of the three-dimensional coordinate system.)

Renderer

With all of these objects, we are just a few steps away from success.

When you look at the title of this section, you may ask: What is a renderer?

In layman’s terms: what we take with a camera is a negative, not a real photograph. This is easy to understand if you remember old cameras.

When we take an old camera (the kind that still needs film) we get a negative for every shot we take. If we want to get the real picture, we have to take the negative and go to the studio to have it developed. At this time, the boss will ask you how big the photo you want to develop, and then develop the photo you want according to your needs.

That’s what renderers are for, so to speak. Remember that when we set the camera parameters, we did not set the width of the camera, but only specified the aspect ratio of the camera. Just like our negatives, it’s small, but it shows the basic aspect ratio of our photos.

We created the renderer the same way we created the other objects in THREE, by instantiating the object first.

Three.js provides several different renderers for us. Here we will use the Three. WebGLRenderer as an example.

var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.render(scene, camera);
Copy the code
  • We set the render length and width by calling setSize().
  • The domElement element of the renderer, representing the canvas in the renderer, all renderers are drawn on domElement, so appendChild means attach this domElement below the body, This way the rendered results can be displayed on the page.
  • Passing our scene and camera in the Render () method is like passing a negative of the scene taken by the camera, which renders the image to our canvas.

You should get a shape like this:

Here we add coordinate system objects just for the sake of observation.

Like a normal object, we add it to our scene by instantiating it and passing in an axis length parameter.

var axes = new THREE.AxisHelper(7);
scene.add(axes);
Copy the code

Here, our coordinate system has an axis length of 7.

At this point, you’ll notice that the image is still static, and the 3D features are not fully utilized.

Animation

There are a few things we need to know about animation before we go into it. I’m going a little further, but it will help us understand animation rendering and improve performance.

To understand the Event Loop

Asynchronous execution works as follows:

  1. All synchronization tasks are executed on the main thread, forming an execution Context stack.
  2. In addition to the main thread, there is a task queue. As long as the execution conditions of asynchronous tasks are met, they are in the Task queuePut an event.
  3. Once all synchronization tasks in the Execution stack are completed, the system reads the Task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.

The main thread repeats step 3 above. The main thread reads events from the “task queue” in a continuous Loop, so the whole operation mechanism is also called an Event Loop. Whenever the main thread is empty, it reads the “task queue”, which is how JavaScript works. The process repeats itself.

The animation principles

Animation is actually an illusion created by a series of pictures playing at a certain frequency for a certain amount of time.

An important characteristic of the eye is visual inertia, that is, once a light image is formed on the retina, the vision will maintain the perception of the light image for a limited time, this physiological phenomenon is called visual persistence. For light stimuli of moderate intensity, the visual duration is about 0.1 to 0.4 seconds.

In order for animations to transition in a coherent and smooth manner, we typically render animations at a rate of 60 frames per second or more.

Why not use setInterval() for animation?

  • The execution time of setInterval() is not fixed. In Javascript, the setInterval() task is placed in an asynchronous queue, and the queue is checked to see if it needs to start executing only after the main thread has finished executing, so the actual execution time of setInterval() is usually later than the set time.
  • SetInterval () can only set a fixed interval, which is not necessarily the same as the screen refresh time.

In both cases, setInterval() does not execute at the same pace as the screen refreshes, resulting in frame loss. So why does being out of step cause frame loss?

The first thing to understand is that setInterval() only changes the image properties in memory, and this change will not be updated to the screen until the next time the screen refreshes. If the two are out of step, it is possible to skip the action in one frame and update the image in the next. Assuming the screen refreshes every 16.7ms (60 frames) and setInterval() sets the image to move 1px to the left every 10ms, the following drawing will occur:

  • 0ms: Screen not refreshed, waiting, setInterval() not executed, waiting;
  • 10ms: The screen is not refreshed, while waiting, setInterval() starts executing and sets image attributes left=1px;
  • 16.7ms: The screen starts to refresh, the image on the screen moves 1px to the left, setInterval() is not executed, continue to wait;
  • 20ms: the screen is not refreshed, waiting, setInterval() starts executing and sets left=2px;
  • 30ms: the screen is not refreshed, waiting, setInterval() starts executing and sets left=3px;
  • 33.4ms: the screen starts to refresh, the image on the screen moves 3px to the left, setInterval() is not executed, and the waiting process continues;

As can be seen from the drawing process above, the screen does not update the left=2px frame, and the image directly jumps from 1px to 3px, which is a frame loss phenomenon, and this phenomenon will cause animation lag.

requestAnimationFrame()

The advantage of requestAnimationFrame ()

The biggest advantage of requestAnimationFrame() over setInterval() is that it is up to the system to decide when to execute the callback function. ** To be more specific, if the screen refresh rate is 60 frames, then the callback is executed every 16.7ms. If the screen refresh rate is 75Hz, then this interval becomes 1000/75=13.3ms. In other words, RequestAnimationFrame () keeps pace with the refresh of the system. It ensures that the callback function is executed only once in each screen refresh interval, thus avoiding frame loss and animation stuttering.

In addition, requestAnimationFrame() has two other advantages:

  • CPU saving: For animations implemented by setInterval(), setInterval() still performs animation tasks in the background when the page is hidden or minimized. Since the page is not visible or available at this time, it is meaningless to refresh the animation, which is a waste of CPU resources. RequestAnimationFrame () is completely different. The screen refresh task of the page is also suspended when the page is not active, so requestAnimationFrame() will also stop rendering when the page is active. The animation picks up where it left off, effectively saving CPU overhead.

  • Function throttling: In high frequency events (resize, Scroll, etc.), requestAnimationFrame() is used to ensure that functions are executed only once in each refresh interval to prevent multiple functions from being executed in one refresh interval. This ensures smoothness and saves function execution costs. It does not make sense if the function is executed more than once in a refresh interval, because the monitor is refreshed every 16.7ms and multiple draws do not show up on the screen.

How requestAnimationFrame() works:

Take a look at Chrome source code:

int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback)
{
  if(! m_scriptedAnimationController) { m_scriptedAnimationController = ScriptedAnimationController::create(this);
    // We need to make sure that we don't start up the animation controller on a background tab, for example.
      if(! page()) m_scriptedAnimationController->suspend(); }return m_scriptedAnimationController->registerCallback(callback);
}
Copy the code

Take a closer look at feel the underlying implementation surprisingly simple, generated a ScriptedAnimationController instance to hold the registered event, then register the callback.

The requestAnimationFrame implementation is obvious:

  • Register the callback function
  • Browser updates at a certain frame rate trigger all registered callbacks

The working mechanism here can be understood as a transfer of ownership, giving ownership of the time that triggers frame updates to the browser kernel to keep pace with the browser’s updates. This avoids browser updates being out of sync with animation frame updates and gives the browser plenty of room for optimization.

Create the animation with requestAnimationFrame()

We need to create a loop rendering function and call:

// a render loop
function render() {
  requestAnimationFrame(render);

  // Update Properties

  // render the scene
  renderer.render(scene, camera);
}
Copy the code

We update and render the corresponding properties inside the function body and let the browser control the update of the animation frame.

animation

Here we will create our animation with requestAnimationFrame(). Allowing the browser to control the update of animation frames maximizes our performance.

var animate = function() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
};
animate();
Copy the code

In the animate() method, we use requestAnimationFrame(animate) to make the browser call animate every time the page is updated. And every time it is called, the properties of the cube will be changed accordingly: each time it is called, the X-axis and Y-axis will be rotated by 0.01 radian, and rendered on the canvas.

This gives us our animation:

THREE. The Color object

Here I’ll add a few additional notes about the built-in color objects in Three.js.

In general, we can create a specified color object using either a hexadecimal string (“#000000”) or a hexadecimal value (0x000000). We could also create it with RGB color values (0.2, 0.3, 0.4), but note that each value ranges from 0 to 1.

Such as:

var color = new THREE.Color(0x000000);
Copy the code

After creating a color object, we can use some of its own methods, which we won’t go into detail here:

The function name describe
set(value) Sets the current color to the specified hex value. This value can be a string, a value, or an existing instance of three.color.
setHex(value) Sets the current color to the specified hexadecimal numeric value.
setRGB(r,g,b) Set the color based on the RGB value provided. Parameters range from 0 to 1.
setHSL(h,s,l) Set the color based on the supplied HSL value. Parameters range from 0 to 1.
setStyle(style) Set the color according to how the CSS sets the color. For example, “RGB (25, 0, 0)”, “#ff0000”, “#ff” or “red” can be used.
copy(color) Copies the color value from the supplied color object to the current object.
getHex() Gets the color value from the color object as a hexadecimal value: 435241.
getHexString() Gets the color value from the color object as a hexadecimal string: “0c0C0c”.
getStyle() Get the color value from the color object as a CSS value: “RGB (112, 0, 0)”
getHSL(optionalTarget) Gets the color value from the color object as an HSL value. If an optionTarget object is provided, three.js sets the h, S, and L properties to that object.
toArray Returns an array of three elements: [r,g,b].
clone() Copy the current color.

conclusion

It can be said as follows:

Everything in three.js is built on the Scene object. Once we have the scene space, we can add the subjects we want to show. Of course, once we have our subject, we also need a light source so that we can see our subject. We also need a camera to take pictures of our subjects. Of course we actually need to rely on our renderer to draw the actual image on the canvas.

By constantly changing the properties of the object, and constantly drawing our scene, this produces animation!

Attached to the source

<html>
  <head>
    <title>Cube</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }

      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <script src="https://cdn.bootcss.com/three.js/r83/three.min.js"></script>
    <script>
      var scene = new THREE.Scene();

      var axes = new THREE.AxisHelper(7);
      scene.add(axes);

      var geometry = new THREE.BoxGeometry(4.4.4);
      var geometryMeshBasicMaterial = new THREE.MeshBasicMaterial({
        color: 0xff0000.wireframe: true
      });
      var geometryMeshLambertMaterial = new THREE.MeshLambertMaterial({
        color: 0x242424
      });
      varcube = THREE.SceneUtils.createMultiMaterialObject(geometry, [ geometryMeshBasicMaterial, geometryMeshLambertMaterial ]);  scene.add(cube);var spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(0.20.20);
      spotLight.intensity = 5;
      scene.add(spotLight);

      var camera = new THREE.PerspectiveCamera(
        75.window.innerWidth / window.innerHeight,
        0.1.1000
      );
      camera.position.x = 5;
      camera.position.y = 10;
      camera.position.z = 10;
      camera.lookAt(cube.position);

      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      var animate = function() {
        requestAnimationFrame(animate);
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        renderer.render(scene, camera);
      };
      animate();
    </script>
  </body>
</html>
Copy the code

-EFO-


The author specially created a repository on Github, which is used to record the skills, difficulties and easy mistakes in learning full stack development. Please click the link below to browse. If you feel good, please give a little star! 👍


2019/04/14

AJie