はじめに

こんにちは!システム開発部のYです。

visionOS 1.xでは、Entityに対してデフォルトのハイライトによるホバーエフェクトのみが利用可能でした。しかし、visionOS 2.0からは、HoverEffectComponentを自由にカスタマイズできるようになりました。本記事では、そのカスタマイズ方法について解説します。

EntityではなくUIのホバー効果のカスタマイズ方法については、別の記事で解説しています。
気になる方は、以下の記事をご覧ください。
【visionOS】HoverEffectをカスタマイズしてみる

環境

  • Swift Version: 5.10
  • Xcode: 16.1
  • visionOS: 2.1
  • Reality Composer Pro: 2.0
  • macOS: 14.5

実装について

以下の手順で進めていきます。

前準備

シーンの変更

New Projectから作成し、Reality Composerでシーンを編集します。
サンプルモデルを1つ配置したシーンを作成しました。

ImmersiveView.swiftの変更

ImmersiveView.swiftのコードを修正します。
サンプルモデルであるEntity(ToyCar)を取得し、視線検知を行うために、取得したToyCarにInputTargetComponentCollisionComponentを設定します。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {

    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                if let toyCar = immersiveContentEntity.findEntity(named: "ToyCar") {

                    // InputTargetComponent, CollisionComponent
                    toyCar.components.set(InputTargetComponent())
                    toyCar.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.15, 0.3])]))
                }
            }
        }
    }
}

デフォルトのHoverEffectComponent

デフォルトのHoverEffectComponentはvisionOS 1.xから利用可能で、白いハイライトが表示されます。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {

    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                if let toyCar = immersiveContentEntity.findEntity(named: "ToyCar") {

                    // InputTargetComponent, CollisionComponent
                    toyCar.components.set(InputTargetComponent())
                    toyCar.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.15, 0.3])]))

                    // HoverEffectComponent
                    toyCar.components.set(HoverEffectComponent())
                }
            }
        }
    }
}

SpotlightHoverEffectStyle

フォーカス時にSpotlightの効果を適用したい場合は、SpotlightHoverEffectStyleを使用します。

フォーカス位置に応じてライトの当たり方が変化する様子が確認できます。

SpotlightHoverEffectStyleinitでは、効果の色や強度をカスタマイズすることができます。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
        
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                if let toyCar = immersiveContentEntity.findEntity(named: "ToyCar") {
                    
                    // InputTargetComponent, CollisionComponent
                    toyCar.components.set(InputTargetComponent())
                    toyCar.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.15, 0.3])]))
                                        
                    // HoverEffectComponent.SpotlightHoverEffectStyle
                    let spotlightHoverEffectStyle = HoverEffectComponent.SpotlightHoverEffectStyle(color: .lightYellow, strength: 2.0)
                    let hoverEffect = HoverEffectComponent(.spotlight(spotlightHoverEffectStyle))
                    toyCar.components.set(hoverEffect)
                }
            }
        }
    }
}

extension UIColor {
    static let lightYellow = UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
}

HighlightHoverEffectStyle

フォーカス時にHighlight効果を適用したい場合は、HighlightHoverEffectStyleを使用します。

このスタイルでは、対象が均一に強調表示されます。

HighlightHoverEffectStyleinitでは、効果の色や強度をカスタマイズすることが可能です。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
        
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                if let toyCar = immersiveContentEntity.findEntity(named: "ToyCar") {
                    
                    // InputTargetComponent, CollisionComponent
                    toyCar.components.set(InputTargetComponent())
                    toyCar.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.15, 0.3])]))
                    
                    // HoverEffectComponent.HighlightHoverEffectStyle
                    let highlightHoverEffectStyle = HoverEffectComponent.HighlightHoverEffectStyle(color: .lightYellow, strength: 2.0)
                    let hoverEffect = HoverEffectComponent(.highlight(highlightHoverEffectStyle))
                    toyCar.components.set(hoverEffect)
                }
            }
        }
    }
}

