WWDC Quick Look 💓 By SwiftGGTeam
Enhance your spatial computing app with RealityKit

Enhance your spatial computing app with RealityKit

元の動画を見る

ハイライト

RealityKit は visionOS 向けに Attachments、VideoPlayerComponent、Portals、Particle Emitters、Anchors の 5 大機能を追加し、SwiftUI ビューを 3D シーンに埋め込み、空間で動画を再生し、異世界へのポータルを作り、パーティクル効果を加え、実世界の壁や床にコンテンツを固定できます。

主要内容

Attachments: SwiftUI ビューを 3D シーンへ

以前は 3D モデル横にテキストラベルを付けるにはテクスチャマップや複雑な 3D テキストが必要でした。Attachments なら SwiftUI を書くように簡単です(02:08)。

RealityView の attachments view builder で SwiftUI ビューを定義し、make クロージャで entity(for:) により対応エンティティを取得して、通常のエンティティと同様にシーンへ追加します。テキストラベル、説明カード、操作ボタンなどが可能です。

VideoPlayerComponent: 3D 空間での動画再生

VideoPlayerComponent は 3D シーンに動画を埋め込む新しい RealityKit コンポーネントです(06:27)。

動画のアスペクト比から矩形メッシュを自動生成し、2D 動画や 3D MV-HEVC を含む AVFoundation 形式をすべてサポートします。デフォルト高さは 1 メートルで、エンティティのスケールで調整します。パススルー着色(passthrough tinting)で環境光を動画のトーンに合わせられます。

Portals: 別世界への窓

Portal は空間に「窓」を開き、そこから別の独立した世界が見えます(11:15)。

その世界は独自の照明、コンテンツ、空間関係を持ちます。Portal は PortalMaterial を表面マテリアルに、PortalComponent で対象世界へリンクします。魔法の窓、門廊、展示空間などに適しています。

Particle Emitters: パーティクル効果

ParticleEmitterComponent は火花、雪、衝撃波などの視覚効果をシーンに作れます(15:09)。

Reality Composer Pro で設計し、実行時に RealityKit で属性を動的調整できます。カスタム System と組み合わせればシーン変化に応答できます。

Anchors: 実世界への固定

AnchorEntity は 3D コンテンツを壁、床、頭部/手部相対位置に固定できます(17:14)。

追跡モードは 2 種: .continuous はアンカー移動に継続追従、.once は配置後は動かない。AnchoredStateChanged イベントで状態変化を監視できます。

詳細

Attachments コード例

02:30

import SwiftUI
import RealityKit

struct MoonOrbit: View {
    var body: some View {
        RealityView { content, attachments in
            guard let earth = try? await Entity(named: "Earth") else {
                return
            }
            content.add(earth)

            if let earthAttachment = attachments.entity(for: "earth_label") {
                earthAttachment.position = [0, -0.15, 0]
                earth.addChild(earthAttachment)
            }
        } attachments: {
            Attachment(id: "earth_label") {
                Text("Earth")
            }
        }
    }
}

キーポイント:

  • RealityView の make クロージャに attachments パラメータが追加
  • attachments view builder で埋め込む SwiftUI ビューを定義
  • 各 Attachment は一意の id(任意の Hashable)が必要
  • attachments.entity(for:) で id からエンティティを取得
  • 取得したエンティティは位置設定し、他エンティティの子として追加可能
  • glassBackgroundEffect() で Attachment にすりガラス背景を追加できる

VideoPlayerComponent コード例

08:03

public func makeVideoEntity() -> Entity {
    let entity = Entity()

    let asset = AVURLAsset(url: Bundle.main.url(forResource: "tides_video",
                                                withExtension: "mp4")!)
    let playerItem = AVPlayerItem(asset: asset)

    let player = AVPlayer()
    entity.components[VideoPlayerComponent.self] = .init(avPlayer: player)

    entity.scale *= 0.4

    player.replaceCurrentItem(with: playerItem)
    player.play()

    return entity
}

キーポイント:

  • VideoPlayerComponentAVPlayer インスタンスが必要
  • コンポーネントが動画アスペクト比から矩形メッシュを自動生成
  • デフォルト高さ 1 メートル。ここでは 0.4 メートル(40 cm)にスケール
  • MV-HEVC 3D 動画を含む AVFoundation 形式をサポート
  • AVPlayer の字幕を自動表示

パススルー着色

10:05

var videoPlayerComponent = VideoPlayerComponent(avPlayer: player)
videoPlayerComponent.isPassthroughTintingEnabled = true

entity.components[VideoPlayerComponent.self] = videoPlayerComponent

キーポイント:

  • isPassthroughTintingEnabled でパススルーを動画トーンに合わせる
  • TV App で映画を見るときの環境光効果に似る
  • 仮想と実環境の調和で没入感を高める

Portal コード例

13:12

struct PortalView : View {
    var body: some View {
        RealityView { content in
            let world = makeWorld()
            let portal = makePortal(world: world)

            content.add(world)
            content.add(portal)
        }
    }
}

