You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Shader与Canvas实现3D模型文本渲染时的文本收缩问题

问题分析与解决方案

你的问题源于两个核心点:Three.js CanvasTexture的默认尺寸处理逻辑,以及片元着色器中未匹配Canvas实际宽高比的UV计算,导致文本被强制缩放到正方形空间内。

一、修复CanvasTexture的尺寸与采样设置

Three.js的CanvasTexture默认会自动将纹理调整为2的幂(Power-of-Two, POT)尺寸,并且默认的采样滤镜和包裹模式会导致非正方形纹理被拉伸。同时,Canvas的尺寸必须是整数,measureText返回的浮点数值需要取整。

修改createTextCanvasTexture函数:

export const createTextCanvasTexture = (text, color, font) => {
  // 创建临时画布测量文本尺寸
  const canvasGetDimensions = document.createElement("canvas");
  const ctxGetDimensions = canvasGetDimensions.getContext("2d");

  const fontSizeForDimensions = 256;
  ctxGetDimensions.font = `${fontSizeForDimensions}px ${font}`;

  // 测量文本的实际宽度与高度(使用字体边界盒而非直接取fontSize)
  const metrics = ctxGetDimensions.measureText(text);
  const textWidth = metrics.width;
  // 文本实际高度 = 上升高度 + 下降高度,更准确反映文本占据的垂直空间
  const textHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;

  // 创建实际画布,尺寸取整(Canvas要求整数尺寸)
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  canvas.width = Math.ceil(textWidth);
  canvas.height = Math.ceil(textHeight);

  // 重新设置字体与样式
  ctx.font = `${fontSizeForDimensions}px ${font}`;
  ctx.fillStyle = color;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);

  // 创建纹理并修正默认行为
  const canvasTexture = new THREE.CanvasTexture(canvas);
  canvasTexture.flipY = false;
  // 禁用自动POT尺寸调整,关闭mipmap
  canvasTexture.generateMipmaps = false;
  // 使用线性滤镜避免模糊,设置边缘夹紧防止纹理重复
  canvasTexture.minFilter = THREE.LinearFilter;
  canvasTexture.magFilter = THREE.LinearFilter;
  canvasTexture.wrapS = THREE.ClampToEdgeWrapping;
  canvasTexture.wrapT = THREE.ClampToEdgeWrapping;
  // 适配高DPI屏幕(可选)
  canvasTexture.setPixelRatio(window.devicePixelRatio);

  // 保存画布宽高比,后续传递给着色器
  canvasTexture.aspectRatio = canvas.width / canvas.height;

  return canvasTexture;
};

二、修正片元着色器的UV计算逻辑

当前着色器将UV强制缩放到正方形空间,没有考虑Canvas的实际宽高比,导致长文本被横向压缩。需要在着色器中引入画布宽高比,修正UV的缩放与采样逻辑。

1. 传递宽高比到材质Uniform

在使用纹理的材质中,添加对应uniform:

// 假设你在创建材质时的uniform配置
const uniforms = {
  // ...其他uniform
  textTexture0: { value: yourTextTexture },
  textScale0: { value: new THREE.Vector2(1, 1) },
  textPosition0: { value: new THREE.Vector2(0.5, 0.5) },
  textRotation0: { value: 0 },
  // 添加宽高比uniform
  textAspectRatio0: { value: yourTextTexture.aspectRatio }
};

2. 修改片元着色器代码

export const createTextFragment = (index) => `
  // 平移UV到文本中心
  vec2 translatedUVText${index} = (vUv - textPosition${index});
  
  // 引入宽高比修正UV,避免文本被强制缩放到正方形
  vec2 scaledUVText${index} = translatedUVText${index};
  // 修正横向UV,匹配画布宽高比(如果画布宽>高,需要放大横向UV;反之则缩小)
  scaledUVText${index}.x /= textScale${index} * textAspectRatio${index};
  scaledUVText${index}.y /= textScale${index};

  // 应用旋转矩阵
  float cosThetaText${index} = cos(textRotation${index});
  float sinThetaText${index} = sin(textRotation${index});
  mat2 rotationMatrixText${index} = mat2(
    cosThetaText${index}, -sinThetaText${index},
    sinThetaText${index}, cosThetaText${index}
  );
  vec2 rotatedUVText${index} = rotationMatrixText${index} * scaledUVText${index};

  // 检查UV是否在有效范围内
  if (rotatedUVText${index}.x >= -0.5 && rotatedUVText${index}.x <= 0.5 &&
      rotatedUVText${index}.y >= -0.5 && rotatedUVText${index}.y <= 0.5) {
      // 采样纹理时还原UV的宽高比
      vec2 sampleUV = rotatedUVText${index};
      sampleUV.x /= textAspectRatio${index};
      vec4 textColor${index} = texture2D(textTexture${index}, sampleUV + 0.5);
      baseColor = mix(baseColor, textColor${index}.rgb, textColor${index}.a * 0.8);
  }
`;

三、调试验证步骤

  1. 将生成的临时Canvas添加到页面(document.body.appendChild(canvas)),确认文本的宽高是否符合预期,排除Canvas生成阶段的问题。
  2. 在着色器中添加调试代码,输出UV颜色(gl_FragColor = vec4(rotatedUVText${index} + 0.5, 0.0, 1.0);),查看UV分布是否正确。

内容的提问来源于stack exchange,提问作者Bn'R

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 02:38:15