16
头图
Disclaimer: The graphic and model materials involved in this article are only for personal study, research and appreciation. Please do not re-modify, illegally spread, reprint, publish, commercialize, or conduct other profit-making activities.

background

In the Three.js Journey course example , an example of the 3D text suspension effect implemented using the Three.js built-in method is provided. This article uses the React + Three.js technology stack to achieve a similar effect with reference to the example. Article related to knowledge include: CSS grid background, MeshNormalMaterial normal material, FontLoader font loader, TextGeometry text buffer geometry, TorusBufferGeometry ring buffer geometry, ConeBufferGeometry cone buffer geometry, OctahedronBufferGeometry octahedral cushion geometry, Three.js late rendering , GlitchPass channel, Element.requestFullscreen , Document.exitFullscreen , etc.

Effect

The effect is shown in the 👆 banner diagram. The main body of the page is composed of the text mesh model in the center and the surrounding torus, cone and octahedron . As the 🖱 mouse is moved or clicked on the page, the model moves with it. There are 2 buttons in the upper right corner of the page, which can switch the background color of the page and switch fault style and post-production effects. Double tap the screen to enter or exit full screen.

👀 Online preview: https://3d-dragonir.vercel.app/#/floating

👀 or https://dragonir.github.io/3d/#/floating

Adapted:

  • 💻 PC end
  • 📱 Mobile terminal

accomplish

resource introduction

First introduce the module resources required for development, of which FontLoader is used to load font files, TextGeometry is used to create 3D font grid, EffectComposer , RenderPass and GlitchPass are used for post-effect rendering.

import * as THREE from "three";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader";
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js';

DOM structure

The structure of the page DOM is very simple, the container #canvas is used for scene rendering, .color_pick is used to switch the page background color, and .pass_button is used to switch the glitch style post-rendering.

<div className='floating_page' style={{ backgroundColor: this.state.backgroundColor }}>
  <div id="canvas"></div>
  <input className='color_pick' type="color" onChange={this.handleInputChange} value={this.state.backgroundColor} />
  <button className='pass_button' onClick={this.handleRenderChange}>特效<span className='highlight'>{this.state.renderGlithPass ? '开' : '关'}</span></button>
</div>

set state

backgroundColor indicates the background color of the current page, and renderGlithPass indicates whether to enable the later state. The self-test found that in the iOS Safari browser, the post-rendering of the faulty style will cause the model to have mold-piercing problems 😱 , so use this parameter to control the default close of post-effects on the mobile phone and open by default on pc .

state = {
  backgroundColor: '#164CCA',
  renderGlithPass: !(window.navigator.userAgent.toLowerCase().indexOf('mobile') > 0)
}

grid background

Use the pure CSS attribute linear-gradient to implement a grid background to beautify the page 🎏 .

background-image: linear-gradient(rgba(3, 192, 60, .3) 1px, transparent 1px), linear-gradient(90deg, rgba(3, 192, 60, .3) 1px, transparent 1px);
background-size: 1em 1em;

scene initialization

Initialize the rendering container, scene, and camera. The position of the camera can be adjusted according to your needs. render Open alpha and set .setClearAlpha(0) to set the background color to transparent.

canvas = document.getElementById('canvas');
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(2, window.devicePixelRatio));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearAlpha(0);
canvas.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, .1, 10000);
camera.position.set(-2 * 10000, 0, 780);

Create material

All mesh models of in this article will use the same material MeshNormalMaterial , and applying its properties can make mesh models produce color gradients. It is created once globally, and subsequent development does not require repeated creation, which is conducive to improving page performance.

const material = new THREE.MeshNormalMaterial();

💡 MeshNormalMaterial

It is a material that maps the normal vector to the color of RGB . You can check whether the surface of the model is flat by observing whether the gradient color of the surface of the model is continuous.

constructor :

MeshNormalMaterial(parameters : Object)
  • parameters : Optional, an object that defines the appearance of the material, with one or more properties.

special attribute :

  • .normalMap[Texture] : Used to create normal map textures, the RGB value affects the surface normal for each pixel fragment and changes the way the color is illuminated.
  • .normalMapType[Integer] : Type of normal map, options are THREE.TangentSpaceNormalMap (default) and THREE.ObjectSpaceNormalMap .
  • .normalScale[Vector2] : How much the normal map affects the material. The range is 0-1 and the default is Vector2 set to (1, 1) .
  • .flatShading[Boolean] : Defines whether the material is rendered with flat shading, default is false .
  • .morphNormals[Boolean] : Defines whether to use morphNormals . Set to true to pass the morphNormal attribute from geometry to shader . The default value is false .
  • .morphTargets[Boolean] : Defines whether the material uses morphTargets , the default is false .

Create text models

Load the FontLoader font JSON file with fontface and create the text geometry model with TextGeometry .

