three.js实战-Sprite实现标签效果

news2025/1/10 17:22:10

1. demo效果

在这里插入图片描述

2 .什么是精灵(Sprite)

按照Three.js官网的解释是:精灵是一个总是面朝着摄像机的平面,通常含有使用一个半透明的纹理。精灵不会投射任何阴影,即使设置了也将不会有任何效果。

3. 代码大致逻辑

  1. 创建一个canvas对象,首先调用ctx.measureText方法计算画布渲染的文字宽度。
  2. 根据上一步计算的宽度,调用roundRect1方法绘制圆角矩形背景框。
  3. 在canvas上绘制字体。
  4. 将canvas传入new THREE.Texture方法生成纹理贴图。
  5. 调用new THREE.Sprite方法创建Sprite对象添加到scene种。

4. demo代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>three.js webgl</title>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
    />
    <style>
      body {
        color: #fff;
        font-family: Monospace;
        font-size: 13px;
        text-align: center;
        background-color: #fff;
        margin: 0px;
        overflow: hidden;
      }
    </style>
    <script src="./three.js"></script>
    <script src="./OrbitControls.js"></script>

  </head>
  <body>
    <div id="container"></div>

    <script>
      let scene, camera, controls, renderer;
      let container = document.getElementById("container");

      renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      container.appendChild(renderer.domElement);

      scene = new THREE.Scene();

      camera = new THREE.PerspectiveCamera(
        40,
        window.innerWidth / window.innerHeight,
        1,
        100000
      );
      camera.position.set(5, 5, 5);
      scene.add(camera);

      controls = new THREE.OrbitControls(camera, renderer.domElement);

      scene.add(new THREE.AxesHelper(50));

      animate();

      function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
      }
      //绘制圆角矩形
      const roundRect1 = (ctx, x, y, w, h, r) => {
        //Canvas 2D API 通过清空子路径列表开始一个新路径的方法。当你想创建一个新的路径时,调用此方法。
        ctx.beginPath();
        //moveTo方法移动画笔到起始点绘制一条线。
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);

        ctx.quadraticCurveTo(x + w, y, x + w, y + r);
        ctx.lineTo(x + w, y + h - r);
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
        ctx.lineTo(x + r, y + h);
        ctx.quadraticCurveTo(x, y + h, x, y + h - r);
        ctx.lineTo(x, y + r);
        ctx.quadraticCurveTo(x, y, x + r, y);
        ctx.closePath();
        ctx.fill();
        ctx.stroke();
      };
      const makeTextTexture = (context2d, message, parameters) => {
        const { canvas, context } = context2d;
        if (!context) {
          return;
        }

        const textHeight = Math.ceil(parameters.fontsize * 1.18);
        /* 绘制圆角矩形 */
        roundRect1(
          context,
          parameters.margin + parameters.border,
          parameters.margin + parameters.border,
          parameters.width - parameters.margin,
          parameters.height - parameters.margin,
          parameters.radius
        );
        /* 字体颜色 */
        context.fillStyle = "rgba(0, 0, 0, 1.0)";
        let xOffset = parameters.fontsize * 0.4;
        let yOffset = parameters.fontsize * 0.2;

        //(msg,x,y) msg绘制的文本,x,y文本绘制的位置
        context.fillText(
          message,
          parameters.margin + parameters.border + xOffset,
          textHeight + parameters.border + parameters.margin + yOffset
        );

        /* 画布内容用于纹理贴图 */
        texture = new THREE.Texture(canvas);
        texture.needsUpdate = true;

        return texture;
      };
      const createCanvas2D = (width = 512, height = 256) => {
        let canvas = document.createElement("canvas");
        canvas.width = width;
        canvas.height = height;
        return canvas;
      };
      const getContext2D = (canvas, parameters) => {
        let context = null;

        do {
          context = canvas.getContext("2d");
          if (!context) {
            break;
          }

          let fontface = parameters?.hasOwnProperty("fontface")
            ? parameters["fontface"]
            : "Arial";

          /* 字体大小 */
          let fontsize =
            parameters?.hasOwnProperty("fontsize") && parameters["fontsize"]
              ? parameters["fontsize"]
              : 18;

          /* 边框厚度 */
          let border =
            parameters?.hasOwnProperty("border") && parameters["border"]
              ? parameters["border"]
              : 4;

          /* 边框颜色 */
          let borderColor =
            parameters?.hasOwnProperty("borderColor") &&
            parameters["borderColor"]
              ? parameters["borderColor"]
              : { r: 0, g: 0, b: 0, a: 1.0 };

          /* 背景颜色 */
          let backgroundColor = { r: 255, g: 255, b: 255, a: 1.0 };
          if (
            parameters &&
            "backgroundColor" in parameters &&
            parameters["backgroundColor"]
          ) {
            backgroundColor = parameters["backgroundColor"];
          }

          if (parameters.bold) {
            /* 字体加粗 */
            context.font = "Bold " + fontsize + "px " + fontface;
          } else {
            /* 字体加粗 */
            context.font = fontsize.toString() + "px " + fontface;
          }

          /* 获取文字的大小数据,高度取决于文字的大小 */
          //let metrics = context.measureText( message );
          //let textWidth = metrics.width;

          /* 背景颜色 */
          context.fillStyle =
            "rgba(" +
            backgroundColor.r +
            "," +
            backgroundColor.g +
            "," +
            backgroundColor.b +
            "," +
            backgroundColor.a +
            ")";

          /* 边框的颜色 */
          context.strokeStyle =
            "rgba(" +
            borderColor.r +
            "," +
            borderColor.g +
            "," +
            borderColor.b +
            "," +
            borderColor.a +
            ")";
          context.lineWidth = border;

          //width = textWidth + borderThickness;
          //height = fontsize * 1.4 + borderThickness;
        } while (false);

        return context;
      };
      /* 初始化canvas和ctx */
      const initializeContext2d = (parameters) => {
        const canvas = createCanvas2D(parameters.width, parameters.height);
        const context = getContext2D(canvas, parameters);
        return {
          canvas: canvas,
          context: context,
        };
      };
      //创建sprite标签
      const makeTextSprite = (message, parameters, context2d) => {
        const texture = makeTextTexture(context2d, message, parameters);
        const spriteMaterial = new THREE.SpriteMaterial({
          size: { x: 0.2, y: 0.2 },
          center: {
            x: 0,
            y: 0.6,
          },
          map: texture,
        });
        const sprite = new THREE.Sprite(spriteMaterial);
        /* 缩放比例 */

        return sprite;
      };
      const measureText = (context2d, message) => {
        const { canvas, context } = context2d;
        if (!context) {
          return;
        }
        /* 获取文字的大小数据,高度取决于文字的大小 */
        let textMetrics = context.measureText(message);

        return textMetrics;
      };
      const initializeParammeters = (context2d, text, textureParameters) => {
        const textSize = measureText(context2d, text);
        textureParameters.width = Math.ceil(
          textSize.width +
            textureParameters.border * 2 +
            textureParameters.margin * 2
        );
        textureParameters.height = Math.ceil(
          textureParameters.fontsize * 1.18 +
            textureParameters.border * 2 +
            textureParameters.margin * 2
        );

        return textureParameters;
      };
      const params = {
        width: 128,
        height: 128,
        radius: 6, //3,
        border: 2, //3,
        margin: 8, //2,
        fontsize: 16,
        bold: false,
        fontface: "Arial",
        borderColor: { r: 136, g: 136, b: 136, a: 1 } /* 边框颜色 */,
        backgroundColor: { r: 255, g: 255, b: 255, a: 1 } /* 背景颜色 */,
      };

      const msg = "我是标签39km"
      //初始化canvas
      const context2d = initializeContext2d(params);
      //根据文字动态计算parameters的宽度属性
      const parameters = initializeParammeters(context2d, msg, params);
      scene.add(makeTextSprite(msg, parameters, context2d));
    </script>
  </body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/118455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