extension UIColor {
    static let lightYellow = UIColor(red: 1.0, green: 1.0, blue: 0.0, alpha: 1.0)
}

Opacity Function

SpotlightHoverEffectStyleHighlightHoverEffectStyleには、Opacity Functionという引数があります。これにより、透明素材に対するホバー効果の見え方をカスタマイズできます。
詳細は公式ドキュメントをご参照ください。

https://developer.apple.com/documentation/realitykit/hovereffectcomponent#Opacity-functions

OpacityFunction.full

エンティティのベースマテリアルの不透明度に関係なく、ホバーエフェクトが常に完全な不透明度で描画されます。

OpacityFunction.mask

エンティティのベースマテリアルの不透明度が 0.05以上 の場合、ホバーエフェクトは完全な不透明度で描画されます。
ベースマテリアルの不透明度が 0.05以下 の場合、ホバーエフェクトはベースマテリアルの不透明度に応じてフェードアウトします。

OpacityFunction.blend

ホバーエフェクトの不透明度は、エンティティのベースマテリアルの不透明度とシェーダーの出力値を掛け合わせた結果になります。
ベースマテリアルとシェーダーの出力が両方とも影響を与えます。

ShaderHoverEffectInputs

フォーカス時にShader Graphを利用した演出を適用したい場合、ShaderHoverEffectInputsを使用します。
フォーカス時に黄色に変更し、フォーカスが外れると黒色に戻る動作を実現しています。

Reality Composer Proでホバー用のShader Graph Materialの作成、マテリアルの設定、コードの変更を行う必要があります。

Reality Composer ProでShader Graph Materialを作成

HoverStateNodeが追加されています。
Intensity値は、ユーザーがフォーカスした際に0から1へと変化します。

Intensity値に応じて、フォーカス時にはForegroundColorを黄色に、フォーカスが外れた際にはBackgroundColorを黒に設定するようにしています。

Materialの設定

作成したShader Graph Materialを、ToyCarのマテリアルとして設定します。

ImmersiveView.swift

HighlightHoverEffectStyleinit では、fadeInDurationfadeOutDuration をカスタマイズできます。
また、defaultの設定では、フェードインとフェードアウトの継続時間が、デフォルトのスポットライトの継続時間に合わせて設定されます。

import SwiftUI
import RealityKit
import RealityKitContent

struct ImmersiveView: View {
        
    var body: some View {
        RealityView { content in
            // Add the initial RealityKit content
            if let immersiveContentEntity = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
                content.add(immersiveContentEntity)

                if let toyCar = immersiveContentEntity.findEntity(named: "ToyCar") {
                    
                    // InputTargetComponent, CollisionComponent
                    toyCar.components.set(InputTargetComponent())
                    toyCar.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.15, 0.3])]))
                    
                    // HoverEffectComponent.ShaderHoverEffectInputs
                    let shaderHoverEffectInputs = HoverEffectComponent.ShaderHoverEffectInputs(fadeInDuration: 1.0, fadeOutDuration: 1.0)
                    let hoverEffect = HoverEffectComponent(.shader(shaderHoverEffectInputs))
                    toyCar.components.set(hoverEffect)
                }
            }
        }
    }
}

Hierarchical-behavior

HoverEffectComponentはエンティティ階層全体にエフェクトを適用します。
最上位のエンティティに設定されたエフェクトは、すべての子エンティティにも引き継がれ、同じエフェクトが表示されます。
詳細は公式ドキュメントをご参照ください。

https://developer.apple.com/documentation/realitykit/hovereffectcomponent#Hierarchical-behavior

まとめ

HoverEffectComponentを使用したEntityのホバーエフェクトのカスタマイズ方法をまとめました。
visionOS 2.0からHoverEffectComponentのカスタマイズできるため、フォーカス時の演出を変更することができました。
また、ShaderHoverEffectInputsを使用することで、Shader Graphでの演出作成が可能となったことで多様な表現ができるようになりそうです。
この記事がお役に立てば幸いです。本記事について誤りや改善点があれば、是非ご指摘ください。

参考



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる