blender建模并用babylonjs更换模型上的文字

更新时间: 2025-08-19 14:09:15

效果如下,我自己建了个广告牌模型,平时的时候显示限速信息
前方有事故的时候显示警告

# 建模

我使用blender建模(实际上我也只会这个) 底座是一个立方体,然后我缩放了立法体的上平面,形成一个锥形

柱子就是基础的10边的圆柱体

显示屏是一个长方体,然后内插了一个平面,再向内挤出

然后分离中间屏幕的平面,给它一个基础的黑色材质, 然后注意要将这个屏幕的模型命名为"info",后面写代码要用上的

# 实现步骤

  1. 首先需要获取到这个平面,并且给它一个自己创建的材质,后面我们就对这个材质的贴图使用canvas生成进行修改即可
import { StandardMaterial } from "@babylonjs/core";

let infoMaterial
// 加载模型  
SceneLoader.ImportMesh(
    null,
    "/models/",
    "info.glb",
    scene,
    (instanceMeshes) => {
      // 创建材质  
      infoMaterial = new StandardMaterial(`infoMaterial`, scene);
      // 获取平面 
      const modelIds = ["info"];
      // 赋予材质  
      modelIds.forEach(id => {
        scene.getMeshById(id).material = infoMaterial
      })
      // 改变材质  
      showText()
    }
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. 改变材质主要是通过canvas生成相应文字的纹理,然后将这个纹理赋给infoMaterial的diffuseTexture和emissiveTexture(因为显示屏发光,所以这个需要)
function showText(text='限速70km/h') {
  // 生成texture,后面再细说  
  const texture = createMultiLineTextTexture(text)
  infoMaterial.diffuseTexture = texture
  infoMaterial.emissiveTexture = texture
}
1
2
3
4
5
6
  1. 生成带文字的纹理,需要注意的是,如果文字是“限速70km/h”时文字横排3行,如果是其他的文字,就竖排两行,同时还要注意文字的大小会随文字的字数改变
function createMultiLineTextTexture(text='限速70km/h', color='#ff0000', width = 224, height = 392) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext("2d");

  // 清空画布
  context.clearRect(0, 0, width, height);

  // 1. 检测是否为"限速xxkm/h"格式
  const speedLimitPattern = /^限速(\d+)km\/h$/;
  const isSpeedLimit = speedLimitPattern.test(text);

  if (isSpeedLimit) {
    // 2. 特殊格式:横排3行
    const match = text.match(speedLimitPattern);
    const lines = ["限速", match[1], "km/h"];
    drawHorizontalText(context, lines, color, width, height);
  } else {
    // 普通文字:竖排2列
    const lines = splitTextToVerticalLines(text); // 切分文字为两列  
    const fontSize = calculateVerticalFontSize(context, lines, width, height);  // 计算文字大小  

    // 设置字体样式
    context.font = `${fontSize}px Arial Bold`;
    context.fillStyle = color;
    context.textAlign = "center";
    context.textBaseline = "middle";

    // 绘制竖排文字
    drawVerticalText(context, lines, color, width, height, fontSize);
  }

  // 创建Babylon纹理
  const texture = new Texture("", scene);
  texture.updateURL(canvas.toDataURL());

  return texture;
}
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
  1. 实现横排文字drawHorizontalText
/**
 * 横排3行文字绘制(居中对齐)
 * @param {CanvasRenderingContext2D} context - Canvas上下文
 * @param {string[]} lines - 3行文字数组
 * @param {string} color - 文字颜色
 * @param {number} width - Canvas宽度
 * @param {number} height - Canvas高度
 */
