In addition to the aperture effect that we talked about in the last section, there is another light effect that is commonly used in 3D visualization, and that is the glow effect

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

Create glow effect

Sometimes we want to add a glow to a geometry to make it more vivid

Effect synthesizer EffectComposer

Composer_fn. js is a composer_fn.js file that is used to write postprocessing functions, and then we import this js file in index.html

// composer_fn.js
function createComposer() {
  // The usual steps of post-processing:
  // 1. Create an EffectComposer, assuming the name is Composer
  // 2. Add (addPass) channels to Composer
  RenderPass is usually the first channel to be added. You can then select the type and order of the channels to be added depending on your requirements. For example, BloomPass is used later
  const bloomComposer = new THREE.EffectComposer(renderer);
  const renderPass = new THREE.RenderPass(scene, camera);
  const bloomPass = createUnrealBloomPass(); // We encapsulated createUnrealBloomPass to create BloomPass (glow)
  bloomComposer.addPass(renderPass);
  bloomComposer.addPass(bloomPass);
  return bloomComposer;
}

// UnrealBloomPass, glow effect
function createUnrealBloomPass() {
  const bloomPass = new THREE.UnrealBloomPass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    1.5.0.4.0.85
  );
  const params = {
    exposure: 1.bloomThreshold: 0.2.bloomStrength: 0.5.// Glow intensity
    bloomRadius: 0}; bloomPass.threshold = params.bloomThreshold; bloomPass.strength = params.bloomStrength; bloomPass.radius = params.bloomRadius;return bloomPass;
}
Copy the code

In addition to importing the js file in index.html, you need to import the js files required by the effect synthesizer (which can be found in ThreeJS’s example source directory) and switch the render method to render in Composer


      
<html>
  <head>.</head>

  <body>
    <script src="..."></script>
    
	<! Post-processing, some JS files required by the effect synthesizer -->
	<script src="lib/EffectComposer.js"></script>
	<script src="lib/ShaderPass.js"></script>
	<script src="lib/RenderPass.js"></script>
	<script src="lib/CopyShader.js"></script>
	<script src="lib/LuminosityHighPassShader.js"></script>
	<script src="lib/UnrealBloomPass.js"></script>
	
    <script>
      // ...
	  const composer = createComposer();
	  
	  function animate(a) {
	    // ...
	    
	    // renderer.render(scene, camera);
	    composer.render(); // Replace the previous render method comment with the render method of Composer
	    
	    requestAnimationFrame(animate);
	  }		
	  animate();
    </script>
  </body>
</html>
Copy the code

Effect:

Partial glow effect

It does glow, but it adds glow to all objects, to all scenes, and we really only need some objects to glow

Principle of partial glow effect:

  1. Prepare two EffectComposer, one bloomComposer for glow and the other finalComposer for normal rendering of the entire scene
  2. Change the material of all objects except glow object to black
  3. In bloomComposer harness BloomPass glow, but here you need to set up bloomComposer. RenderToScreen = false; Does not render to the screen
  4. Restores the black material to its original material
  5. Start rendering with finalComposer, where finalComposer needs to add a channel (addPass) that takes advantage of the rendering results of bloomComposer

In Three, all geometers are assigned 1 to 32 layers, numbered from 0 to 31. All geometers are stored on the 0th Layer by default. In order to better distinguish glow objects from non-glow objects, we need to create a Layer with Layer and add glow objects on a new Layer

// create_fn.js
// Create a Layer to distinguish glow objects
function createLayer(num) {
  const layer = new THREE.Layers();
  layer.set(num);
  return layer;
}

// Used in index.html
const bloomLayer = createLayer(1); // Create a new layer with the number 1

// Then add a new layer to all the glow objects, using the machine example we wrote earlier
// create_fn.js
function createEarth(conf) {
  const geometry = new THREE.SphereBufferGeometry(5.64.64);
  const texture = new THREE.TextureLoader().load("./img/earth.png");
  const material = new THREE.MeshBasicMaterial({ map: texture });
  const mesh = new THREE.Mesh(geometry, material);
  initConfig(mesh, conf);
  mesh.layers.enable(1); // Create a relationship with layer 1 and switch to that layer. Layers. Set (1); Because set will delete the related layer, if layer 0 is missing, the object cannot be rendered in finalComposer
  return mesh;
}
Copy the code