密码学_DES加密算法

目录 DES&#xff08;Data Encryption Standard&#xff09; IP置换&#xff1a; E盒扩展 S盒压缩 P盒置换 K密钥生成 PC-1置换表&#xff08;通常用此表&#xff09;&#xff1a; PC-2置换表&#xff08;通常用此表&#xff09;&#xff1a; IP-1逆置换表 DES&#x…

MyBatis的SQL执行结果和客户端执行结果不一致问题排查

MyBatis的SQL执行结果和客户端执行结果不一致问题排查问题引入测试表、测试数据问题介绍排查问题调试 MyBatis源码JDBC 执行 SQL解决问题待解决问题最近遇到一个调试很久的问题&#xff0c;MyBatis 查询 Oracle 数据库查询结果与在客户端查询结果不一致。 问题引入 测试表、测…

自动化审批流程有哪些?使用中的优点是什么

自动化审批对HR管理有多重要呢&#xff1f;相信每一位HR都会希望让审批流程实现自动化&#xff0c;从而释放更多的时间去处理更加复杂的工作。 在人力资源管理过程中&#xff0c;自动化审批可以有效帮助HR提高流程效率。当管理人员每天收到很多审批请求之后&#xff0c;如果不…

AVS3中的AMVR和EMVR

在AVS2中运动预测中使用的MV都是1/4像素精度&#xff0c;通过在整像素间插值能显著提升非整像素运动预测的精度&#xff0c;同时带来的问题是随着MV精度的提高编码MVD所需的比特数也会增加。 AMVR AMVR支持的MVD编码5种精度的MVR{1/4,1/2,1,2,4}&#xff0c;索引为0到4&#x…