public func makeWorld() -> Entity {
    let world = Entity()
    world.components[WorldComponent.self] = .init()

    let environment = try! EnvironmentResource.load(named: "SolarSystem")
    world.components[ImageBasedLightComponent.self] = .init(source: .single(environment),
                                                            intensityExponent: 6)
    world.components[ImageBasedLightReceiverComponent.self] = .init(imageBasedLight: world)

    let earth = try! Entity.load(named: "Earth")
    let moon = try! Entity.load(named: "Moon")
    let sky = try! Entity.load(named: "OuterSpace")
    world.addChild(earth)
    world.addChild(moon)
    world.addChild(sky)

    return world
}

public func makePortal(world: Entity) -> Entity {
    let portal = Entity()

    portal.components[ModelComponent.self] = .init(
        mesh: .generatePlane(width: 1, height: 1, cornerRadius: 0.5),
        materials: [PortalMaterial()]
    )
    portal.components[PortalComponent.self] = .init(target: world)

    return portal
}

キーポイント:

  • WorldComponent はエンティティツリーを別世界に属するとマーク
  • 世界内コンテンツは Portal 表面を通してのみ見える
  • ImageBasedLightComponent で画像ベース照明を設定
  • PortalMaterial() でメッシュを Portal 表面にする
  • PortalComponent(target: world) で Portal を対象世界へリンク
  • world と Portal の両方を RealityView の content に追加する

パーティクル効果 System

15:50

public class ParticleTransitionSystem: System {
    private static let query = EntityQuery(where: .has(ParticleEmitterComponent.self))

    public func update(context: SceneUpdateContext) {
        let entities = context.scene.performQuery(Self.query)
        for entity in entities {
            updateParticles(entity: entity)
        }
    }
}

public func updateParticles(entity: Entity) {
    guard var particle = entity.components[ParticleEmitterComponent.self] else {
        return
    }

    let scale = max(entity.scale(relativeTo: nil).x, 0.3)

    let vortexStrength: Float = 2.0
    let lifeSpan: Float = 1.0
    particle.mainEmitter.vortexStrength = scale * vortexStrength
    particle.mainEmitter.lifeSpan = Double(scale * lifeSpan)

    entity.components[ParticleEmitterComponent.self] = particle
}

キーポイント:

  • ParticleEmitterComponent を持つエンティティをすべてクエリ
  • エンティティスケールに応じてパーティクル属性を動的調整
  • vortexStrength は回転強度、lifeSpan は寿命
  • 変更後は Component をエンティティに再代入する必要がある
  • パーティクルリソースは Reality Composer Pro で設計可能

壁へのアンカー

18:19

import SwiftUI
import RealityKit

struct PortalApp: App {

    @State private var immersionStyle: ImmersionStyle = .mixed

    var body: some SwiftUI.Scene {
        ImmersiveSpace {
            RealityView { content in
                let anchor = AnchorEntity(.plane(.vertical, classification: .wall,
                                                 minimumBounds: [1, 1]))
                content.add(anchor)

                anchor.addChild(makePortal())
            }
        }
        .immersionStyle(selection: $immersionStyle, in: .mixed)
    }
}

キーポイント:

  • アンカーは ImmersiveSpace 内で使用する
  • .plane(.vertical, classification: .wall) で垂直壁を探索
  • minimumBounds: [1, 1] は壁が少なくとも 1×1 メートル必要
  • 一致するアンカーが見つかると RealityKit が壁に自動付着
  • .mixed immersion style はパススルーを保ちつつ仮想を表示
  • アンカー変換は App から見えず、プライバシーを保護

重要ポイント

  • やること: 壁の Portal から世界各地の風景が見える魔法の窓 App を作る。

  • 価値: Portal で実際の部屋から任意の世界への窓を開き、空間オーディオで臨場感を出せる。

  • 始め方: WorldComponent で世界コンテンツを定義し、AnchorEntity(.plane(.vertical)) で Portal を壁に固定。

  • やること: 3D モデル横に Attachment で知識ラベルを表示する教育 App を開発する。

  • 価値: Attachments で SwiftUI と 3D をシームレスに結合し、説明文がモデルと一緒に動く。

  • 始め方: RealityView の attachments で Text を定義し、entity(for:) で取得してモデルの子に追加。

  • やること: 空間で 3D 映画を再生する没入型シネマ App を作る。

  • 価値: VideoPlayerComponent は MV-HEVC 3D をサポートし、passthrough tinting で環境光を映画トーンに合わせられる。

  • 始め方: VideoPlayerComponent(avPlayer: player) で動画エンティティを作り、isPassthroughTintingEnabled = true を設定。

  • やること: 部屋にパーティクル(雪、花火)を置ける祝祭デコレーション App を開発する。

  • 価値: ParticleEmitterComponent とアンカーで効果を実空間位置に固定できる。

  • 始め方: Reality Composer Pro でパーティクルを設計し、AnchorEntity で天井や壁に固定、System で属性を動的調整。

関連セッション

コメント

GitHub Issues · utterances