function drawHorizontalText(context, lines, color, width, height) {
  // 计算最佳字体大小(确保3行文字垂直居中且不溢出)
  const lineCount = 3;
  const maxFontSize = Math.floor(height / (lineCount * 1.5)); // 行高为字体1.5倍
  let fontSize = maxFontSize;

  // 设置字体样式
  context.font = `${fontSize}px Arial Bold`;
  context.fillStyle = color;
  context.textAlign = "center";
  context.textBaseline = "middle";

  // 计算行高和总高度
  const lineHeight = fontSize * 1.5;
  const totalTextHeight = lineCount * lineHeight;
  const verticalOffset = (height - totalTextHeight) / 2;

  // 绘制每行文字
  lines.forEach((line, index) => {
    const y = verticalOffset + index * lineHeight + lineHeight / 2;
    context.fillText(line, width / 2, y);
  });
}
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
  1. 实现 splitTextToVerticalLines
/**
 * 将文字拆分为竖排2行
 * @param {string} text - 输入文字
 * @returns {string[]} 拆分后的2行文字
 */
function splitTextToVerticalLines(text) {
  // 情况1:用户已使用|分隔符指定竖排拆分
  if (text.includes("|")) {
    return text.split("|").slice(0, 2); // 最多2列
  }

  // 情况2:自动均衡拆分文字
  const chars = text.split("");
  const mid = Math.ceil(chars.length / 2);

  // 确保左右列数量尽量均衡
  return [
    chars.slice(0, mid).join(""),
    chars.slice(mid).join("")
  ];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  1. 计算文字大小实现
/**
 * 计算竖排文字最佳字体大小
 * @param {CanvasRenderingContext2D} context - Canvas上下文
 * @param {string[]} lines - 两行文字
 * @param {number} width - Canvas宽度
 * @param {number} height - Canvas高度
 * @returns {number} 最佳字体大小
 */
function calculateVerticalFontSize(context, lines, width, height) {
  // 基于文字数量和高度计算初始大小
  const maxChars = Math.max(lines[0].length, lines[1].length);
  let fontSize = Math.floor(height / (maxChars * 1.5)); // 行高为字体1.5倍

  // 检查宽度是否溢出(单字宽度不应超过列宽的80%)
  context.font = `${fontSize}px Arial Bold`;
  const charWidth = context.measureText("测").width; // 取一个中文字符宽度
  const maxColumnWidth = (width / 2) * 0.8; // 每列最大可用宽度

  // 调整字体大小以适应宽度
  while (charWidth > maxColumnWidth && fontSize > 12) {
    fontSize--;
    context.font = `${fontSize}px Arial Bold`;
  }

  return fontSize;
}
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
  1. 实现绘制竖排文字 drawVerticalText
/**
 * 竖排2行文字绘制(居中对齐)
 * @param {CanvasRenderingContext2D} context - Canvas上下文
 * @param {string[]} lines - 2行文字数组
 * @param {string} color - 文字颜色
 * @param {number} width - Canvas宽度
 * @param {number} height - Canvas高度
 */
function drawVerticalText(context, lines, color, width, height, fontSize) {
  // 计算左右两列位置(竖排分两列)
  const columnWidth = width / 2;

  // 绘制左侧列文字(垂直排列)
  drawVerticalColumn(context, lines[0], columnWidth * 0.5, height, fontSize);

  // 绘制右侧列文字(垂直排列)
  drawVerticalColumn(context, lines[1], columnWidth * 1.5, height, fontSize);
}

/**
 * 垂直绘制一列文字(逐字垂直排列)
 * @param {CanvasRenderingContext2D} context - Canvas上下文
 * @param {string} text - 文字内容
 * @param {number} x - 列X坐标
 * @param {number} height - Canvas高度
 * @param {number} fontSize - 字体大小
 */
function drawVerticalColumn(context, text, x, height, fontSize) {
  const lineHeight = fontSize * 1.5;
  const totalHeight = text.length * lineHeight;
  const verticalOffset = (height - totalHeight) / 2;

  // 逐字垂直排列
  for (let i = 0; i < text.length; i++) {
    const y = verticalOffset + i * lineHeight + lineHeight / 2;
    context.fillText(text[i], x, y);
  }
}

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