:顶点数据与 TEXCOORD1 的来源)
Unity TMP_SDF 分析一顶点数据与TEXCOORD1的来源适用版本Unity2021.3.23f1c1TextMeshPro3.0.9UGUI1.0.0分析范围项目中的标准Assets/TextMesh Pro/Shaders/TMP_SDF.shader及其关联源码Mobile、SSD、Surface 等变体不直接套用。系列导航第 1/9 篇前置无下一篇纹理映射 UV 与 PackUVTextMeshProTMP会把字符几何、字体图集坐标、装饰纹理映射坐标以及 SDF 缩放信息一起写入网格再交给 Shader。两组 UV 的分工如下TEXCOORD0采样字体 SDF Atlas_MainTex。TEXCOORD1保存打包后的 Face/Outline 装饰纹理映射 UV以及 SDF 缩放量xScale。TEXCOORD1.x保存 Face/Outline 装饰纹理映射 UV 的打包值。一、Shader 顶点输入与 UV 通道分工TMP_SDF.shader的顶点输入结构如下节选struct vertex_t { UNITY_VERTEX_INPUT_INSTANCE_ID float4 position : POSITION; float3 normal : NORMAL; fixed4 color : COLOR; float2 texcoord0 : TEXCOORD0; float2 texcoord1 : TEXCOORD1; };两个 UV 通道在 Shader 中走的是不同的数据流节选float2 textureUV UnpackUV(input.texcoord1.x); float2 faceUV TRANSFORM_TEX(textureUV, _FaceTex); float2 outlineUV TRANSFORM_TEX(textureUV, _OutlineTex); output.atlas input.texcoord0; output.textures float4(faceUV, outlineUV);片元阶段采样 SDF Atlas 的代码是float c tex2D(_MainTex, input.atlas).a;这里的input.atlas来自input.texcoord0。而texcoord1.x解包后只用于_FaceTex、_OutlineTex的纹理映射。Shader 语义Unity Mesh 通道TMP 数据实际用途TEXCOORD0Mesh.uvTMP_MeshInfo.uvs0采样_MainTex中的 SDF 字形图集TEXCOORD1Mesh.uv2TMP_MeshInfo.uvs2.x为打包映射 UV.y为xScale二、TMP_TextInfo与TMP_MeshInfoTMP 为每个文本对象维护一个TMP_TextInfo。其中的meshInfo数组按材质保存主文本和子网格所需的数据节选publicclassTMP_TextInfo{publicTMP_MeshInfo[]meshInfo;}publicstructTMP_MeshInfo{publicMeshmesh;publicVector2[]uvs0;publicVector2[]uvs2;}最终uvs0会进入Mesh.uvuvs2会进入Mesh.uv2。在标准 TMP SDF Shader 中它们分别对应TEXCOORD0和TEXCOORD1。Unity 的属性名uv2表示第二组 UV对应 Shader 语义TEXCOORD1。三、必须区分临时uv2与最终Mesh.uv2在GenerateTextMesh()内部同一个TMP_Vertex.uv2字段会经历两个阶段映射阶段四个顶点的uv2.x / uv2.y暂时保存二维纹理映射坐标(u, v)。打包阶段二维映射坐标被PackUV(u, v)压入.x.y被覆盖为xScale。打包代码如下节选floatx0characterInfos[i].vertex_BL.uv2.x;floaty0characterInfos[i].vertex_BL.uv2.y;floatx1characterInfos[i].vertex_TR.uv2.x;floaty1characterInfos[i].vertex_TR.uv2.y;floatdx(int)x0;floatdy(int)y0;x0-dx;x1-dx;y0-dy;y1-dy;characterInfos[i].vertex_BL.uv2.xPackUV(x0,y0);characterInfos[i].vertex_BL.uv2.yxScale;characterInfos[i].vertex_TL.uv2.xPackUV(x0,y1);characterInfos[i].vertex_TL.uv2.yxScale;characterInfos[i].vertex_TR.uv2.xPackUV(x1,y1);characterInfos[i].vertex_TR.uv2.yxScale;characterInfos[i].vertex_BR.uv2.xPackUV(x1,y0);characterInfos[i].vertex_BR.uv2.yxScale;所以若观察的是映射阶段uv2是二维坐标若观察的是最终的TMP_MeshInfo.uvs2或Mesh.uv2其含义已经变为Mesh.uv2 (PackUV(mappingU, mappingV), xScale)映射坐标的四种计算模式将在第二篇展开。四、xScale3D 文本与 UGUI 的计算路径不同1. 3DTextMeshProTMPro_Private.cs中的计算为节选xScalecharacterInfos[i].scale*Mathf.Abs(lossyScale)*(1-m_charWidthAdjDelta);2.TextMeshProUGUIUGUI 先计算字符自身的缩放与宽度压缩节选xScalecharacterInfos[i].scale*(1-m_charWidthAdjDelta);然后根据 Canvas 的渲染模式继续修正switch(canvasRenderMode){caseRenderMode.ScreenSpaceOverlay:xScale*Mathf.Abs(lossyScale)/canvasScaleFactor;break;caseRenderMode.ScreenSpaceCamera:xScale*isCameraAssigned?Mathf.Abs(lossyScale):1;break;caseRenderMode.WorldSpace:xScale*Mathf.Abs(lossyScale);break;}3DTextMeshPro与TextMeshProUGUI使用不同的xScale路径ScreenSpaceOverlay还会加入 Canvas Scale Factor 换算。五、xScale中各因子的含义因子含义characterInfos[i].scale排版阶段为当前字符得到的字符缩放量Mathf.Abs(lossyScale)对象层级缩放在当前渲染路径中的影响取绝对值以保留缩放幅度canvasScaleFactorOverlay Canvas 下用于抵消 Canvas 缩放的因子1 - m_charWidthAdjDeltaAuto Size 为容纳超宽文本而施加的字符宽度压缩比例正负号负值表示使用 Shader 模拟 Bold绝对值仍是 SDF 缩放量Shader 中同时使用了xScale的符号与绝对值节选float bold step(input.texcoord1.y, 0); float scale rsqrt(dot(pixelSize, pixelSize)); scale * abs(input.texcoord1.y) * _GradientScale * (_Sharpness 1);step(input.texcoord1.y, 0)用符号判断是否采用 Bold 权重。abs(input.texcoord1.y)用幅度修正 SDF 的屏幕空间缩放。六、m_charWidthAdjDelta是 Auto Size 的宽度压缩量m_charWidthAdjDelta是 Auto Size 溢出处理路径中的字符宽度压缩量当文本自动缩放已启用、当前是行首第一个单词并且继续换行已经无法解决超宽问题时TMP 会先尝试压缩字符宽度节选if(isTextAutoSizingEnabledisFirstWordOfLine){if(m_charWidthAdjDeltam_charWidthMaxAdj/100m_AutoSizeIterationCountm_AutoSizeMaxIterationCount){floatadjustedTextWidthtextWidth;if(m_charWidthAdjDelta0)adjustedTextWidth/1f-m_charWidthAdjDelta;floatadjustmentDeltatextWidth-(widthOfTextArea-0.0001f)*(isJustifiedOrFlush?1.05f:1.0f);m_charWidthAdjDeltaadjustmentDelta/adjustedTextWidth;m_charWidthAdjDeltaMathf.Min(m_charWidthAdjDelta,m_charWidthMaxAdj/100);returnVector2.zero;}}该值增大时1 - m_charWidthAdjDelta变小字符在水平方向被压窄。isJustifiedOrFlush只影响计算时预留的阈值。七、负xScale与替代 Typeface在完成缩放计算后TMP 会编码 Bold 状态节选if(!characterInfos[i].isUsingAlternateTypeface(characterInfos[i].styleFontStyles.Bold)FontStyles.Bold){xScale*-1;}只有同时满足以下条件才会取负字符样式包含FontStyles.Bold。当前字符没有使用字体权重表中的替代 Typeface。如果字体资源已经为相应的字重或斜体配置了专用 TypefaceTMP_FontAssetUtilities找到字符时会把isAlternativeTypeface设为true。此时字形本身已经来自专用字体不需要再用负号要求 Shader 模拟加粗。这里还要区分“替代 Typeface”和“Fallback Font Asset”替代 Typeface 是fontWeightTable中针对字重或斜体配置的字体资源找到字符时会明确令isAlternativeTypeface true。普通 Fallback 负责后备字符查找只有最终字符来自字体权重表中的替代 Typeface 时isAlternativeTypeface才会为true。Bold 样式既可以来自富文本标签也可以来自组件样式设置。例如b文本/b会把FontStyles.Bold压入样式状态。编辑器中的粗体选项最终也会令字符样式包含FontStyles.Bold。八、UpdateSDFScale()缩放变化时更新uvs2.y对象缩放变化后TMP 不一定重新生成所有字符。UpdateSDFScale()可以直接批量修改各材质网格的uvs2.y节选voidUpdateSDFScale(floatscaleDelta){if(scaleDelta0||scaleDeltafloat.PositiveInfinity||scaleDeltafloat.NegativeInfinity){m_havePropertiesChangedtrue;OnPreRenderCanvas();return;}for(intmaterialIndex0;materialIndexm_textInfo.materialCount;materialIndex){TMP_MeshInfomeshInfom_textInfo.meshInfo[materialIndex];for(inti0;imeshInfo.uvs2.Length;i)meshInfo.uvs2[i].y*Mathf.Abs(scaleDelta);}}正常情况下它只调整 SDF 缩放量并把更新后的 UV2 推送回主网格和子网格。若scaleDelta为0或正负无穷则会标记属性变化并进入预渲染流程触发完整重建路径。3DTextMeshPro使用OnPreRenderObject()UGUI 使用OnPreRenderCanvas()两者的保护思路相同。九、完整数据流排版与字符生成 ├─ 字形 Atlas 坐标 │ └─ TMP_MeshInfo.uvs0 → Mesh.uv → TEXCOORD0 │ └─ tex2D(_MainTex, input.atlas) 采样 SDF │ └─ Face / Outline 映射坐标 (u, v) └─ 临时 TMP_Vertex.uv2 ├─ PackUV(u, v) → 最终 Mesh.uv2.x └─ xScale → 最终 Mesh.uv2.y └─ Shader ├─ UnpackUV(.x) → _FaceTex / _OutlineTex UV ├─ sign(.y) → 是否模拟 Bold └─ abs(.y) → SDF 屏幕空间缩放最终结论可以压缩为一句话标准TMP_SDF.shader中字体 SDF Atlas 使用TEXCOORD0TEXCOORD1.x是 Face/Outline 映射 UV 的打包值TEXCOORD1.y是带有 Bold 符号信息的 SDF 缩放量。十、源码索引Scripts/Runtime/TMPro_UGUI_Private.csUGUI 映射、Canvas 模式修正、UV 打包与UpdateSDFScale()。Scripts/Runtime/TMPro_Private.cs3D 文本的xScale、UV 打包与UpdateSDFScale()。Scripts/Runtime/TMP_Text.csPackUV()与 Auto Size 的字符宽度压缩。Scripts/Runtime/TMP_FontAssetUtilities.cs替代 Typeface 与 Fallback 字符查找。Assets/TextMesh Pro/Shaders/TMP_SDF.shaderTEXCOORD0/1的实际消费方式。Assets/TextMesh Pro/Shaders/TMPro.cgincUnpackUV()。