const loader = new FontLoader();
loader.load('./fonts/helvetiker_regular.typeface.json', font => {
  textMesh.geometry = new TextGeometry('@dragonir\nfantastic\nthree.js\nart work', {
    font: font,
    size: 100,
    height: 40,
    curveSegments: 12,
    bevelEnabled: true,
    bevelThickness: 30,
    bevelSize: 8,
    bevelOffset: 1,
    bevelSegments: 12
  });
  textMesh.material = material;
  scene.add(textMesh);
});

💡 FontLoader Font Loader

Use a class for loading fonts in JSON format, return Font , the return value is an array of type Shape representing the font, which internally uses FileLoader to load the file.

constructor :

FontLoader(manager: LoadingManager)
  • manager : 06228032a90f91 used by the loadingManager , default is THREE.DefaultLoadingManager .

method:

  • .load from URL and passes the loaded texture to onLoad .

    • .load(url: String, onLoad: Function, onProgress: Function, onError: Function): null
    • url : The URL or path of the file, or Data URI .
    • onLoad : Will be called when loading is complete. The callback parameter is texture to be loaded.
    • onProgress : Will be called during loading. The parameter is an instance of XMLHttpRequest , containing total and loaded bytes.
    • onError : Called on load error.
  • .parse is parsed in JSON format and returns a Font .

    • .parse (json: Object ): Font
    • json : JSON structure for parsing.

💡 TextGeometry Text geometry

A class for generating text into a single geometry constructed from a given string of text and parameters consisting of the loaded Font font and the settings in the ExtrudeGeometry parent class of that geometry.

constructor :

TextGeometry(text: String, parameters: Object)
  • text : The text that will be displayed.
  • parameters

    • font[Font] : THREE.Font instance.
    • size[Float] : font size, default is 100 .
    • height[Float] : The thickness of the extruded text, the default value is 50 .
    • curveSegments[Integer] : The number of points on the curve representing the text, the default value is 12 .
    • bevelEnabled[Boolean] : Whether to enable bevel, the default is false .
    • bevelThickness[Float] : The depth of the text bevel, the default value is 20 .
    • bevelSize[Float] : The extension distance between the bevel and the original text outline, the default is 8 .
    • bevelSegments[Integer] : Number of segments for the bevel, default is 3 .
🔗 can convert Three.js supported by online using facetype.js 16228032a91341.

Create geometry model

Decorate the page with the other 3 built-in geometry models Torus, Cone and Octahedron . The number of decorative geometry is relatively large. In order to effectively improve page performance, the following two points need to be paid attention to:

// 批量创建模型方法
generateRandomMesh = (geometry, material, count) => {
  for (let i = 0; i < count; i++) {
    let mesh = new THREE.Mesh(geometry, material);
    let dist = farDist / 3;
    let distDouble = dist * 2;
    // 设置随机的位置和旋转角度
    mesh.position.x = Math.random() * distDouble - dist;
    mesh.position.y = Math.random() * distDouble - dist;
    mesh.position.z = Math.random() * distDouble - dist;
    mesh.rotation.x = Math.random() * 2 * Math.PI;
    mesh.rotation.y = Math.random() * 2 * Math.PI;
    mesh.rotation.z = Math.random() * 2 * Math.PI;
    // 手动控制何时重新计算3D变换以获得更好的性能
    mesh.matrixAutoUpdate = false;
    mesh.updateMatrix();
    group.add(mesh);
  }
}
// 创建100个八面体
const octahedronGeometry = new THREE.OctahedronBufferGeometry(80);
generateRandomMesh(octahedronGeometry, material, 100);
// 创建200个圆环面
const torusGeometry = new THREE.TorusBufferGeometry(40, 25, 16, 40);
generateRandomMesh(torusGeometry, material, 200);
// 创建100个圆锥
const coneGeometry = new THREE.ConeBufferGeometry(40, 80, 80);
generateRandomMesh(coneGeometry, material, 100);
scene.add(group);

💡 TorusBufferGeometry Torus buffer geometry

Class for generating torus geometry.

constructor :

TorusBufferGeometry(radius: Float, tube: Float, radialSegments: Integer, tubularSegments: Integer, arc: Float)

💡 ConeBufferGeometry Cone Buffer Geometry

Class for generating cone geometry.

constructor :

ConeBufferGeometry(radius: Float, height: Float, radialSegments: Integer, heightSegments: Integer, openEnded: Boolean, thetaStart: Float, thetaLength: Float)

💡 OctahedronBufferGeometry Octahedron buffer geometry

Class for creating octahedrons.

constructor :

OctahedronBufferGeometry(radius: Float, detail: Integer)

mouse event listener

Add listener methods for mouse movement and touch movement events by converting between 🖱 mouse movement coordinates and model coordinates.

const mouseFX = {
  windowHalfX: window.innerWidth / 2,
  windowHalfY: window.innerHeight / 2,
  coordinates: (coordX, coordY) => {
    mouseX = (coordX - mouseFX.windowHalfX) * 5;
    mouseY = (coordY - mouseFX.windowHalfY) * 5;
  },
  onMouseMove: e => { mouseFX.coordinates(e.clientX, e.clientY) },
  onTouchMove: e => { mouseFX.coordinates(e.changedTouches[0].clientX, e.changedTouches[0].clientY)}
};
document.addEventListener('mousemove', mouseFX.onMouseMove, false);
document.addEventListener('touchmove', mouseFX.onTouchMove, false);

