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:
- Prepare two EffectComposer, one bloomComposer for glow and the other finalComposer for normal rendering of the entire scene
- Change the material of all objects except glow object to black
- In bloomComposer harness BloomPass glow, but here you need to set up bloomComposer. RenderToScreen = false; Does not render to the screen
- Restores the black material to its original material
- 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:
- Set the unpasted parts as the first Mask, using the original channel: add glow, anti-aliasing
- 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:
- 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
- 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
- Renderer’s autoClear is set to false
- EffectComposer renderTarget2. StencilBuffer set to true
- RenderPass clear is set to false
- Because manual Clear is set, you finally need to addPass a CopyShader