Write the effect handler code

// composer_fn.js
function createComposer() {
  const renderPass = new THREE.RenderPass(scene, camera); // Both composer use this renderPass, so declare it in the public section

  // bloomComposer creates a glow, but does not render to the screen
  const bloomComposer = new THREE.EffectComposer(renderer);
  bloomComposer.renderToScreen = false; // Do not render to screen
  const bloomPass = createUnrealBloomPass();
  bloomComposer.addPass(renderPass);
  bloomComposer.addPass(bloomPass);

  // Finally render the effect synthesizer finalComposer to the screen
  const finalComposer = new THREE.EffectComposer(renderer);
  const shaderPass = createShaderPass(bloomComposer); // Create a custom shader Pass, as detailed below
  finalComposer.addPass(renderPass);
  finalComposer.addPass(shaderPass);
  return { bloomComposer, finalComposer };
}

// ShaderPass, ShaderPass, custom degree, need to write OpenGL code
/ / incoming bloomComposer
function createShaderPass(bloomComposer) {
  // Use custom shader rendering materials
  const shaderMaterial = new THREE.ShaderMaterial({
    uniforms: {
      baseTexture: { value: null },
      bloomTexture: { value: bloomComposer.renderTarget2.texture }, // The glow map property is set to the bloomComposer passed in, which explains why bloomComposer should not be rendered to the screen
    },
    vertexShader: document.getElementById("vertexshader").textContent, // Vertex shader
    fragmentShader: document.getElementById("fragmentshader").textContent, // Chip shader
    defines: {},
  });
  const shaderPass = new THREE.ShaderPass(shaderMaterial, "baseTexture");
  shaderPass.needsSwap = true;
  return shaderPass;
}
Copy the code

