Text3D Plugin で一部の文字メッシュが正しく生成されない件について

お世話になっております。

<br/>

現在 UE 5.7.2 をベースに “UE-359856” の修正分を取り込んだ Text3D プラグインを用いてテキストの表示を行っているのですが、

簡体字の一部の文字にて、生成したメッシュの見た目が崩れてしまう現象が発生しています。

<br/>

> 暗

> 园

他にも問題が発生する文字がある可能性がございますが、上記の漢字について Text3DActor を用いて表示した場合は文字内の隙間部分が塗りつぶされてしまう現象を確認しています。

フォントによって発生する、しないがあるようですが、Windows に同梱されている “Microsoft Yahei”. “Sim Sun 標準” で再現することを確認しております。

※開発中のアプリケーションで使用しているフォントでも発生しております

<br/>

どちらの漢字も Font の uasset を開いた際に確認出来る Preview ウィンドウに入力した際は正しく表示されているため、

フォント自体や FreeType 側の問題ではなく Text3D のメッシュ生成部分で何かしらの問題が起きているのではと考えております。

<br/>

UE 5.8.0 Preview でも再現が取れているため、再現プロジェクトを添付しております。

Text3D Plugin の改修などでの回避方法をご教示頂けないでしょうか。

<br/>

宜しくお願い致します。

[Attachment Removed]

Steps to Reproduce
・添付のプロジェクトを UE 5.8.0 Preview で開く​

・Windows 同梱フォントを指定した Text3DActor が配置されたレベルが開く

・​暗 の日へん部分と、园 の 元 と 囗(くにがまえ) の間が塗りつぶされた見た目を確認

[Attachment Removed]

お世話になっております。

ご報告いただきましたText3Dでの簡体字メッシュの崩れにつきまして、ご連絡いただいた内容と添付の画像を確認いたしました。

いただいた画像およびご説明を拝見する限り、本現象はフォントやFreeType側ではなく、Text3Dのメッシュ生成処理側で生じているものと考えております。Fontアセットのプレビュー表示とText3Dとでは文字の描画経路が異なっており、Text3Dはグリフの輪郭を取得したうえで独自に三角形分割を行っております。そのため、輪郭の内外(穴部分)の判定処理で問題が生じている可能性が高く、暗の日へんの部分や、园の囗と内側の元の間が塗りつぶされる症状も、入れ子状になった輪郭の判定が正しく行われていないことによるものと考えております。なお、関連する不具合(UE-359856)がUE5.7.3で修正されておりますが、当該修正を取り込んでいただいた環境でも今回の暗および园では再現するとのことですので、これとは別の事象として扱っております。

本件につきましては、エンジン側の不具合として開発チームへ起票いたしました。トラッキング用の番号はUE-383019となります。今後の修正状況につきましては、進展があり次第ご連絡いたします。

https://issues.unrealengine.com/issue/UE\-383019 (時間経つとリンクを見れるようになります)

回避策として、表示する文字列が設計時点で固定されている場合に限り、DCCツール側で文字をメッシュ化したものをスタティックメッシュとして取り込み、Text3Dを使用せずに表示する方法が考えられます。お手元の環境でお試しいただき、表示が改善されるかご確認いただけますでしょうか。ただしこの方法では、実行時に文字列を動的に変更することはできなくなりますので、その点はご留意いただければと思います。なお、フォントの変更による回避につきましては、開発中のアプリケーションでご使用のフォントでも再現するとのことですので、確実な回避策にはならないものかと思われます。

お手数ですが、よろしくお願いします。

[Attachment Removed]

お世話になっております。

起票ありがとうございます。

> ​DCCツール側で文字をメッシュ化したものをスタティックメッシュとして取り込み、Text3Dを使用せずに表示

Text3D Plugin を用いて生成したメッシュをスタティックメッシュとして保存し、そちらを用いて表示を行っているため、頂いた上記方法での回避も可能かとは思うのですが、

