【Swift】visionOS でテキストをEntityとして表示する
初めに
今回は Apple Vision Pro でテキストを表示する実装を行います。
SwiftUI では通常の Text 表示ではなく、Entity としてテキストを表示することができます。
今回は Entity としてテキストを表示し、カスタマイズする方法についてまとめていきたいと思います。
記事の対象者
- Swift, SwiftUI 学習者
- Apple Vision Pro のテキスト表示をカスタマイズしたい方
目的
今回は Apple Vision Pro で Entity のテキストを表示する実装を行うことが目的です。
テキストの表示、カスタマイズができればアプリのタイトル画面や目立たせたいテキストなどで使用することができるかと思います。
以下のようにフォントやテキストのスタイルを変更できるようにします。
実装
実装は以下の手順で進めていきます。
- シンプルな実装
- テキストのカスタマイズ
1. シンプルな実装
まずはシンプルにテキストを Entity として表示させる実装を行います。
コードは以下の通りです。
import SwiftUI
import RealityKit
struct SimpleTextView: View {
var body: some View {
RealityView { content in
do {
let textString = AttributedString("Hello World !")
let textMesh = try await MeshResource(
extruding: textString
)
let material = SimpleMaterial(
color: .black,
isMetallic: false
)
let textModel = ModelEntity(mesh: textMesh, materials: [material])
let boundingBox = textModel.visualBounds(relativeTo: nil)
let textWidth = boundingBox.extents.x
textModel.position = SIMD3<Float>(
x: -textWidth / 2,
y: 1.5,
z: -1.5
)
content.add(textModel)
} catch {
print("テキストが表示できませんでした: \(error.localizedDescription)")
}
}
}
}
それぞれ以下で詳しくみていきます。
以下では表示させるテキストとマテリアルを定義しています。
MeshResource
の extruding
に対して、 AttributedString
を渡すと非同期で文字列から3Dのメッシュが作成されます。元々 AttributedString
はテキストの一部分にスタイルを当てるなど、テキストの見た目のカスタマイズをするためのものですが、今回は文字列を渡すのみにしています。
material
として、SimpleMaterial
を定義しています。
SimpleMaterial
は名前の通り、シンプルなマテリアルであり、色と表面の粗さとメタリック(見た目を金属っぽくするかどうか)のみが設定できます。以下では色を黒にするだけにしています。
do {
let textString = AttributedString("Hello World !")
let textMesh = try await MeshResource(
extruding: textString
)
let material = SimpleMaterial(
color: .black,
isMetallic: false
)
以下では先ほど作成した textMesh
と material
を ModelEntity
に渡しています。
これで文字列をもとに ModelEntity
を作成することができました。
let textModel = ModelEntity(mesh: textMesh, materials: [material])
以下ではテキストの位置を調整しています。
textModel.visualBounds
の部分で、 textModel
の大きさを BoundingBox
として取得しています。取得した大きさのうち横幅のみを使用するため textWidth
に代入しています。
textModel.position
で textModel
を表示させる位置を調整することができます。
x: -textWidth / 2
とすることでユーザーの目の前に中心に表示させることができます。
y: 1.5
は高さ 1.5m であり、ユーザーの足元から 1.5m の高さに設定しています。
z: -1.5
は奥行き 1.5m であり、ユーザーの足元から 1.5m 離れた場所に設定しています。
let boundingBox = textModel.visualBounds(relativeTo: nil)
let textWidth = boundingBox.extents.x
textModel.position = SIMD3<Float>(
x: -textWidth / 2,
y: 1.5,
z: -1.5
)
以下の content.add
で textModel
が追加され、RealityView
で表示されるようになります。
content.add(textModel)
上記のコードを実行すると以下のような見た目になっているかと思います。
これでシンプルな表示は完了です。
App, ContentView の実装
App
, ContentView
は以下のようになっており、 ContentView
のボタンから SimpleTextView
を開けるようにしています。
これからの実装で ID や表示させる View は逐一変更する必要があります。
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
ImmersiveSpace(id: "SimpleTextView") {
SimpleTextView()
}
}
}
import SwiftUI
import RealityKit
struct ContentView: View {
@Environment(\.openImmersiveSpace) private var openImmersiveSpace
var body: some View {
Button(action: {
Task {
await openImmersiveSpace(id: "SimpleTextView")
}
}, label: {
Text("Open Immersive View")
})
}
}
2. テキストのカスタマイズ
次にテキストをカスタマイズしていきます。
テキストのカスタマイズは以下の手順で進めていきます。
- フォントの大きさ
- フォントの種類
- 段落スタイル
- 立体感のあるテキスト
- マテリアルの変更
1. フォントの大きさ
まずはフォントの大きさを変更してみます。
先程のコードを以下のように変更してみます。
AttributedString
には font
を設定することができます。
このコードではフォントの大きさを 5 に設定しています。
RealityView { content in
do {
+ var textString = AttributedString("Hello World !") // var に変更
+ let font = UIFont.systemFont(ofSize: 5)
+ textString.font = font
let textMesh = try await MeshResource(
extruding: textString
)
上記の変更を加えて実行すると以下のようにフォントの大きさが小さくなっていることがわかります。
2. フォントの種類
次はフォントの種類を変更してみます。
以下のように UIFont
の name
にフォントの名前を指定することでフォントを変更することができます。
var textString = AttributedString("Hello World !")
+ let font = UIFont(name: "Academy Engraved LET", size: 5)
textString.font = font
let textMesh = try await MeshResource(
extruding: textString
)
上記のコードで実行すると以下のようにフォントが変更されているのが確認できます。
なお、フォントの名前は MacBook の「Font Book」アプリで確認できます。
以下の赤枠で囲まれた部分を UIFont
の name
として指定することでフォントが表示できます。
これでフォントが表示できない場合は以下の手順を踏むことで正常に表示できるようになります。
- フォントの書き出し
- Xcode に追加
- Info.plist の編集
1. フォントの書き出し
Font Book で使用したいフォントを選んで、ファイル > 書き出す
を選択してフォントを書き出します。
2. Xcode に追加
書き出したフォントを Xcode に追加します。
Fonts
のようなディレクトリを作ってそこに追加すると良いかと思います。
この時、追加したフォントのファイルの「Target Membership」にアプリが含まれていることを確認します。ここの設定ができていないとアプリのプロジェクトでフォントが認識されず使用することができません。
3. Info.plist の編集
最後に Info.plist
を開いて、「Fonts provided by application」の項目を追加し、 Item で追加したフォントのファイル名を記述します。
以下では「Doto-Light.ttf」と「Doto-Black.ttf」という二つのフォントのファイルを追加しています。
これで、追加したフォントの名前を UIFont
の name
に指定することで表示できるかと思います。筆者の手元で追加した「Doto-Black.ttf」のフォントを使用したい場合は UIFont
の name
に Doto-Black
と指定すれば正常に表示できます。
ちなみに、Doto-Black
のフォントを使用して、文字の大きさを大きくして表示すると以下の画像のようになります。通常のテキストとはかなり印象が異なるかと思います。
サービスのイメージや伝えたいことによって使い分けられるのが理想かなと思います。
3. 段落スタイル
次に複数行のテキストに対応して、かつ段落のスタイルを変更していきます。
AttributedString
には append
で新たなテキストを追加することができます。
以下のようにするとテキストを追加できます。
var textString = AttributedString("Hello World !")
+ textString.append(AttributedString("SwiftUI"))
+ textString.append(AttributedString("Apple Vision Pro"))
let font = UIFont(name: "Academy Engraved LET", size: 5)
textString.font = font
let textMesh = try await MeshResource(
extruding: textString
)
これで実行すると以下のようになります。
上記のままだと改行ができていないので、それぞれのテキストの前に \n
を追加して実行します。
var textString = AttributedString("Hello World !")
+ textString.append(AttributedString("\nSwiftUI"))
+ textString.append(AttributedString("\nApple Vision Pro"))
let font = UIFont(name: "Academy Engraved LET", size: 5)
textString.font = font
let textMesh = try await MeshResource(
extruding: textString
)
これで実行すると以下のようになります。
append
で複数行のテキストが追加できることがわかりました。
次は段落のスタイルを変更していきます。
以下のようにコードを変更します。
段落のスタイルは NSMutableParagraphStyle
を変更することで設定できます。
NSMutableParagraphStyle
の alignment
でテキストを左寄せ、中央寄せ、右寄せなどに設定することができます。以下のコードでは中央寄せにしています。
作成した paragraphStyle
を AttributeContainer
で包んで mergeAttributes
に渡すことで、今まで設定したフォントなどのスタイルと結合することができます。
var textString = AttributedString("Hello World !")
textString.append(AttributedString("\nSwiftUI"))
textString.append(AttributedString("\nApple Vision Pro"))
let font = UIFont(name: "Academy Engraved LET", size: 5)
textString.font = font
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.alignment = .center
+ let paragraphAttributeContaner = AttributeContainer([
+ .paragraphStyle: paragraphStyle
+ ])
+ textString.mergeAttributes(paragraphAttributeContaner)
let textMesh = try await MeshResource(
extruding: textString
)
これで実行すると以下のようにテキストが中央寄せになります。
4. 立体感のあるテキスト
次に立体感のあるテキストにしてみます。
以下のようにコードを変更します。
MeshResource.ShapeExtrusionOptions
で立体感のあるメッシュの設定を行うことができます。
extrusionMethod
に .linear(depth: 5)
を指定することで直線的に 5 だけ奥側にテキストを押し出すことができます。
そして、その設定を MeshResource
の extrusionOptions
に渡すことで反映させることができます。
var textString = AttributedString("Hello World !")
textString.append(AttributedString("\nSwiftUI"))
textString.append(AttributedString("\nApple Vision Pro"))
+ let font = UIFont(name: "Academy Engraved LET", size: 10) // フォントサイズ変更
textString.font = font
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byClipping
paragraphStyle.alignment = .center
let paragraphAttributeContaner = AttributeContainer([
.paragraphStyle: paragraphStyle
])
textString.mergeAttributes(paragraphAttributeContaner)
+ var extrusionOptions = MeshResource.ShapeExtrusionOptions()
+ extrusionOptions.extrusionMethod = .linear(depth: 5)
let textMesh = try await MeshResource(
extruding: textString,
+ extrusionOptions: extrusionOptions
)
let material = SimpleMaterial(
color: .black,
isMetallic: false
)
上記のコードで実行すると以下のように立体感のあるテキストにすることができます。
以下のように chamferRadius
の設定を追加することで、テキストのモデルの角を丸めることができるようになります。
var extrusionOptions = MeshResource.ShapeExtrusionOptions()
extrusionOptions.extrusionMethod = .linear(depth: 5)
+ extrusionOptions.chamferRadius = 0.1
「角を丸める」とは公式ドキュメントの chamferradius の以下の画像がわかりやすいかと思いますが、モデルの角が滑らかになるということです。
ただ、この値はフォントの種類や大きさ、押し出す depth の大きさなどによってエラーになることがあるため、慎重に値を変更しつつ設定する必要があります。
5. マテリアルの変更
最後にテキストのマテリアルを変更してみます。
以下のようにコードを変更します。
今までは黒色の SimpleMaterial
を使用していましたが、 PhysicallyBasedMaterial
を使って水色で多少発光するマテリアルを使用してみます。
baseColor
でマテリアル自体の色、 emissiveColor
で発光する色を指定することができます。
作成した cyanEmissionMaterial
を ModelEntity
の materials
に割り当てることでモデルに反映させることができます。
let textMesh = try await MeshResource(
extruding: textString,
extrusionOptions: extrusionOptions
)
- let material = SimpleMaterial(
- color: .black,
- isMetallic: false
- )
+ var cyanEmissionMaterial = PhysicallyBasedMaterial()
+ cyanEmissionMaterial.baseColor = .init(tint: .cyan)
+ cyanEmissionMaterial.emissiveColor = .init(color: .cyan)
let textModel = ModelEntity(
mesh: textMesh,
+ materials: [cyanEmissionMaterial]
)
上記のコードで実行すると以下のように明るい水色のテキストのモデルが表示されます。
以下ではさらに細かいマテリアルの設定を行います。
コードを以下のように変更します。
var extrusionOptions = MeshResource.ShapeExtrusionOptions()
+ extrusionOptions.extrusionMethod = .linear(depth: 1) // 押し出し量を調節
+ extrusionOptions.materialAssignment = .init(
+ front: 1
+ )
let textMesh = try await MeshResource(
extruding: textString,
extrusionOptions: extrusionOptions
)
+ let blackMaterial = SimpleMaterial( // 黒色のマテリアルを再度追加
+ color: .black,
+ isMetallic: false
+ )
var cyanEmissionMaterial = PhysicallyBasedMaterial()
cyanEmissionMaterial.baseColor = .init(tint: .cyan)
cyanEmissionMaterial.emissiveColor = .init(color: .cyan)
let textModel = ModelEntity(
mesh: textMesh,
+ materials: [cyanEmissionMaterial, blackMaterial] // blackMaterial をリストに追加
)
これで実行すると以下のような表示になります。
以下で詳しくみていきます。
以下の materialAssignment
ではモデルのどの部分にどのマテリアルを割り当てるかを細かく設定することができます。ここでは front
に 1
を割り当てているため、 materials
に入っているマテリアルのうち、インデックスが 1
のマテリアルが割り当てられます。
先程のコードでは materials: [cyanEmissionMaterial, blackMaterial]
となっているため、 front
には blackMaterial
が割り当てられ、結果としてテキストの前面が黒色になります。
extrusionOptions.materialAssignment = .init(
front: 1
)
materialAssignment
には以下の設定項目が設けられており、モデルのどの部分をどのマテリアルで表示するかを細かく指定することができます。
- front : モデルの前面
- back : モデルの背面
- extrusion : 押し出し部分
- frontChamfer : 前面の角が丸まっている部分
- backChamfer : 背面の角が丸まっている部分
以下のコードでそれぞれの色に対応するマテリアルを作成して、割り当てて実行すると以下の画像のようになります。
var extrusionOptions = MeshResource.ShapeExtrusionOptions()
extrusionOptions.extrusionMethod = .linear(depth: 5)
extrusionOptions.chamferRadius = 0.8
extrusionOptions.materialAssignment = .init(
front: 0, // blackMaterial
back: 1, // whiteMaterial
extrusion: 2, // redMaterial
frontChamfer: 3, // greenMaterial
backChamfer: 4 // blueMaterial
)
let textModel = ModelEntity(
mesh: textMesh,
materials: [
blackMaterial, // 黒色のマテリアル
whiteMaterial, // 白色のマテリアル
redMaterial, // 赤色のマテリアル
greenMaterial, // 緑色のマテリアル
blueMaterial // 青色のマテリアル
]
)
前から見たモデル
後ろから見たモデル
それぞれ対応する部分にマテリアルが割り当てられていることがわかります。
以上です。
まとめ
最後まで読んでいただいてありがとうございました。
今回は visionOS におけるテキストの扱いについてまとめました。
今回紹介した以外にも見た目をカスタマイズすることができるので、必要に応じて調べつつ実装できればと思います。
誤っている点やもっと良い書き方があればご指摘いただければ幸いです。
参考
Discussion