-
Nouveautés du rendu Metal pour les apps immersives
Découvrez les dernières améliorations apportées au rendu Metal pour les apps immersives grâce aux Compositor Services. Apprenez à ajouter des effets de survol pour mettre en valeur les éléments interactifs de votre app et à assurer un rendu plus fidèle grâce à la qualité de rendu dynamique. Découvrez le nouveau style d'immersion progressive. Et explorez comment mettre en œuvre des expériences immersives dans les apps macOS en effectuant le rendu de contenu Metal directement du Mac vers le Vision Pro.
Pour tirer le meilleur parti de cette séance, regardez d'abord « Découvrez Metal pour les apps immersives » de la WWDC23.Chapitres
- 0:00 - Introduction
- 1:58 - Nouvelles API de boucle de rendu
- 4:21 - Effets de survol
- 10:50 - Qualité de rendu dynamique
- 14:44 - Immersion progressive
- 18:32 - Rendu spatial macOS
- 23:51 - Étapes suivantes
Ressources
- Analyzing the performance of your Metal app
- Rendering hover effects in Metal immersive apps
- Optimizing GPU performance
Vidéos connexes
WWDC25
- Allez plus loin avec les jeux Metal 4
- Découvrez Metal 4
- Explorer les entrées d’accessoires spatiaux sur visionOS
- Explorez les jeux Metal 4
- Nouveautés dans SwiftUI
- Quoi de neuf dans visionOS 26
WWDC24
WWDC23
-
Rechercher dans cette vidéo…
-
-
0:01 - Scene render loop
// Scene render loop extension Renderer { func renderFrame(with scene: MyScene) { guard let frame = layerRenderer.queryNextFrame() else { return } frame.startUpdate() scene.performFrameIndependentUpdates() frame.endUpdate() let drawables = frame.queryDrawables() guard !drawables.isEmpty else { return } guard let timing = frame.predictTiming() else { return } LayerRenderer.Clock().wait(until: timing.optimalInputTime) frame.startSubmission() scene.render(to: drawable) frame.endSubmission() } } -
5:54 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer let trackingAreasFormat: MTLPixelFormat = .r8Uint if capabilities.supportedTrackingAreasFormats.contains(trackingAreasFormat) { configuration.trackingAreasFormat = trackingAreasFormat } } } -
7:54 - Object render function
// Object render function extension MyObject { func render(drawable: Drawable, renderEncoder: MTLRenderCommandEncoder) { var renderValue: LayerRenderer.Drawable.TrackingArea.RenderValue? = nil if self.isInteractive { let trackingArea = drawable.addTrackingArea(identifier: self.identifier) if self.usesHoverEffect { trackingArea.addHoverEffect(.automatic) } renderValue = trackingArea.renderValue } self.draw(with: commandEncoder, trackingAreaRenderValue: renderValue) } } -
8:26 - Metal fragment shader
// Metal fragment shader struct FragmentOut { float4 color [[color(0)]]; uint16_t trackingAreaRenderValue [[color(1)]]; }; fragment FragmentOut fragmentShader( /* ... */ ) { // ... return FragmentOut { float4(outColor, 1.0), uniforms.trackingAreaRenderValue }; } -
10:09 - Event processing
// Event processing extension Renderer { func processEvent(_ event: SpatialEventCollection.Event) { let object = scene.objects.first { $0.identifier == event.trackingAreaIdentifier } if let object { object.performAction() } } } -
13:08 - Quality constants
// Quality constants extension MyScene { struct Constants { static let menuRenderQuality: LayerRenderer.RenderQuality = .init(0.8) static let worldRenderQuality: LayerRenderer.RenderQuality = .init(0.6) static var maxRenderQuality: LayerRenderer.RenderQuality { menuRenderQuality } } } -
13:32 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer if configuration.isFoveationEnabled { configuration.maxRenderQuality = MyScene.Constants.maxRenderQuality } } -
13:57 - Set runtime render quality
// Set runtime render quality extension MyScene { var renderQuality: LayerRenderer.RenderQuality { switch type { case .world: Constants.worldRenderQuality case .menu: Constants.menuRenderQuality } } } extension Renderer { func adjustRenderQuality(for scene: MyScene) { guard layerRenderer.configuration.isFoveationEnabled else { return; } layerRenderer.renderQuality = scene.renderQuality } } -
16:58 - SwiftUI immersion style
// SwiftUI immersion style @main struct MyApp: App { @State var immersionStyle: ImmersionStyle var body: some Scene { ImmersiveSpace(id: "MyImmersiveSpace") { CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in Renderer.startRenderLoop(layerRenderer) } } .immersionStyle(selection: $immersionStyle, in: .progressive, .full) } } -
17:12 - Layer configuration
// Layer configuration struct MyConfiguration: CompositorLayerConfiguration { func makeConfiguration(capabilities: LayerRenderer.Capabilities, configuration: inout LayerRenderer.Configuration) { // Configure other aspects of LayerRenderer if configuration.layout == .layered { let stencilFormat: MTLPixelFormat = .stencil8 if capabilities.drawableRenderContextSupportedStencilFormats.contains( stencilFormat ) { configuration.drawableRenderContextStencilFormat = stencilFormat } configuration.drawableRenderContextRasterSampleCount = 1 } } } -
17:40 - Render loop
// Render loop struct Renderer { let portalStencilValue: UInt8 = 200 // Value not used in other stencil operations func renderFrame(with scene: MyScene, drawable: LayerRenderer.Drawable, commandBuffer: MTLCommandBuffer) { let drawableRenderContext = drawable.addRenderContext(commandBuffer: commandBuffer) let renderEncoder = configureRenderPass(commandBuffer: commandBuffer) drawableRenderContext.drawMaskOnStencilAttachment(commandEncoder: renderEncoder, value: portalStencilValue) renderEncoder.setStencilReferenceValue(UInt32(portalStencilValue)) scene.render(to: drawable, renderEncoder: renderEncoder) drawableRenderContext.endEncoding(commandEncoder: commandEncoder) drawable.encodePresent(commandBuffer: commandBuffer) } } -
20:55 - App structure
// App structure @main struct MyImmersiveMacApp: App { @State var immersionStyle: ImmersionStyle = .full var body: some Scene { WindowGroup { MyAppContent() } RemoteImmersiveSpace(id: "MyRemoteImmersiveSpace") { MyCompositorContent() } .immersionStyle(selection: $immersionStyle, in: .full, .progressive) } } -
21:14 - App UI
// App UI struct MyAppContent: View { @Environment(\.supportsRemoteScenes) private var supportsRemoteScenes @Environment(\.openImmersiveSpace) private var openImmersiveSpace @State private var spaceState: OpenImmersiveSpaceAction.Result? var body: some View { if !supportsRemoteScenes { Text("Remote SwiftUI scenes are not supported on this Mac.") } else if spaceState != nil { MySpaceStateView($spaceState) } else { Button("Open remote immersive space") { Task { spaceState = await openImmersiveSpace(id: "MyRemoteImmersiveSpace") } } } } } -
21:35 - Compositor content and ARKit session
// Compositor content and ARKit session struct MyCompositorContent: CompositorContent { @Environment(\.remoteDeviceIdentifier) private var remoteDeviceIdentifier var body: some CompositorContent { CompositorLayer(configuration: MyConfiguration()) { @MainActor layerRenderer in guard let remoteDeviceIdentifier else { return } let arSession = ARKitSession(device: remoteDeviceIdentifier) Renderer.startRenderLoop(layerRenderer, arSession) } } } -
23:17 - C interoperability
// Swift let remoteDevice: ar_device_t = remoteDeviceIdentifier.cDevice Renderer.start_rendering(layerRenderer, remoteDevice) // C void start_rendering(cp_layer_renderer_t layer_renderer, ar_device_t remoteDevice) { ar_session_t session = ar_session_create_with_device(remoteDevice); // ... }
-
-
- 0:00 - Introduction
Le rendu Metal sur visionOS, associé aux Compositor Services, apporte cette année de nouvelles fonctionnalités passionnantes : effets de survol sur les objets interactifs, qualité de rendu dynamique pour vos contenus, tout nouveau style d’immersion progressive et possibilité de diffuser du contenu immersif sur le Vision Pro directement depuis macOS.
- 1:58 - Nouvelles API de boucle de rendu
La boucle de rendu de visionOS connaît un changement important cette année. Au lieu de renvoyer un seul objet drawable, l’objet queryDrawables renvoie désormais un tableau contenant 1 ou 2 drawables. Un second drawable est généré automatiquement lors de l’enregistrement de vidéos haute qualité dans Reality Composer Pro. Consultez Xcode pour trouver un modèle afin de bien démarrer. Metal et Metal 4 sont pris en charge.
- 4:21 - Effets de survol
Une fois que vous avez adopté la nouvelle API de boucle de rendu, vous pouvez commencer à implémenter des effets de survol sur les objets interactifs. Grâce aux effets de survol, les utilisateurs peuvent voir quels objets sont interactifs et anticiper les cibles de leurs actions. Le système met en surbrillance de manière dynamique l’objet que l’utilisateur regarde. Par exemple, un jeu de puzzle peut mettre en évidence les pièces qui peuvent être sélectionnées. Vous pouvez le faire en utilisant la nouvelle texture de zones de suivi qui définit les différentes zones interactives de votre scène. Il y a quelques éléments supplémentaires à prendre en compte si vous utilisez le MSAA (anticrénelage multi-échantillons).
- 10:50 - Qualité de rendu dynamique
Vous pouvez restituer votre contenu avec une précision encore plus élevée. En utilisant la qualité de rendu dynamique, vous pouvez ajuster la résolution de votre contenu à la complexité de vos scènes. Cette approche s’appuie sur le rendu fovéal, qui privilégie la densité de pixels là où le spectateur regarde. Vous pouvez définir une qualité de rendu maximale, puis l’ajuster dynamiquement selon les besoins. Une meilleure qualité améliore la clarté, mais consomme plus de mémoire et d’énergie. L’équilibre entre qualité et performance est crucial. Utilisez des outils comme Instruments et le débogueur Metal pour trouver le bon équilibre.
- 14:44 - Immersion progressive
Le contenu peut désormais s’afficher dans un portail immersif progressif. Les utilisateurs contrôlent le niveau d’immersion en tournant la Digital Crown. Cela les ancre dans l’environnement réel et peut les aider à se sentir plus à l’aise devant des scènes complexes avec du mouvement. Pour l’activer, demandez au système de fournir un stencil buffer afin de masquer le contenu hors des limites du portail. Le système applique un effet de fondu sur les bords du portail, créant une transition fluide entre l’environnement réel et le contenu rendu. Les pixels hors du portail ne sont pas rendus, ce qui économise des ressources. Les détails d’implémentation sont partagés.
- 18:32 - Rendu spatial macOS
Le rendu spatial macOS vous permet d’exploiter la puissance de votre Mac pour diffuser des contenus immersifs directement sur l’Apple Vision Pro. Cette fonctionnalité enrichit les apps Mac existantes avec des expériences immersives, comme des aperçus 3D en temps réel. ARKit et worldTrackingProvider sont désormais disponibles sur macOS. Cela vous permet d’interroger la position du Vision Pro dans l’espace. Le RemoteImmersiveSpace de macOS héberge le CompositorLayer et l’ARKitSession, comme le ferait une app native visionOS. Un nouvel ID (remoteDeviceIdentifier) permet de connecter la session ARKit de votre Mac au Vision Pro. Toutes les API concernées disposent d’équivalents natifs en C.
- 23:51 - Étapes suivantes
Ces nouvelles capacités de Metal et des Compositor Services sur visionOS offrent une meilleure interactivité, une fidélité visuelle accrue et un nouveau style d’immersion progressive dans les apps et jeux. Ensuite, consultez « Set the scene with SwiftUI in visionOS » et « What’s new in visionOS 26 ».