今回共有させて頂いた文字以外でも多数現象が発生している関係もあり、Text3D Plugin でのメッシュ生成を修正しての解決ができればと考えております。

> 今後の修正状況につきましては、進展があり次第ご連絡

早期解決ができればと考えておりますので、不具合が発生していそうな該当のソースコードの箇所や修正方法(予測のもので構いません)をご共有頂けますと幸いです。

お手数をおかけしますが、引き続き、宜しくお願い致します。​

[Attachment Removed]

お世話になっております。

ご返信ありがとうございます。エンジン側で修正したいとのこと、承知しました。今のところの調査結果と、原因の可能性が高いと思う場所をお伝えします。以下は推測を含み、確定した修正方法ではありません。正式な修正はUE-383019で進めます。

対象のコードは、Text3DプラグインのText3DGlyphLoader.cppです。UE5.7系では戻り値がTSharedPtr、UE5.8ではTOptionalに書き直されていますが、処理の内容は同じです。

流れとしては、FreeTypeから取った各輪郭がCreateContour(中でProcessFreetypeOutlineとRemoveBadPointsを呼びます)でFPolygon2fに変換され、問題ないと判定された輪郭だけがInsert(中のIsInside、つまりFPolygon2f::Containsで「図形が別の図形の中に入っているか」を判定)で入れ子のツリーに登録されます。そのあとFixParityAndDivideが、各輪郭が「塗る部分」か「穴」かを、ツリーの入れ子の深さで決めます。このとき輪郭が元々持つFreeTypeの回転方向は使わず、深さに合わせた向き(bClockwise)に無理やり合わせ(必要ならReverseで反転)、最後にText3DGlyphMeshBuilderがそのbClockwiseで三角形分割します。

つまり、ある穴(カウンター)が正しくくり抜かれるには、その輪郭がツリーの正しい深さ(穴にあたる奇数の深さ)に入っている必要があります。輪郭がツリーに無い、または間違った深さに入ると、その部分は塗りつぶされます。

可能性が高いと思うのは、これらの文字のカウンターの輪郭が、CreateContourの段階で捨てられているケースです。具体的には、ProcessFreetypeOutlineが空を返す条件(輪郭の点が3つより少ない、または最初の点と重なる点を見つけるContourIsBad)や、RemoveBadPointsが「近すぎる点」「同じ直線上の点」とみなして点を消した結果、点が4つより少なくなって捨てられる条件です。

切り分けとしては、まず暗と园について、FreeTypeが返す輪郭の数と、実際にInsertされた輪郭の数を比べてみてください。あとのほうが少なければどこかで輪郭が捨てられているので、CreateContour・ProcessFreetypeOutline・RemoveBadPointsにログを入れ、どの条件で捨てているかご確認ください(箱状のカウンターでのRemoveBadPointsの過剰な点削除、またはContourIsBadが有力と考えています)。

なお、この部分はUE-294272(UE5.6.1)やUE-359856(UE5.7.3)でも同種の不具合が直されており、特定の輪郭の形で繰り返し問題が起きやすい箇所です。引き続きUE-383019で対応を進めますが、調査の中で具体的な発生条件が分かりましたら、ご共有いただけると修正の特定が早まり助かります。

バグのチケットでは更新情報があればお知らせさせていただきます。

お手数ですが、よろしくお願いします。

[Attachment Removed]

情報ありがとうございます。

弊社側でも頂いた情報を元に調査致します。

[Attachment Removed]

お世話になっております。

园 にて主に調査をしてみたところ、RemoveBadPoints や ContourIsBad では特に条件に引っかかっていないようで、Contour の 除外はされていないように見受けられました。

※RemoveBadPoints、ContourIsBad を無効化しても結果が変わらない

> FixParityAndDivideが、各輪郭が「塗る部分」か「穴」かを、ツリーの入れ子の深さで決めます

