Skip to content

Commit f45c113

Browse files
committed
Initial commit of MSDF stuff
1 parent fad76d2 commit f45c113

File tree

8 files changed

+475
-0
lines changed

8 files changed

+475
-0
lines changed
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/**
2+
*
3+
* MIT License
4+
*
5+
* Copyright(c) 2019 Merlin
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all
15+
* copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
* SOFTWARE.
24+
*/
25+
26+
#if UNITY_EDITOR
27+
28+
using System.Collections;
29+
using System.Collections.Generic;
30+
using System.IO;
31+
using UnityEditor;
32+
using UnityEngine;
33+
34+
public class MSDFAtlasGenerator : EditorWindow
35+
{
36+
public Font FontToConvert = null;
37+
38+
public Texture2D AtlasToSave = null;
39+
40+
private const string MSDFGenPath = "Assets/Merlin/MSDF/bin/msdfgen.exe";
41+
private const string MSDFTempPath = "Assets/Merlin/MSDF/gen/glyph{0}.png";
42+
43+
[MenuItem("Window/Merlin/MSDF Font Generator")]
44+
public static void ShowWindow()
45+
{
46+
EditorWindow window = EditorWindow.GetWindow(typeof(MSDFAtlasGenerator));
47+
window.maxSize = new Vector2(400, 200);
48+
}
49+
50+
void OnGUI()
51+
{
52+
EditorGUILayout.LabelField("MSDF Atlas Generator", EditorStyles.boldLabel);
53+
54+
FontToConvert = (Font)EditorGUILayout.ObjectField("Font Asset:", FontToConvert, typeof(Font), false);
55+
56+
EditorGUI.BeginDisabledGroup(FontToConvert == null);
57+
if (GUILayout.Button("Generate Atlas"))
58+
{
59+
GenerateAtlas();
60+
}
61+
EditorGUI.EndDisabledGroup();
62+
63+
//AtlasToSave = (Texture2D)EditorGUILayout.ObjectField("Texture to save:", AtlasToSave, typeof(Texture2D), false);
64+
65+
//if (GUILayout.Button("Save Atlas to PNG"))
66+
//{
67+
// SaveToPNG();
68+
//}
69+
}
70+
71+
private void SaveToPNG()
72+
{
73+
string assetPath = AssetDatabase.GetAssetPath(AtlasToSave).Replace(".asset", ".png");
74+
75+
File.WriteAllBytes(assetPath, ImageConversion.EncodeToPNG(AtlasToSave));
76+
}
77+
78+
private void GenerateAtlas()
79+
{
80+
TrueTypeFontImporter fontImporter = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(FontToConvert)) as TrueTypeFontImporter;
81+
82+
if (fontImporter == null)
83+
Debug.LogError("Could not import mesh asset! Builtin Unity fonts like Arial don't work unless you put them in the project directory!");
84+
85+
fontImporter.characterSpacing = 4;
86+
fontImporter.characterPadding = 2;
87+
88+
fontImporter.SaveAndReimport();
89+
90+
// Hacky method to get the generated font texture so that we can figure out where to put pixels
91+
Texture2D fontTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GetAssetPath(FontToConvert));
92+
93+
Dictionary<CharacterInfo, Texture2D> characterGlyphMap = new Dictionary<CharacterInfo, Texture2D>();
94+
95+
CharacterInfo[] characterInfos = FontToConvert.characterInfo;
96+
97+
Texture2D newAtlas = new Texture2D(fontTexture.width, fontTexture.height, TextureFormat.ARGB32, false, true);
98+
for (int x = 0; x < newAtlas.width; ++x)
99+
{
100+
for (int y = 0; y < newAtlas.height; ++y)
101+
{
102+
newAtlas.SetPixel(x, y, Color.black);
103+
}
104+
}
105+
106+
int charCount = 0;
107+
108+
foreach (CharacterInfo info in characterInfos)
109+
{
110+
charCount++;
111+
112+
EditorUtility.DisplayProgressBar("Generating MSDF Atlas...", string.Format("Glyph {0}/{1}", charCount, characterInfos.Length), charCount / (float)characterInfos.Length);
113+
114+
Texture2D currentGlyphTex = GenerateGlyphTexture(info.index, info.glyphWidth, info.glyphHeight);
115+
116+
if (currentGlyphTex == null)
117+
continue;
118+
119+
for (int x = 0; x < currentGlyphTex.width; ++x)
120+
{
121+
for (int y = 0; y < currentGlyphTex.height; ++y)
122+
{
123+
float progressX = (x) / (float)(currentGlyphTex.width);
124+
float progressY = (y) / (float)(currentGlyphTex.height);
125+
126+
float uvProgressX = Mathf.Lerp(info.uvTopLeft.x, info.uvTopRight.x, progressX) * fontTexture.width;
127+
float uvProgressY = Mathf.Lerp(info.uvBottomLeft.y, info.uvTopLeft.y, progressY) * fontTexture.height;
128+
129+
// flipped iS dEpRiCaTeD uSiNg ThE Uv WiLl bE CoNSiStEnT. It's not consistent in my limited experience.
130+
// Maybe I'm doing something wrong, but I don't want to try fighting with Unity trying to fix an issue that may be on their end.. I've wasted enough time on trusting Unity to do things correctly.
131+
#pragma warning disable 0618
132+
if (info.flipped)
133+
#pragma warning restore 0618
134+
{
135+
uvProgressY = Mathf.Lerp(info.uvTopLeft.y, info.uvTopRight.y, progressX) * fontTexture.height;
136+
uvProgressX = Mathf.Lerp(info.uvBottomLeft.x, info.uvTopLeft.x, progressY) * fontTexture.width;
137+
}
138+
139+
140+
int targetX = Mathf.RoundToInt(uvProgressX);
141+
int targetY = Mathf.RoundToInt(uvProgressY) - 1;
142+
143+
Color glyphCol = currentGlyphTex.GetPixel(x, y);
144+
145+
newAtlas.SetPixel(targetX, targetY, glyphCol);
146+
}
147+
}
148+
149+
}
150+
151+
EditorUtility.ClearProgressBar();
152+
153+
newAtlas.Apply(false);
154+
155+
string fontPath = AssetDatabase.GetAssetPath(FontToConvert);
156+
string savePath = Path.Combine(Path.GetDirectoryName(fontPath), Path.GetFileNameWithoutExtension(fontPath) + "_msdfAtlas.asset");
157+
158+
AssetDatabase.CreateAsset(newAtlas, savePath);
159+
160+
EditorGUIUtility.PingObject(newAtlas);
161+
}
162+
163+
private Texture2D GenerateGlyphTexture(int UTFChar, int glyphWidth, int glyphHeight)
164+
{
165+
System.Diagnostics.Process msdfProcess = new System.Diagnostics.Process();
166+
167+
msdfProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
168+
msdfProcess.StartInfo.CreateNoWindow = true;
169+
msdfProcess.StartInfo.UseShellExecute = false;
170+
msdfProcess.StartInfo.FileName = Path.GetFullPath(MSDFGenPath);
171+
msdfProcess.EnableRaisingEvents = true;
172+
173+
string fontPath = Path.GetFullPath(AssetDatabase.GetAssetPath(FontToConvert));
174+
//string glyphLocalPath = string.Format(MSDFTempPath, UTFChar);
175+
string glyphLocalPath = string.Format(MSDFTempPath, 0);
176+
string glyphPath = Path.GetFullPath(glyphLocalPath);
177+
178+
Directory.CreateDirectory(Path.GetDirectoryName(string.Format(MSDFTempPath, 0)));
179+
string argStr = string.Format("msdf -o \"{0}\" -font \"{1}\" {4} -size {2} {3} -pxrange 4 -autoframe", glyphPath, fontPath, glyphWidth, glyphHeight, UTFChar);
180+
181+
msdfProcess.StartInfo.Arguments = argStr;
182+
183+
msdfProcess.Start();
184+
msdfProcess.WaitForExit();
185+
186+
if (!File.Exists(glyphLocalPath))
187+
{
188+
Debug.LogWarning("Could not load glyph " + UTFChar);
189+
return null;
190+
}
191+
192+
Texture2D loadedGlyph = new Texture2D(glyphWidth, glyphHeight);
193+
ImageConversion.LoadImage(loadedGlyph, File.ReadAllBytes(glyphLocalPath), false);
194+
195+
return loadedGlyph;
196+
197+
#if false // Old import code that bounces through the Asset Database so it's much slower.
198+
AssetDatabase.ImportAsset(glyphLocalPath);
199+
200+
TextureImporter glyphImporter = AssetImporter.GetAtPath(glyphLocalPath) as TextureImporter;
201+
202+
if (glyphImporter != null)
203+
{
204+
glyphImporter.npotScale = TextureImporterNPOTScale.None;
205+
glyphImporter.sRGBTexture = false;
206+
glyphImporter.mipmapEnabled = false;
207+
glyphImporter.textureCompression = TextureImporterCompression.Uncompressed;
208+
glyphImporter.isReadable = true;
209+
210+
glyphImporter.SaveAndReimport();
211+
}
212+
else
213+
{
214+
Debug.LogWarning("Failed to import glyph " + glyphPath);
215+
}
216+
217+
return AssetDatabase.LoadAssetAtPath<Texture2D>(glyphLocalPath);
218+
#endif
219+
}
220+
}
221+
222+
#endif
223+