IU5706 外置MOS、33V输出大功率同步升压芯片产品介绍

概要 IU5706E是高性能宽输入范围&#xff08;4.5V~24V&#xff09;同步升压控制器&#xff0c;支持高达33V的输出电压。输出电压采用恒定频率电流模式脉宽调制&#xff08;PWM&#xff09;控制来实现调节。 芯片通过外部定时电阻器或通过与外部时钟信号同步来设置开关频率。在电…

PGL 系列(五)DeepWalk

DeepWalk 通过随机游走(truncated random walk)学习出一个网络的表示,在网络标注顶点很少的情况也能得到比较好的效果。随机游走起始于选定的节点,然后从当前节点移至随机邻居,并执行一定的步数,该方法大致可分为四个步骤: (a) 展示了原始的用户行为序列。(b) 基于这些用…

Redis架构 - Sentinel哨兵模式

简介 Redis Sentinel是Redis官方提供的一个高可用方案。是一种用于监控、提醒和自动故障转移的系统。它可以监控多个Redis实例&#xff0c;并在主服务器出现故障时执行故障转移&#xff0c;将从服务器升级为主服务器。 在Sentinel模式下&#xff0c;可以设置多个Sentinel实例…

6.1 函数基础

文章目录编写函数调用函数形参和实参函数的形参列表函数的返回类型局部对象自动对象局部静态对象函数声明在头文件中进行函数的声明分离式编译编译和链接多个源文件一个典型的函数 定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的 列表以及函数体。其中&#xff0…

数据库,计算机网络、操作系统刷题笔记20

数据库&#xff0c;计算机网络、操作系统刷题笔记20 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

第三十章 数论——扩展中国剩余定理

第三十章 数论——扩展中国剩余定理一、中国剩余定理的弊端二、扩展中国剩余定理1、作用2、内容3、问题4、代码一、中国剩余定理的弊端 在第二十九章中&#xff0c;作者详细地讲解了中国剩余定理的使用&#xff0c;在开始本章节的讲解之前&#xff0c;建议读者先去看上一章节的…

Tensorflow2 图像分类-Flowers数据深度学习模型保存、读取、参数查看和图像预测

目录 1.原文完整代码 1.1 模型运行参数总结 1.2模型训练效果 ​编辑2.模型的保存 3.读取模型model 4.使用模型进行图片预测 5.补充 如何查看保存模型参数 5.1 model_weights 5.2 optimizer_weights 使用之前一篇代码&#xff1a; 原文链接&#xff1a;Tensorflow2 图像分…