In the entry file index. HTML, use the effects handler to implement partial glow


      
<html>
  <head>.</head>
  <body>
  	<div>.</div>
  	
  	<! -- Shader code -->
    <script type="x-shader/x-vertex" id="vertexshader">
      varying vec2 vUv;
      void main() {
      	vUv = uv;
      	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
      }
    </script>
    <script type="x-shader/x-fragment" id="fragmentshader">
      uniform sampler2D baseTexture;
      uniform sampler2D bloomTexture;
      varying vec2 vUv;
      vec4 getTexture( sampler2D texelToLinearTexture ) {
      	return mapTexelToLinear( texture2D( texelToLinearTexture , vUv ) );
      }
      void main() {
      	gl_FragColor = ( getTexture( baseTexture ) + vec4( 1.0 ) * getTexture( bloomTexture ) );
      }
    </script>
    
    <script src="..."></script>
    <script>
      // ...
      const bloomLayer = createLayer(1); // Create a new layer with the number 1
      const materials = {};
      const darkMaterial = new THREE.MeshBasicMaterial({ color: "black" }); // Create a black plain material in advance for later use
	  const { bloomComposer, finalComposer } = createComposer(); // Create an effect handler

      function animate(time) {
        // ...

        // Implement local glow
        // 1. Use the darkenNonBloomed function to turn the material of other objects except the glow object to black
        scene.traverse(darkenNonBloomed);
        // 2. Use bloomComposer to generate glow
        bloomComposer.render();
        // 3. Restore the black material to its original material
        scene.traverse(restoreMaterial);
        // 4. Use finalComposer for the final rendering
        finalComposer.render();

        requestAnimationFrame(animate);
      }
      // Turn all materials in the scene except glow objects to black
      function darkenNonBloomed(obj) {
        // The layer test method checks whether the layer in the argument is the same layer as its own layer
        // If obj is geometry and not on bloomLayer, it is not glow
        if ((obj.isMesh || obj.isSprite) && bloomLayer.test(obj.layers) === false) {
          // If it is Sprite geometry, it needs to be changed to black Sprite material for special treatment
          if (obj.isSprite) {
            materimals[obj.uuid] = obj.material; // Store the original material information in the materimals variable
            obj.material = new THREE.SpriteMaterial({
              color: "# 000"});// Other geometry can be converted to normal black material
          } else {
            materials[obj.uuid] = obj.material; // Store the original material information in the materimals variableobj.material = darkMaterial; }}}// Restore the scene with the material changed to black
      function restoreMaterial(obj) {
        if (materials[obj.uuid]) {
          obj.material = materials[obj.uuid]; // Restore the material
          delete materials[obj.uuid]; // Delete from memory
        }
      }
	  animate();
    </script>
  </body>
</html>
Copy the code

Effect:

Add anti-aliasing

We finally implemented partial glow, but on closer inspection we suddenly realized that the object was heavily aliased after adding BloomPass, even after we set the Antialias in render, so I introduced another post-processing channel here, FxaaPass

// composer_fn.js
function createComposer() {
  const renderPass = new THREE.RenderPass(scene, camera); 

  const bloomComposer = new THREE.EffectComposer(renderer);
  bloomComposer.renderToScreen = false;
  const bloomPass = createUnrealBloomPass();
  bloomComposer.addPass(renderPass);
  bloomComposer.addPass(bloomPass);

  const finalComposer = new THREE.EffectComposer(renderer);
  const shaderPass = createShaderPass(bloomComposer);
  const FxaaPass = createFxaaPass(); // THE function I encapsulated to create FxaaPass is detailed below
  finalComposer.addPass(renderPass);
  finalComposer.addPass(shaderPass);
  finalComposer.addPass(FxaaPass);
  
  return { bloomComposer, finalComposer };
}

// Anti-aliasing, FXAA, SMAA, SSAA can all be anti-aliasing, the anti-aliasing effect decreases successively
function createFxaaPass() {
  let FxaaPass = new THREE.ShaderPass(THREE.FXAAShader);
  const pixelRatio = renderer.getPixelRatio();
  FxaaPass.material.uniforms["resolution"].value.x =
    1 / (window.innerWidth * pixelRatio);
  FxaaPass.material.uniforms["resolution"].value.y =
    1 / (window.innerHeight * pixelRatio);
  FxaaPass.renderToScreen = true;
  return FxaaPass;
}
Copy the code

Introduces a new dependency file in the entry file index.html

<! -- index.html -->
<script src="lib/FXAAShader.js"></script>
Copy the code

After adding anti-aliasing, the whole picture is much smoother

Advanced effects combinator MaskPass

Finally solved the anti-aliasing problem, finally can take a breath… Wait, why did the elvish text suddenly become blurry? After a lot of research, I finally came up with the advanced effects combinator MaskPass to solve this problem

What is MaskPass? To put it simply, masks can be grouped in an EffectComposer. Each set of masks uses a different channel (that is, each set of addPass content is different), and each set of masks can be rendered on a different Scene

Implementation principle:

  1. Set the unpasted parts as the first Mask, using the original channel: add glow, anti-aliasing
  2. Set the pasted parts as a second set of masks and use a new channel: render directly without any processing. This group has Sprite text and glow (glow does not require glow and anti-aliasing, but can cause color aberration and other unexpected bugs if placed in the previous group, so it is also placed in this group without any treatment)

The following is the specific implementation process:

  1. Create two scenes, then take the geometry of the second Mask, rendered without any processing, from the Group Group (each Group had a Sprite text and the glow was placed in the second Group) and add it to the normalScene. The rest is going to stay in the scene
// init_fn.js
function initThree(selector) {
  const scene = new THREE.Scene();
  const normalScene = new THREE.Scene(); // Create two scenes
  constcamera = ... ;constrenderer = ... ; renderer.autoClear =false; // Look out here!! Manual clearing is required. To use the advanced effects combinator MaskPass, autoClear must be set to false
  document.querySelector(selector).appendChild(renderer.domElement);
  return { scene, normalScene, camera, renderer };
}
Copy the code

Entry file index.html


      
<html>
  <head>.</head>

  <body>
    <script src="..."></script>
    <script>
	  const { scene, normalScene, camera, renderer } = initThree("#canvas-frame");
	  // ...
	  let group1, group1Animate;
	  {
	    // ...
	  }
	  
	  let group2, group2Animate;
	  {
	    // ...
	  }

      // normalScene contents of the scene
      let normalSceneAnimate;
      {
        const { sprite: spriteText1 } = await createSpriteText("#label1", {
          position: { x: - 65..y: 23}});// Extract the Sprite text from the original Group1
        const { sprite: spriteText2 } = await createSpriteText("#label2", {
          position: { x: 36.y: 23}});// Extract the Sprite text from the original Group2
        const beam = createLightBeam(100.56.2."red", {
          scale: { z: 10 },
          rotation: { x: Math.PI / 2 },
          position: { x: - 13.y: 3.9.z: - 28}});// Extract the aperture effect from the original Group2
       normalScene.add(spriteText1);
       normalScene.add(spriteText2);
       normalScene.add(beam); // Add all to normalScene
       
       let direction = true;
       normalSceneAnimate = function () {
         if (direction) {
            beam.material[1].opacity -= 0.01;
            if (beam.material[1].opacity <= 0.5) {
              direction = false; }}else {
            beam.material[1].opacity += 0.01;
            if (beam.material[1].opacity >= 1) {
              direction = true; }}}; }function animate() {
	    group1Animate();
        group2Animate();
        normalSceneAnimate();
         
        stats.update();
	    
	    // The rendering process remains the same, becoming just the render group Mask of the EffectComposer
	    scene.traverse(darkenNonBloomed);
        bloomComposer.render();
        scene.traverse(restoreMaterial);
        finalComposer.render();
	    composer.render();
	    
	    requestAnimationFrame(animate);
	  }		
	  animate();
    </script>
  </body>
</html>
Copy the code
  1. Modify the logic of EffectComposer to group rendering using MaskPass
// composer_fn.js
function createComposer() {
  const renderPass = new THREE.RenderPass(scene, camera); // RenderPass for the first group
  const renderNormalPass = new THREE.RenderPass(normalScene, camera); // RenderPass for the second group

  // Produces a glow, but does not render to the screen
  const bloomComposer = new THREE.EffectComposer(renderer);
  bloomComposer.renderToScreen = false;
  const bloomPass = createUnrealBloomPass();
  bloomComposer.addPass(renderPass);
  bloomComposer.addPass(bloomPass);

  // Finally render to screen with MaskPass
  const finalComposer = new THREE.EffectComposer(renderer);
  finalComposer.renderTarget1.stencilBuffer = true;
  finalComposer.renderTarget2.stencilBuffer = true; // Set both to true
  renderPass.clear = false;
  renderNormalPass.clear = false; RenderPass defaults to false. If this is false, renderNormalPass clears the color of the previous RenderPass
  finalComposer.addPass(renderPass);
  finalComposer.addPass(renderNormalPass);
  
  const clearMaskPass = new THREE.ClearMaskPass();
  // The first group starts rendering
  const maskPass1 = new THREE.MaskPass(scene, camera);
  const shaderPass = createShaderPass(bloomComposer);
  const FxaaPass = createFxaaPass();
  finalComposer.addPass(maskPass1); // Add maskPass for the first group
  finalComposer.addPass(shaderPass);
  finalComposer.addPass(FxaaPass);
  finalComposer.addPass(clearMaskPass); // Clear maskPass for the first group

  // The second group starts rendering
  const maskPass2 = new THREE.MaskPass(normalScene, camera);
  finalComposer.addPass(maskPass2); // Add maskPass for the second group
  finalComposer.addPass(clearMaskPass); // Add maskPass for the second group

  const effectCopy = new THREE.ShaderPass(THREE.CopyShader);
  finalComposer.addPass(effectCopy); // CopyShader is required last because manual cleanup is set
  return { bloomComposer, finalComposer };
}
Copy the code

End result: partial glow and anti-aliasing without making some objects burn

  1. Renderer’s autoClear is set to false
  2. EffectComposer renderTarget2. StencilBuffer set to true
  3. RenderPass clear is set to false
  4. Because manual Clear is set, you finally need to addPass a CopyShader