Merlin/Scripts/MSDFAtlasGenerator.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Merlin/Shaders/MSDFTMFont.shader

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
Shader "Merlin/MSDF Text Mesh Font"
2+
{
3+
Properties
4+
{
5+
[HideInInspector]_MainTex ("Texture", 2D) = "white" {}
6+
[HDR]_TextColor("Text Color", Color) = (1, 1, 1, 1)
7+
[NoScaleOffset]_MSDFTex("MSDF Texture", 2D) = "black" {}
8+
_PixelRange("Pixel Range", Float) = 4.0
9+
}
10+
SubShader
11+
{
12+
Tags { "RenderType"="Cutout" "Queue"="AlphaTest+1"
13+
"IgnoreProjector" = "True" }
14+
LOD 100
15+
16+
Pass
17+
{
18+
//Blend SrcAlpha OneMinusSrcAlpha
19+
AlphaToMask On
20+
21+
CGPROGRAM
22+
#pragma vertex vert
23+
#pragma fragment frag
24+
25+
#include "UnityCG.cginc"
26+
27+
struct appdata
28+
{
29+
float4 vertex : POSITION;
30+
float2 uv : TEXCOORD0;
31+
};
32+
33+
struct v2f
34+
{
35+
float4 vertex : SV_POSITION;
36+
float2 uv : TEXCOORD0;
37+
};
38+
39+
float4 _TextColor;
40+
sampler2D _MSDFTex; float4 _MSDFTex_TexelSize;
41+
float _PixelRange;
42+
43+
float median(float r, float g, float b)
44+
{
45+
return max(min(r, g), min(max(r, g), b));
46+
}
47+
48+
v2f vert (appdata v)
49+
{
50+
v2f o;
51+
o.vertex = UnityObjectToClipPos(v.vertex);
52+
o.uv = v.uv;
53+
54+
return o;
55+
}
56+
57+
float4 frag (v2f i) : SV_Target
58+
{
59+
float2 msdfUnit = _PixelRange / _MSDFTex_TexelSize.zw;
60+
61+
float4 sampleCol = tex2D(_MSDFTex, i.uv);
62+
float sigDist = median(sampleCol.r, sampleCol.g, sampleCol.b) - 0.46;
63+
sigDist *= max(dot(msdfUnit, 0.5/fwidth(i.uv)), 0.9); // Max to handle fading out to quads in the distance
64+
float opacity = clamp(sigDist + 0.5, 0.0, 1.0);
65+
float4 color = float4(_TextColor.rgb, _TextColor.a * opacity);
66+
67+
clip(color.a - 0.005);
68+
69+
//return sampleCol;
70+
return color;
71+
}
72+
ENDCG
73+
}
74+
}
75+
}

Merlin/Shaders/MSDFTMFont.shader.meta

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)