preface

The actual effect of this article can be seen in auroral Au Design official website, here by the way, Au Design has been officially launched!! Don’t forget to give a thumbs-up if you think it’s good.

Au Design

AU Design is a Design language, Jiguang Experience Design (JED for short) is a comprehensive Experience Design team, specializing in interaction Design, visual Design, effect advertising Design, etc., responsible for the creativity and Experience Design of the whole line of aurora products. Enabling business through experience design. JED adheres To the concept of extreme design and is committed To building a first-class To B comprehensive experience design team.

So with all this vertex shaders, slice shaders and all this stuff, it’s time for the fun part. To create a full 60fps dazzle motion model switching effect. There are both CPU rendering and GPU rendering implementations of this effect, but with too many vertices, the performance of rendering in CUP will suffer.

Analysis of the characteristics of

The effect is a combination of the following transformations

  • Particle smooth displacement transformation
  • The particle size increases or decreases smoothly
  • The transparency of particles decreases as the Z-axis becomes smaller
  • Glow aftertreatment

Let’s take a look at what the structure of the plug-in class looks like, without going into detail about shaders, models, parameter passing and so on. It’s a little difficult to understand, but you’re curious about how to do it, so you can read the first two basic articles. The Distance between the Front End and the GPU

structure

First, the plug-in class inherits and encapsulates the utility classes described in the last two articles, and then implements the functionality step by step.

// ModelControl.ts
export default class ModelControl extends ThreeTool{
    // Model list
    public modelList: Array<IModel> = [];
    // Record the current model
    public currentModelIndex = 0;
    
    / /...
    constructor(){
        super({
          canvas: document.getElementById("canvasFrame") as HTMLCanvasElement,
          container: document.getElementById("canvasWrap") as HTMLCanvasElement,
          mode: "dev".clearColor: new THREE.Color("# 000")}); }/ /...
    // Generate a particle material
    private createMainMaterial(){}
    
    // Generate the default geometry for later saving particle/vertex position coordinates
    private createInitGeometry(){}
    
    // Generate the cache model
    private createInitModel(){}
    
    // Switch the current model to the specified model in the model list
    public changeModelByIndex(){}
    
    // Switch the model automatically
    public autoPlay(){}
    
    // Load the custom model
    public async loaderModel(){}
    
    / /...
}
Copy the code

With the help of ThreeTool, we don’t need to worry about cameras, lights, sizes, etc.

Implementation details

  • Since this is a model toggle effect, only one model can exist at any given time.
  • The model holds the current vertex coordinatespositionAnd the target model target to which you need to switchtargetPosition, which can achieve the effect of model switching.
  • Maintains the state of particles in shaders, rendered using the GPU.
  • Use a list to save the models for easy cycling.
  • Use Tweenjs to generate continuous time segments.

The particle motion

If you imagine A point moving from A to B in time T, how would that be represented? So just to think about it, you’re going to go from A to B A little bit at every interval, and you’re going to get to the B coordinate. It’s the same thing in three dimensions, except you decompose the coordinates of the points into three dimensions xyz.

Again, there are two ways to do this

  • The first intuitive way to do this is to useThe total time TBetween two pointsDistance DcalculateRate of SEach frame is addedS*timeSo much distance.
  • The second is to do a linear transformation between the AB coordinates, where each frame is locatedxA + (xB - xA) * val, includingvalThe value of is in the range of[0, 1]Between, thisvalYou can think of it as the rate of change. Because time is linear, the effect of using this directly is a linear transformation. If the linear transformation is too rigid, you can adjust itvalTo make a variety of easing effects.

For example, you can use xA + (xB-XA) * pow(val,2) to create a slow and fast effect

The two solutions are essentially the same, but the second is more concise. Let’s take a look at the CPU and GPU rendering scheme written with the second solution.

CPU render

render((time) = >{
    const val = (time * 0.0001) % 1;
    const x = xA + (xB - xA) * val;
    const y = yA + (yB - yA) * val;
    const z = zA + (zB - zA) * val;
    point.position.set(x,y,z);
})
Copy the code

The GPU to render

uniform float uTime;
attribute vec3 targetPosition;

void main() {
  vec3 cPosition;
  cPosition.x = position.x + (targetPosition.x - position.x) * uTime;
  cPosition.y = position.y + (targetPosition.y - position.y) * uTime;
  cPosition.z = position.z + (targetPosition.z - position.z) * uTime;

  gl_PointSize = 2.
  gl_Position = projectionMatrix * modelViewMatrix * vec4(cPosition, 1.0);
}
Copy the code

Here a smooth motion can be achieved by using a continuous time change

Random flashing

Since each particle/vertex has a different spatial coordinate, you can use their spatial coordinates to generate the initial state, such as cposition.x * cposition.y * cposition.z.

The particle size

The smooth change of particle size is achieved with the help of the sine/cosine function, which is actually modulated into a waveform like the one below.

gl_PointSize = (sin(cPosition.x*cPosition.y*cPosition.z+uTime)+1.) *2.;
Copy the code

Particle gradient

Same idea as particle size change, just a little tweaked, the transparency needs to be reduced as the Z-axis gets smaller.