こちらについて、

void FText3DGlyphLoader::FixParityAndDivide(FText3DGlyphContourNode& Node, const bool bInClockwise)
		// Fix parity
		if (Child.bClockwise != bInClockwise)
		{
			//Child.Contour->Reverse();

のように Reverse を行わないようにしてみたところ添付画像のようになったため、頂点情報自体は正しく、塗りつぶしに関する不具合にも見受けられるのですがいかがでしょうか?

[Attachment Removed]

お世話になっております。

問題無く出力されているものと比較したところ、Node の Children に違いが見られましたので、

判定に関わらず Insert するようにしてみたところ、添付画像のようになりました。

void FText3DGlyphLoader::Insert(FText3DGlyphContourNode&& NodeA, FText3DGlyphContourNode& NodeB) const
{
	for (int32 ChildBIndex = 0; ChildBIndex < NodeB.Children.Num(); ChildBIndex++)
	{
		FText3DGlyphContourNode& ChildB = NodeB.Children[ChildBIndex];
 
		//if (IsInside(*NodeA.Contour, *ChildB.Contour)) // 必ずInsert
		{
			Insert(MoveTemp(NodeA), ChildB);
			return;
		}

問題が発生しているフォントでは「园」の「元」の左下部分が「囗」にくっついているため、Inside の判定に差が出ている可能性がありますが情報として共有致します。

お手数ですが、引き続き、宜しくお願い致します。

[Attachment Removed]

すみません、追記となります。

>「园」の「元」の左下部分が「囗」にくっついているため、Inside の判定に差が出ている可能性

Overlaps に置きかえることで該当の文字に関しては改善することを確認致しました。

全てをこちらの判定に置きかえると不具合が発生する文字があるため、完全な置き換えはできませんが、念のためご報告となります。

bool FText3DGlyphLoader::IsInside(const FPolygon2f& ContourA, const FPolygon2f& ContourB) const
{
	//return ContourB.Contains(ContourA);
	return ContourB.Overlaps(ContourA);
}

[Attachment Removed]

お世話になっております。

切り分けのご報告、ありがとうございます。大変助かります。

いただいた内容から、原因はほぼ特定できたと考えております。RemoveBadPointsやContourIsBadで輪郭が捨てられているわけではなく、頂点のデータ自体は正しい、というご確認のとおりです。問題はIsInside(FPolygon2f::Contains)による入れ子の判定にあり、ご指摘の「元の左下が囗に接触している」ことが引き金になっています。

理由はFPolygon2f::Containsの実装にあります。ポリゴンを渡す版のContainsは、(1)相手の全頂点が自分の内側にあること、かつ(2)辺どうしが交差しないこと、の両方を満たしたときだけtrueを返します。元が囗に接触していると、その接触部分が辺の交差として扱われて(2)を満たさなくなり、Containsはfalseを返します。その結果、元が囗の中に入っていると判定されず、入れ子の深さがずれて、塗りと穴が反転します。

Overlapsで园が直るのも同じ理由です。Overlapsは「少しでも重なればtrue」になるため、接触していても入れ子と判定されます。ただしOverlapsは、本来は入れ子ではなく、ただ接触しているだけの輪郭どうしもtrueにしてしまうので、他の文字が壊れます。全部を置き換えられないのは、ご認識のとおりかと思います。

判定の方針としては、「相手の輪郭の代表的な内側の点が、自分の内側にあるか」を、点を渡す版のContains(FPolygon2f::Contains(点))で見る方法が比較的安全かと考えております。これなら、辺が接触していても内側の点は内側のままなので园は正しく入れ子になり、ただ接触しているだけの別の輪郭は内側の点が外側にあるので入れ子になりません。ただし、代表点は確実に内側にある点を選ぶ必要があります。凹んだ輪郭では中心(重心)が外に出ることがあるため、その選び方には注意が必要です。

本件の発生条件(内側の輪郭が外側の輪郭に接触するケース)と、上記のContains・Overlapsの挙動は、UE-383019に反映済みです。正式な修正がこの接触ケースも含むように対応を進めます。もし安定して直る判定方法が固まりましたら、ご共有いただけると助かります。

引き続きよろしくお願いいたします。

[Attachment Removed]

お世話になっております。

ご確認ありがとうございます。

> 上記のContains・Overlapsの挙動は、UE-383019に反映済み

https://issues.unrealengine.com/issue/UE-383019

共有頂いている上記の URL ですが、いまだに Unknown Issue 状態で確認できない状況となっています…

Perforce の CL 番号を共有頂けないでしょうか?

引き続き、宜しくお願い致します。

[Attachment Removed]

お世話になっております。

追加で Insert に関する​情報となります。

Insert 時、ChildB に​ NodeA をコピーしている箇所がありますが、for文内で ChildrenB(Rootの子供)​の中身を ChildrenA(追加対象の輪郭) に移動した後にコピーされていないため、輪郭が 1 つ消えたままになっているように見受けられます。

お渡ししたサンプルですと、SIMSUN の Actor に「日」​を入れると追いやすいかと思います。

こちらもご確認お願いできますでしょうか?

void FText3DGlyphLoader::Insert(const TText3DGlyphContourNodeShared NodeA, const TText3DGlyphContourNodeShared NodeB) const
{
...
​		if (IsInside(ChildBContour, ContourA))
 		{
 			// add ChildBContour to list of contours that are inside ContourA
 			TArray<TText3DGlyphContourNodeShared>& ChildrenA = NodeA->Children;
 			ChildrenA.Add(ChildB);
 			// replace ChildBContour with ContourA in list it was before
 			//ChildB = NodeA; // ここではコピーしない
 			// check if other contours in that list are inside ContourA
 			for (int32 ChildBSiblingIndex = ChildrenB.Num() - 1; ChildBSiblingIndex > ChildBIndex; ChildBSiblingInex--)
 			{
 				TText3DGlyphContourNodeShared ChildBSibling = ChildrenB[ChildBSiblingIndex;
 ​
 				if (IsInside(ChildBSibling->Contour, ContourA))
 				{
 					ChildrenA.Add(ChildBSibling);
 					ChildrenB.RemoveAt(ChildBSiblingIndex);
 				}
 			}
 ​
 			// replace ChildBContour with ContourA in list it was before
 			ChildB = NodeA; // for 文の内容をここで反映

[Attachment Removed]

五月雨式に申し訳ありません。

アプリケーション​側で使用しているフォントにて、まだメッシュが塗りつぶされてしまう現象が発生しているため、引き続き調査を行っているのですが、

FixParityAndDivide ​内でも不具合が発生してしまっているようでした。

頂点をマージしているような処理ではありますが、処理を通り過ぎると輪郭が丸ごと消失してしまう挙動を確認しています。

※FixParityAndDivide(Child, !bInClockwise); をコールする​だけにすると、添付画像のように改善が見られます

​void FText3DGlyphLoader::FixParityAndDivide(FText3DGlyphContourNode& Node, const bool bInClockwise)
 
		// Find first junction
		if (!FindJunction(Arrangement))
		{
			// If no junction was found, contour doesn't have self-intersections
			FixParityAndDivide(Child, !bInClockwise);
			continue;
		}
 
		// ■■■ ここより下の処理に入った場合にメッシュが塗りつぶされてしまう ■■■
 
		// Remove this contour from parent's child list
		FText3DGlyphContourNode RemovedChildNode = MoveTemp(Child);
		Node.Children.RemoveAt(ChildIndex--);
...

現時点の症状をまとめますと​

・「口」(くにがまえ)の中が塗りつぶされてしまう → IsInside での判定時に、接触しているケースで不具合が発生

・「日​」が塗りつぶされてしまう → 後から判定に入ってきた輪郭が、既にある輪郭を包括しているケースで子供の付け替えを行う際に不具合が発生

・「会」​が塗りつぶされてしまう → FindJunction が true のケースで、頂点のマージのような処理が走った際に不具合が発生

となっています。

ご確認のほど、宜しくお願い致します。​

[Attachment Removed]

お世話になっております。

ここまで詳しく調べていただき、本当にありがとうございます。おかげさまで、原因がかなり見えてきました。

いただいた切り分けを拝見する限り、今回の塗りつぶしは、どうやら原因の異なる3つの不具合が重なっているようです。いずれもText3DGlyphLoader.cppの中の処理で、こちらでも5.7のソースを追って同じ箇所を確認できました。

まず囗の中が塗りつぶされる件は、まさにおっしゃる通り、IsInside(FPolygon2f::Contains)の接触まわりが原因かと思います。Containsは「相手の全頂点が内側にある」ことと「辺が交差しない」ことの両方が揃って初めてtrueになるので、元が囗に触れていると、その接触が辺の交差とみなされてfalseになり、入れ子と判定されないまま塗りつぶされてしまう、という流れですね。Overlapsにすると一部の文字は直るものの、本来は入れ子ではない、ただ触れているだけの輪郭まで拾ってしまうので別の文字が崩れる、という点もこちらで確認できました。全部を置き換えるのは難しい、というご認識のとおりかと思います。

日の方は少し毛色が違っていて、Insertで後から来た輪郭が既存の輪郭を包み込むときの、子の付け替え(ChildBへのNodeAの代入と、その後に兄弟を集めるループ)のあたりが怪しそうです。囗とは別の問題として見ております。「ChildB = NodeAをループの後ろに動かすと日が直る」とのこと、検証まで進めていただき助かりました。こちらでもSIMSUNのActorに日を入れて動きを追いつつ、いただいた修正をベースに対応を進めさせていただきます。

会については、FixParityAndDivideで、FindJunctionがtrueになる(自己交差がある)ケースの分割処理が引っかかっているようでした。分割したあとにRemoveUnneededNodesが親と同じ向きの輪郭を落としてしまうので、本来残すべき輪郭がここで消えているのかもしれません。FixParityAndDivide(Child, !bInClockwise)だけを呼ぶと改善する、というお話とも符合しますね。こちらも独立した問題として扱っています。

この3点は、それぞれ別の事象としてUE-383019にまとめて記載しておきました。引き続き原因を追ってまいりますが、また何か見えてきましたら、ぜひ教えていただけると嬉しいです。

開発チームから情報を聞いたらこのスレッドを更新させていっただきます。時間が経つとチケットでも確認できるようになります。

引き続き、どうぞよろしくお願いいたします!

[Attachment Removed]

お世話になっております。

> 可能性が高いと思うのは、これらの文字のカウンターの輪郭が、CreateContourの段階で捨てられているケースです。

> 具体的には、ProcessFreetypeOutlineが空を返す条件(輪郭の点が3つより少ない、

> または最初の点と重なる点を見つけるContourIsBad)や、RemoveBadPointsが「近すぎる点」「同じ直線上の点」とみなして点を消した結果、点が4つより少なくなって捨てられる条件です。

フォントと文字の組み合わせによっては、初期のご回答時に頂いた上記の ContourIsBad に引っかかり、輪郭が捨てられてしまうものもあるようでしたのでご報告となります。

※「花」の草冠含めた右半分が無くなる、「懂」でほとんどメッシュが残っていない状態になる、といった症状を確認しており、ContourIsBad の処理を省くことで改善が見られます

引き続き、宜しくお願い致します。

[Attachment Removed]

お世話になっております。

沢山調べていただいて、本当に大変助かります。情報を追加しておきます。

動きがあればここで報告させていただきます。

引き続きよろしくお願いします。

[Attachment Removed]