前言
大家好,這里是 CSS 魔法使――alphardex。
之前在逛國外網(wǎng)站的時候,發(fā)現(xiàn)有些網(wǎng)站的文字是刻在3D圖形上的,并且能在圖形上運動,視覺效果相當(dāng)不錯,于是筆者就也想用three.js來嘗試復(fù)現(xiàn)出這種效果
上圖只是所有效果的其中之一,接下來讓我們一起開干吧~
準(zhǔn)備工作
筆者自行封裝的three.js模板:Three.js Starter
讀者可以點擊右下角fork一份后再開始本項目
本項目需要用到位圖字體,可以直接復(fù)制demo的HTML里的font字體代碼
一個注意點:three-bmfont-text這個庫依賴全局的three.js,因此要在JS里額外引入一次three.js,如下圖
實現(xiàn)思路
- 加載位圖字體文件,將其轉(zhuǎn)化為文字對象所需要的形狀和材質(zhì)
- 創(chuàng)建文字對象
- 創(chuàng)建渲染目標(biāo),可以理解為canvas中的canvas,因為接下來我們要將文字對象本身當(dāng)做貼圖
- 創(chuàng)建承載字體的容器,將文字對象作為貼圖貼上去
- 動畫
正片
搭好架子
<div class="relative w-screen h-screen"> <div class="kinetic-text w-full h-full bg-blue-1"></div> <div class="font"> <font> 一坨從demo里CV而來的字體代碼 </font> </div> </div>
:root { --blue-color-1: #2c3e50; } .bg-blue-1 { background: var(--blue-color-1); }
import createGeometry from "https://cdn.skypack.dev/three-bmfont-text@3.0.1"; import MSDFShader from "https://cdn.skypack.dev/three-bmfont-text@3.0.1/shaders/msdf"; import parseBmfontXml from "https://cdn.skypack.dev/parse-bmfont-xml@1.1.4"; const font = parseBmfontXml(document.querySelector(".font").innerHTML); const fontAtlas = "https://i.loli.net/2021/02/20/DcEhuYNjxCgeU42.png"; const kineticTextTorusKnotVertexShader = `(頂點著色器代碼,先空著,具體見下文)`; const kineticTextTorusKnotFragmentShader = `(片元著色器代碼,先空著,具體見下文)`; class KineticText extends Base { constructor(sel: string, debug: boolean) { super(sel, debug); this.cameraPosition = new THREE.Vector3(0, 0, 4); this.clock = new THREE.Clock(); this.meshConfig = { torusKnot: { vertexShader: kineticTextTorusKnotVertexShader, fragmentShader: kineticTextTorusKnotFragmentShader, geometry: new THREE.TorusKnotGeometry(9, 3, 768, 3, 4, 3) } }; this.meshNames = Object.keys(this.meshConfig); this.params = { meshName: "torusKnot", velocity: 0.5, shadow: 5, color: "#000000", frequency: 0.5, text: "ALPHARDEX", cameraZ: 2.5 }; } // 初始化 async init() { this.createScene(); this.createPerspectiveCamera(); this.createRenderer(true); await this.createKineticText(this.params.text); this.createLight(); this.createOrbitControls(); this.addListeners(); this.setLoop(); } // 創(chuàng)建動態(tài)文字 async createKineticText(text: string) { await this.createFontText(text); this.createRenderTarget(); this.createTextContainer(); } }
加載和創(chuàng)建字體
首先加載字體文件,并創(chuàng)建出形狀和材質(zhì),有了這兩樣就能創(chuàng)建出字體對象了
class KineticText extends Base { loadFontText(text: string): any { return new Promise((resolve) => { const fontGeo = createGeometry({ font, text }); const loader = new THREE.TextureLoader(); loader.load(fontAtlas, (texture) => { const fontMat = new THREE.RawShaderMaterial( MSDFShader({ map: texture, side: THREE.DoubleSide, transparent: true, negate: false, color: 0xffffff }) ); resolve({ fontGeo, fontMat }); }); }); } async createFontText(text: string) { const { fontGeo, fontMat } = await this.loadFontText(text); const textMesh = this.createMesh({ geometry: fontGeo, material: fontMat }); textMesh.position.set(-0.965, -0.525, 0); textMesh.rotation.set(ky.deg2rad(180), 0, 0); textMesh.scale.set(0.008, 0.025, 1); this.textMesh = textMesh; } }
著色器
頂點著色器
通用模板,直接CV即可
varying vec2 vUv; varying vec3 vPosition; void main(){ vec4 modelPosition=modelMatrix*vec4(position,1.); vec4 viewPosition=viewMatrix*modelPosition; vec4 projectedPosition=projectionMatrix*viewPosition; gl_Position=projectedPosition; vUv=uv; vPosition=position; }
片元著色器
利用fract函數(shù)創(chuàng)建重復(fù)的貼圖,加上位移距離displacement使得貼圖能隨著時間的增加而動起來,再用clamp函數(shù)來根據(jù)z軸大小限定陰影的范圍,意思是離畫面越遠(yuǎn)則陰影越重,反之離畫面越近則陰影越輕
uniform sampler2D uTexture; uniform float uTime; uniform float uVelocity; uniform float uShadow; varying vec2 vUv; varying vec3 vPosition; void main(){ vec2 repeat=vec2(12.,3.); vec2 repeatedUv=vUv*repeat; vec2 displacement=vec2(uTime*uVelocity,0.); vec2 uv=fract(repeatedUv+displacement); vec3 texture=texture2D(uTexture,uv).rgb; // texture*=vec3(uv.x,uv.y,1.); float shadow=clamp(vPosition.z/uShadow,0.,1.);// farther darker (to 0). vec3 color=vec3(texture*shadow); gl_FragColor=vec4(color,1.); }
此時文本顯示到了屏幕上
創(chuàng)建渲染目標(biāo)
為了將字體對象本身作為貼圖,創(chuàng)建了一個渲染目標(biāo)
class KineticText extends Base { createRenderTarget() { const rt = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight ); this.rt = rt; const rtCamera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000); rtCamera.position.z = this.params.cameraZ; this.rtCamera = rtCamera; const rtScene = new THREE.Scene(); rtScene.add(this.textMesh); this.rtScene = rtScene; } }
創(chuàng)建字體容器
創(chuàng)建一個容器,并將字體對象本身作為貼圖貼上去,再應(yīng)用動畫即可完成
class KineticText extends Base { createTextContainer() { if (this.mesh) { this.scene.remove(this.mesh); this.mesh = null; this.material!.dispose(); this.material = null; } this.rtScene.background = new THREE.Color(this.params.color); const meshConfig = this.meshConfig[this.params.meshName]; const geometry = meshConfig.geometry; const material = new THREE.ShaderMaterial({ vertexShader: meshConfig.vertexShader, fragmentShader: meshConfig.fragmentShader, uniforms: { uTime: { value: 0 }, uVelocity: { value: this.params.velocity }, uTexture: { value: this.rt.texture }, uShadow: { value: this.params.shadow }, uFrequency: { value: this.params.frequency } } }); this.material = material; const mesh = this.createMesh({ geometry, material }); this.mesh = mesh; } update() { if (this.rtScene) { this.renderer.setRenderTarget(this.rt); this.renderer.render(this.rtScene, this.rtCamera); this.renderer.setRenderTarget(null); } const elapsedTime = this.clock.getElapsedTime(); if (this.material) { this.material.uniforms.uTime.value = elapsedTime; } } }
別忘了把相機調(diào)遠(yuǎn)一些
this.cameraPosition = new THREE.Vector3(0, 0, 40);
風(fēng)騷的動態(tài)文字出現(xiàn)了:)
項目地址
demo里不止本文創(chuàng)建的這一種形狀,大家可以隨意把玩。
總結(jié)
到此這篇關(guān)于three.js如何實現(xiàn)3D動態(tài)文字效果的文章就介紹到這了,更多相關(guān)three.js 3D動態(tài)文字內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://juejin.cn/post/6931647184349691911