three.js如何实现3D动态文字效果
这篇文章主要给大家介绍了关于three.js如何实现3D动态文字效果的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
前言
大家好,这里是 CSS 魔法使——alphardex。
之前在逛国外网站的时候,发现有些网站的文字是刻在3D图形上的,并且能在图形上运动,视觉效果相当不错,于是笔者就也想用three.js来尝试复现出这种效果
上图只是所有效果的其中之一,接下来让我们一起开干吧~
准备工作
笔者自行封装的three.js模板:Three.js Starter
读者可以点击右下角fork一份后再开始本项目
本项目需要用到位图字体,可以直接复制demo的HTML里的font字体代码
一个注意点:three-bmfont-text这个库依赖全局的three.js,因此要在JS里额外引入一次three.js,如下图
实现思路
加载位图字体文件,将其转化为文字对象所需要的形状和材质
创建文字对象
创建渲染目标,可以理解为canvas中的canvas,因为接下来我们要将文字对象本身当做贴图
创建承载字体的容器,将文字对象作为贴图贴上去
动画
正片
搭好架子
1 2 3 4 5 6 7 8 | < 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 > |
1 2 3 4 5 6 7 | :root { --blue-color -1: #2c3e50 ; } .bg-blue -1 { background : var(--blue-color -1 ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | const font = parseBmfontXml(document.querySelector( ".font" ).innerHTML); 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(); } // 创建动态文字 async createKineticText(text: string) { await this .createFontText(text); this .createRenderTarget(); this .createTextContainer(); } } |
加载和创建字体
首先加载字体文件,并创建出形状和材质,有了这两样就能创建出字体对象了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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即可
1 2 3 4 5 6 7 8 9 10 11 12 | 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函数创建重复的贴图,加上位移距离displacement使得贴图能随着时间的增加而动起来,再用clamp函数来根据z轴大小限定阴影的范围,意思是离画面越远则阴影越重,反之离画面越近则阴影越轻
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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.); } |
此时文本显示到了屏幕上
创建渲染目标
为了将字体对象本身作为贴图,创建了一个渲染目标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } } |
创建字体容器
创建一个容器,并将字体对象本身作为贴图贴上去,再应用动画即可完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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; } } } |
别忘了把相机调远一些
1 | this .cameraPosition = new THREE.Vector3(0, 0, 40); |
风骚的动态文字出现了:)
项目地址
demo里不止本文创建的这一种形状,大家可以随意把玩。
总结
到此这篇关于three.js如何实现3D动态文字效果的文章就介绍到这了