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); } `;
三、调试验证步骤
- 将生成的临时Canvas添加到页面(
document.body.appendChild(canvas)),确认文本的宽高是否符合预期,排除Canvas生成阶段的问题。 - 在着色器中添加调试代码,输出UV颜色(
gl_FragColor = vec4(rotatedUVText${index} + 0.5, 0.0, 1.0);),查看UV分布是否正确。
内容的提问来源于stack exchange,提问作者Bn'R
相关产品推荐
相关产品推荐