float opacity = ((vZIndex+150.) /300.) - sin(curPos.z*curPos.x*curPos.y+uTime) + 0.3;
Copy the code

Dazzle light effects

The flash effect uses post-processing technology. The simple thing to do here is to use Threejs’ built-in flash shader, LuminosityHighPassShader, which can be found in the Shaders directory. It’s ok to write your own flash shader, but there’s no need to reinvent the wheel.


// UnrealBloomPass parameter
// resolution: The size of the scene covered by the light
// Strength: strength of light
// radius: the radius of the light emission
// threshold: the threshold for the light to dazzle (if the light in the scene is stronger than this value, the effect will be dazzle)
// See the first article on Wrapping the Threejs utility class for details on the render function.
public bloomRender(){
  const renderScene = new RenderPass(this.scene, this.camera);
  // Create a channel
  const bloomPass = new UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5.0.5.0.2
  );
  bloomPass.renderToScreen = true;
  bloomPass.strength = 1.5;
  bloomPass.radius = 0.5;
  bloomPass.threshold = 0.2;

  const composer = new EffectComposer(this.renderer);
  composer.setSize(window.innerWidth, window.innerHeight);
  composer.addPass(renderScene);
  // The channel bloomPass is inserted into the composer
  composer.addPass(bloomPass);

  const render = (time: number) = > {
    if (this.resizeRendererToDisplaySize(this.renderer)) {
      const canvas = this.renderer.domElement;
      this.css2drenderer.setSize(canvas.clientWidth, canvas.clientHeight);
      this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
      this.camera.updateProjectionMatrix();
    }
    this.css2drenderer.render(this.scene, this.camera);
    composer.render();
    const t = time * 0.001;
    requestAnimationFrame(render);
  };
  render(0);
};
Copy the code

The model is loaded

The model is loaded into the model list and the model with the most vertices is counted.

public loaderModel(geoPList: Array<Promise<IModel>>) { const modelList = await Promise.all(geoPList); this.modelList = modelList; const maxCount = Math.max( ... modelList.map((item) => item.attributes.position.count) ); this.positionCache = new Float32Array(maxCount * 3); return modelList; }Copy the code

Model switching

Change the model according to the subscript of the model list. The vertex coordinates of the target model are overwritten into the targetPosition of the cache model. After the passing time change this.tween.start(), the vertex coordinates of the particle model will change from the current coordinate position to targetPosition.

public changeModelByIndex(current: number){
  this.currentModelIndex = current;
  const originModel = this.originModel;
  const targetModel = this.modelList[current];
  const targetPosition = targetModel.attributes.position.array;
  const positionCache = this.positionCache;
  
  // The target coordinate of the last switch overwrites the current coordinate
  if (originModel.geometry.attributes.targetPosition) {
    const position = new Float32Array(
      originModel.geometry.attributes.targetPosition.array
    );
    originModel.geometry.setAttribute(
      "position".new THREE.BufferAttribute(position, 3)); originModel.material.uniforms.uVal.value =0;
  }
  // Override the coordinates of the target model
  for (let i = 0, j = 0; i < positionCache.length; i++, j++) {
    j %= targetPosition.length;
    positionCache[i] = targetPosition[j];
  }

  originModel.geometry.setAttribute(
    "targetPosition".new THREE.BufferAttribute(positionCache, 3));// Generate time changes
  this.tween.start();
  this.tween.onComplete(() = > {
    this.currentVal.uVal = 0;
  });
  return originModel;
};
Copy the code

Automatically play

Set the timer to automatically call the model switch method.

public autoPlay(time: number = 8000, current? :number){
  if(current ! = =undefined) {
    this.currentModelIndex = current;
  }

  const timer = setInterval(() = > {
    this.changeModelByIndex(this.currentModelIndex);
    this.currentModelIndex =
      (this.currentModelIndex + 1) % this.modelList.length;
  }, time);
  this.timer = timer;
  return timer;
};
Copy the code

The end of the

So far, a cool particle effect transform plugin is done. The next article will look at the effects that can already be achieved using the various shader functions: Using shader built-in functions and related effects.

The source code

Original is not easy to reprint, please contact the author. This article involves the source code is still on the road, the author of the like is open source code and continue to update the power. The source code and Demo of the tool class were given in the previous article “The Distance between the front end and the GPU” on shaders.

Ready to update the series

  • On sorting and Encapsulating The Threejs Tool class
  • The Distance between front End and GPU
  • “On using Shader built-in Functions and related effects” (in draft)
    • Various functions and special effects
  • About Shader Lighting effects (in draft)
    • Flat shading
    • Per-vertex shading (Gouraud shading)
    • Per-pixel shading (Phong shading)
    • BlinnPhong lighting model
    • Fresnel effect
    • Cartoon shaders
  • “On local Coordinates, World Coordinates, Projection Coordinates” (in draft)
    • Local coordinates
    • The world coordinates
    • The projection coordinate
    • Matrix transformation
  • On Github homepage Earth Effects
  • About D3js
  • On Visualizing a Data Diagram
  • “On writing a Hop hop game.”
    • Scenario generation
    • Collision detection
    • The game logic