background color switch

Use a input[type='color'] tag to toggle the background color.

handleInputChange = e => {
  this.setState({ backgroundColor: e.target.value });
}

post rendering

For a more punchy visual effect 💥 , I added a glitch style post-render effect and used a button switch to turn the effect on and off.

composer = new EffectComposer(renderer);
composer.addPass( new RenderPass(scene, camera));
glitchPass = new GlitchPass();
composer.addPass(glitchPass);
handleRenderChange = () => {
  this.setState({ renderGlithPass: !this.state.renderGlithPass });
}

💡 Post rendering

Three.js Post-rendering processing is the process of achieving the desired visual effect by superimposing rendering channels. The implementation process is as follows:

💡 GlitchPass glitch style pass

GlitchPass channel produces an analog glitch style effect, it has only one optional configuration parameter:

📌 Three.js provides many post-processing channels, which can be used directly. The ShaderPass channel is also provided, which supports the use of custom Shader to create advanced custom post-processing channels.

animation

Updated scene, camera, and post render passes in requestAnimationFrame .

function animate() {
  requestAnimationFrame(animate);
  camera.position.x += (mouseX - camera.position.x) * 0.05;
  camera.position.y += (mouseY * -1 - camera.position.y) * 0.05;
  camera.lookAt(scene.position);
  // 给场景中的立方体网格和字体网格添加自转动画
  const t = Date.now() * 0.001;
  const rx = Math.sin(t * 0.7) * 0.5;
  const ry = Math.sin(t * 0.3) * 0.5;
  const rz = Math.sin(t * 0.2) * 0.5;
  group.rotation.x = rx;
  group.rotation.y = ry;
  group.rotation.z = rz;
  textMesh.rotation.x = rx;
  textMesh.rotation.y = ry;
  textMesh.rotation.z = rx;
  renderer.render(scene, camera);
  // 更新后期渲染通道
  composer.render();
}

zoom fit

renderer and composer should be resized at the same time.

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  composer.setSize( window.innerWidth, window.innerHeight );
}, false);

Double tap to full screen

Monitor the page 🖱 double-click dblclick event, and enter or exit the full-screen state by calling requestFullscreen and exitFullscreen .

window.addEventListener('dblclick', () => {
  let fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement;
  if (!fullscreenElement) {
    if (canvas.requestFullscreen) {
      canvas.requestFullscreen();
    } else if (canvas.webkitRequestFullscreen) {
      canvas.webkitRequestFullscreen();
    }
    console.log('进入全屏')
  } else {
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    }
    console.log('退出全屏')
  }
})

💡 Element.requestFullscreen

Element.requestFullscreen method is used to make an asynchronous request to put the element into full screen mode. Calling this API does not guarantee that the element will be able to enter fullscreen mode . If the element is allowed to enter full screen mode, the returned Promise will be resolve , and the element will receive a fullscreenchange event notifying it that it has entered full screen mode. If the fullscreen request is rejected, the returned promise becomes rejected and the element receives a fullscreenerror event. If the element has been detached from the original document, the document will receive these events.

syntax :

var Promise = Element.requestFullscreen(options);
📌 This method can only be called when the user interacts or the device orientation changes, otherwise it will fail. FullscreenOptions The only option currently is navigationUI , which controls whether the navbar is shown when the element is in fullscreen mode UI . The default value is auto , indicating that it will be up to the browser to display the navigation bar.

💡 Document.exitFullscreen

Document.exitFullscreen method is used to take the current document out of full screen mode. Calling this method will roll back the document to the state it was in before the last call to the Element.requestFullscreen method to enter full screen mode.

Syntax :

document.exitFullscreen();

At this point, all the functions of the example page are completed, you can visit the following link to view the complete code 😀 .

🔗 full code: https://github.com/dragonir/3d/tree/master/src/containers/Floating

Summarize

The new knowledge mainly included in the knowledge points of this article:

  • CSS grid background
  • MeshNormalMaterial normal material
  • FontLoader font loader
  • TextGeometry Text Buffer Geometry
  • TorusBufferGeometry Ring buffer geometry
  • ConeBufferGeometry Conical buffer geometry
  • OctahedronBufferGeometry Buffer Geometry
  • Three.js post rendering
  • GlitchPass channel
  • Element.requestFullscreen
  • Document.exitFullscreen
To learn about scene initialization, lighting, shadows, basic geometry, meshes, materials, and other Three.js related knowledge, you can read my previous articles. Please indicate the original address and author . If you think the article is helpful to you, don't forget to one-click three links 👍 .

appendix


dragonir
1.8k 声望3.9k 粉丝

Accepted ✔