English Learning - L1-7 介词 2022.12.26 周一

English Learning - L1-7 介词 2022.12.26 周一7 介词7.1 介词功能 1 - 表示动作的方向&#xff0c;范围和程度7.2 介词功能 2 - 胶水词&#xff0c;链接不同的名词7.3 介词功能 3 - 与 be 动词连用代替动词7.4 江南四大介词on核心意义&#xff1a;在。。。上; 在&#xff08;某…

融云 x OHLA:「社交+游戏」双轮驱动,逐鹿中东陌生人社交

完整报告&#xff0c;关注公众号文章限免下载 走过十多年的出海历程&#xff0c;中国创业者面临的机遇和挑战正在发生根本性变化。TikTok、SHEIN 在全球大获全胜的背后&#xff0c;不仅有中国产业链成熟、工程师红利的厚积薄发&#xff0c;也有一代代出海人布局全球商业路径的思…

Ubuntu20.04部署KVM并安装Ubuntu Server 20.04

kvm虚拟化技术 KVM介绍 KVM是Linux开源社区大力支持的虚拟化技术&#xff0c;基于Intel和AMD的硬件虚拟化技术。KVM&#xff08;Kernel-bashdVirtual Machine&#xff0c;即基于内核的虚拟机&#xff09;&#xff0c;它是用于Linux内核中的虚拟化环境设施&#xff0c;是Linux…

python:什么?你听MP3居然还要付费?看我一键......

前言 大家早好、午好、晚好吖 ❤ ~ 在我们上班空闲\游玩\散步的时候,总会习惯的拿出手机放首音乐来听一听 但是吧,有时候我们听一首歌起劲的时候,它会你提醒你 这时候怎么办呢&#xff1f;通常我们是下一首&#xff0c;或者充值 但是手头不宽裕但又想听怎么办&#xff1f; …

JavaEE-Spring(IoC控制反转,DI依赖注入,Spring项目创建和基本使用,ApplicationContext和BeanFactory的区别)

文章目录1. IoCDI2. Spring项目创建和使用ApplicationContext和BeanFactory的区别1. IoC Spring是一个包含多个工具方法的IoC容器 tomcat是web容器 List/Map是数据存储容器 IoC&#xff1a;Inversion of Control&#xff08;控制反转&#xff09; 将对象的控制权交给Spring&…

RK3399+PCIe+FPGA 在高速AD采样中的应用

一、需求 要实现高速AD/DA的数据采集&#xff0c;并发送到高性能arm核进行数据处理&#xff1b; 方案RK3399pcieFPGAAD/DA。 二、器件介绍 一、RK3399 RK3399是一款低功耗、高性能处理器&#xff0c;用于计算、个人移动互联网设备和其他智能设备应用。基于Big.Little架构&…

计算机发展史之查尔斯·巴贝奇

查尔斯巴贝奇&#xff08;Charles Babbage&#xff0c;1791年12月26日—1871年10月18日&#xff09;是一名英国数学家、发明家、科学家&#xff0c;科学管理的先驱者&#xff0c;出生于一个富有的银行家的家庭&#xff0c;曾就读于剑桥大学三一学院。 他在24岁时就被选为英国皇…

智慧医院数据可视化(数据大屏)

本次分享的作品是用软件Axure8.0&#xff08;兼容9和10&#xff09;制作的针对智慧医院设计的数据可视化大屏&#xff0c;其作品内容主要是对医院的运营情况、门诊、住院、手术、药品、医务、医疗设备、卫生耗材以及医疗质量数据进行综合可视分析。 运营情况:对医院的整体数据…

左神算法学习:第一天-------位运算

前言 位运算是在算法设计中的一种非常重要和高效的方法&#xff0c;常见的有与运算&#xff0c;非运算&#xff0c;异或运算。我们常用的比较多的可能就是异或运算&#xff0c;又叫无进位相加。 1.1 取非运算----&#xff08;~&#xff09; 取非运算其实就是和我们的无符号数…