three.js如何實(shí)現(xiàn)3D動(dòng)態(tài)文字效果
前言
大家好,這里是 CSS 魔法使——alphardex。
之前在逛國(guó)外網(wǎng)站的時(shí)候,發(fā)現(xiàn)有些網(wǎng)站的文字是刻在3D圖形上的,并且能在圖形上運(yùn)動(dòng),視覺效果相當(dāng)不錯(cuò),于是筆者就也想用three.js來(lái)嘗試復(fù)現(xiàn)出這種效果

上圖只是所有效果的其中之一,接下來(lái)讓我們一起開干吧~
準(zhǔn)備工作
筆者自行封裝的three.js模板:Three.js Starter
讀者可以點(diǎn)擊右下角fork一份后再開始本項(xiàng)目
本項(xiàng)目需要用到位圖字體,可以直接復(fù)制demo的HTML里的font字體代碼
一個(gè)注意點(diǎn):three-bmfont-text這個(gè)庫(kù)依賴全局的three.js,因此要在JS里額外引入一次three.js,如下圖

實(shí)現(xiàn)思路
- 加載位圖字體文件,將其轉(zhuǎn)化為文字對(duì)象所需要的形狀和材質(zhì)
- 創(chuàng)建文字對(duì)象
- 創(chuàng)建渲染目標(biāo),可以理解為canvas中的canvas,因?yàn)榻酉聛?lái)我們要將文字對(duì)象本身當(dāng)做貼圖
- 創(chuàng)建承載字體的容器,將文字對(duì)象作為貼圖貼上去
- 動(dò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而來(lái)的字體代碼 </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 = `(頂點(diǎn)著色器代碼,先空著,具體見下文)`;
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)建動(dòng)態(tài)文字
async createKineticText(text: string) {
await this.createFontText(text);
this.createRenderTarget();
this.createTextContainer();
}
}
加載和創(chuàng)建字體
首先加載字體文件,并創(chuàng)建出形狀和材質(zhì),有了這兩樣就能創(chuàng)建出字體對(duì)象了
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;
}
}
著色器
頂點(diǎn)著色器
通用模板,直接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使得貼圖能隨著時(shí)間的增加而動(dòng)起來(lái),再用clamp函數(shù)來(lái)根據(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.);
}
此時(shí)文本顯示到了屏幕上
創(chuàng)建渲染目標(biāo)
為了將字體對(duì)象本身作為貼圖,創(chuàng)建了一個(gè)渲染目標(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)建一個(gè)容器,并將字體對(duì)象本身作為貼圖貼上去,再應(yīng)用動(dò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;
}
}
}
別忘了把相機(jī)調(diào)遠(yuǎn)一些
this.cameraPosition = new THREE.Vector3(0, 0, 40);
風(fēng)騷的動(dòng)態(tài)文字出現(xiàn)了:)

項(xiàng)目地址
demo里不止本文創(chuàng)建的這一種形狀,大家可以隨意把玩。
總結(jié)
到此這篇關(guān)于three.js如何實(shí)現(xiàn)3D動(dòng)態(tài)文字效果的文章就介紹到這了,更多相關(guān)three.js 3D動(dòng)態(tài)文字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS事件處理機(jī)制及事件代理(事件委托)實(shí)例詳解
這篇文章主要介紹了JS事件處理機(jī)制及事件代理,結(jié)合實(shí)例形式詳細(xì)分析了JS時(shí)間處理機(jī)制與事件代理功能、用法及相關(guān)使用技巧,需要的朋友可以參考下2023-06-06
JS操作字符串轉(zhuǎn)換為數(shù)值并取整的代碼
這篇文章主要介紹了JS操作字符串轉(zhuǎn)換為數(shù)值并取整的代碼,代碼比較短,需要的朋友可以參考下2014-01-01
js+html5實(shí)現(xiàn)canvas繪制圓形圖案的方法
這篇文章主要介紹了js+html5實(shí)現(xiàn)canvas繪制圓形圖案的方法,涉及html5圖形繪制的基礎(chǔ)技巧,需要的朋友可以參考下2015-06-06
詳解TypeScript中type與interface的區(qū)別
在寫 ts 相關(guān)代碼的過(guò)程中,總能看到 interface 和 type 的身影。它們的作用好像都一樣的,相同的功能用哪一個(gè)都可以實(shí)現(xiàn),也都很好用,所以也很少去真正的理解它們之間到底有啥區(qū)別,因此本文將詳細(xì)講解二者的區(qū)別,需要的可以參考一下2022-04-04
javascript獲取URL參數(shù)與參數(shù)值的示例代碼
本篇文章主要是對(duì)javascript獲取URL參數(shù)與參數(shù)值的示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-12-12
標(biāo)題過(guò)長(zhǎng)使用javascript按字節(jié)截取字符串
在網(wǎng)頁(yè)展示中經(jīng)常會(huì)碰到,標(biāo)題過(guò)長(zhǎng),需要截取字符串,用CSS的實(shí)現(xiàn)的話各種兼容問題,下面為大家介紹下javascript如何按字節(jié)截取字符串2014-04-04

