当前位置: 首页 > news >正文

Three.js 精灵文字教程

精灵文字 ·Sprite Text· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • OrbitControls 相机轨道交互
  • Canvas 动态纹理贴图
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示精灵文字效果:用 Canvas 2D 绘制内容并实时映射为 Three.js 纹理;核心用到 OrbitControls、Canvas。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • OrbitControls轨道旋转缩放;开enableDamping时每帧需controls.update()

实现步骤

  • 搭建 Scene / Camera / Renderer 与 OrbitControls
  • rAF 循环中 update 并 render
  • 代码要点

    import * as THREE from 'three'

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

    const box = document.getElementById('box')

    const scene = new THREE.Scene()

    const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000)

    camera.position.set(0, 10, 10)

    const renderer = new THREE.WebGLRenderer()

    renderer.setSize(box.clientWidth, box.clientHeight)

    box.appendChild(renderer.domElement)

    new OrbitControls(camera, renderer.domElement)

    scene.add(new THREE.AmbientLight(0xffffff, 3), new THREE.AxesHelper(1000))

    animate()

    function animate() {

    requestAnimationFrame(animate)

    renderer.render(scene, camera)

    }

    window.onresize = () => {

    renderer.setSize(box.clientWidth, box.clientHeight)

    camera.aspect = box.clientWidth / box.clientHeight

    camera.updateProjectionMatrix()

    }

    const citys = await fetch('https://z2586300277.github.io/three-editor/dist/files/other/city.json').then(res => res.json()) // 获取城市数据

    const updateCanvasText = createCanvasText({ dpr: 1.4 }) // 创建canvas

    const getColor = () => '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0') // 随机颜色

    for (const key in citys) {

    const canvas = updateCanvasText({ text: key, color: getColor() })

    const texture = new THREE.TextureLoader().load(canvas.toDataURL())

    const material = new THREE.SpriteMaterial({ map: texture })

    const sprite = new THREE.Sprite(material)

    sprite.scale.set(canvas.width / canvas.height, 1, 1)

    // 设置随机位置 sprite.position.set( Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10 )

    scene.add(sprite)

    }

    // 创建canvas文字方法 function createCanvasText(params) {

    const defaultParams = { dpr: 1, maxWidth: 100, fontSize: 20, color: 'white', fontFamily: 'serif', align: 'center', border: false, ...params } // 默认参数

    const { dpr, border, maxWidth, fontSize, align } = defaultParams

    const devicePixelRatio = window.devicePixelRatio * dpr

    // 准备 cnvas const canvas = document.createElement('canvas')

    canvas.width = maxWidth * devicePixelRatio

    canvas.height = fontSize * devicePixelRatio

    // 获取 2d 上下文 const ctx = canvas.getContext('2d')

    ctx.imageSmoothingQuality = 'high'

    ctx.scale(devicePixelRatio, devicePixelRatio)

    // 创建边框 function createBorder() {

    ctx.strokeStyle = '#fff'

    // 创建宽度为10px的边框 ctx.lineWidth = 1 * devicePixelRatio;

    ctx.strokeRect(

    ctx.lineWidth / 2,

    ctx.lineWidth / 2,

    canvas.width / devicePixelRatio - ctx.lineWidth,

    canvas.height / devicePixelRatio - ctx.lineWidth

    )

    }

    // 创建文字 const createText = ({ text, color, fontSize, fontFamily }) => {

    // 参数设定 ctx.fillStyle = color || defaultParams.color

    ctx.font = fontSize || defaultParams.fontSize + 'px ' + fontFamily || defaultParams.fontFamily

    // 文本长度计算 let textMaxNum = 0

    let totalWidth = 0

    for (let i = 0; i < text.length; i++) {

    const metrics = ctx.measureText(text[i])

    totalWidth += metrics.width;

    if (totalWidth > maxWidth) break

    textMaxNum++

    }

    text = text.slice(0, textMaxNum)

    // 文字 绘制 const metrics = ctx.measureText(text) // 文本尺寸

    const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent // 实际文字高度

    const textFillHeight = (canvas.height / devicePixelRatio - actualHeight) / 2 + metrics.actualBoundingBoxAscent

    let textLeftOffset = 0

    if (align === 'center') textLeftOffset = (canvas.width / devicePixelRatio - metrics.width) / 2

    ctx.fillText(text, textLeftOffset, textFillHeight, canvas.width / devicePixelRatio)

    }

    return (parameters) => {

    ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空 canvas 文字

    if (border) createBorder() // 创建边框

    createText(parameters) // 创建文字

    return canvas

    }

    }

    完整源码:GitHub

    小结

    • 本文提供精灵文字完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.jsqmd.com/news/1092690/

相关文章:

  • 【题解-信息学奥赛一本通】1321:【例6.3】删数问题(Noip1994)
  • Minecraft世界区块管理神器:MCA Selector完全指南与实战技巧
  • Codex MCP server failed MCP 服务启动失败处理
  • 诊断:Docker 登录失败 Error response from daemon: login attempt to http://XXXXXXXX/v2/ 的深层网络与代理配置探析
  • 如何用SPT-AKI存档编辑器快速掌控你的逃离塔科夫离线版游戏体验
  • MicroPython BLE HID库:零基础打造无线控制设备的终极指南
  • 3步轻松修复损坏视频:开源神器Untrunc让你不再丢失珍贵回忆 [特殊字符]
  • 超越Nmap:Zmap与Zgrab2构建企业级外网资产地图实战
  • 如何用ctfileGet实现城通网盘免等待下载:3个关键技术解析
  • 一键解锁浏览器多任务:Chrome画中画扩展完全指南
  • PCM5242音频DAC电源管理与寄存器配置实战指南
  • 告别远程控制烦恼:BilldDesk开源方案如何彻底改变你的跨平台协作体验
  • 如何用LRCGET批量下载歌词:5步解决离线音乐库歌词同步问题
  • AppleRa1n终极指南:iOS 15-16设备iCloud激活锁绕过技术解析
  • Win11Debloat终极指南:3分钟让你的Windows 11性能飙升70%
  • Halcon手眼标定实战:Eye-in-Hand场景下移动相机标定全流程解析
  • 配置中心——让配置“云同步“
  • 3步解锁隐藏BIOS:让你的联想拯救者性能全面释放
  • OpenDesign Skills 完全指南:一站式 AI 编码工具知识库
  • MSPM0 H系列TIMx定时器:从基础计数到高级PWM的实战指南
  • 收藏!AI转型必看:老板先上手,开箱即用让团队轻松跟上
  • 人形机器人零件加工,选报价最低的服务商反而更贵?
  • 评测:国内主流S2B2C系统服务商全方位横评(2026版)
  • MCA Selector:3步学会管理Minecraft世界区块,释放硬盘空间不再难![特殊字符]
  • [智能体-594]:OpenClaw 中 Tool(工具)与 Skill(技能)完整异同解析
  • qmcdump:轻松解密QQ音乐加密文件,实现跨平台自由播放
  • 如何用League Akari实现英雄联盟自动秒选:终极配置指南
  • 幕布导出管道的技术实现——大纲文档节点树到Word/PDF/图片多格式渲染链路分析
  • 进销存出入库怎么做最省事?掌握这套操作流程就够了
  • League Akari终极指南:8个秘诀掌握英雄联盟自动秒选黑科技