-
Novidades na renderização no Metal para apps imersivos
Descubra as melhorias mais recentes na renderização no Metal para apps imersivos com o framework Compositor Services. Saiba como adicionar efeitos de hover para destacar os elementos interativos do seu app e como renderizar com mais fidelidade usando qualidade de renderização dinâmica. Saiba mais sobre o novo estilo de imersão progressiva. Além disso, descubra como incluir experiências imersivas nos apps para macOS renderizando o conteúdo diretamente no Metal do Mac para o Vision Pro.
Para aproveitar ao máximo esta sessão, primeiro assista ao vídeo “Discover Metal for immersive apps” da WWDC23.Capítulos
- 0:00 - Introdução
- 1:58 - Novas APIs de loops de renderização
- 4:21 - Efeitos de foco
- 10:50 - Qualidade de renderização dinâmica
- 14:44 - Imersão progressiva
- 18:32 - Renderização espacial para macOS
- 23:51 - Próximas etapas
Recursos
- Analyzing the performance of your Metal app
- Rendering hover effects in Metal immersive apps
- Optimizing GPU performance
Vídeos relacionados
WWDC25
- Aprimore jogos com o Metal 4
- Conheça o Metal 4
- Explore a entrada de acessórios espaciais no visionOS
- Explore jogos feitos com o Metal 4
- Novidades do SwiftUI
- Novidades do visionOS 26
WWDC24
WWDC23
-
Buscar neste vídeo...
-
-
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 - Introdução
A renderização com o Metal no visionOS, juntamente com os Compositor Services, traz recursos novos este ano, incluindo: efeitos de destaque em objetos interativos, qualidade de renderização dinâmica para o conteúdo, novo estilo de imersão progressiva e a capacidade de renderizar conteúdo imersivo no Vision Pro a partir do macOS.
- 1:58 - Novas APIs de loops de renderização
Houve uma mudança no loop de renderização no visionOS. Em vez de retornar um recurso gráfico, o objeto queryDrawables retorna uma matriz com um ou dois recursos gráficos. O segundo recurso gráfico estará presente durante a gravação de um vídeo em alta qualidade com o Reality Composer Pro. Confira no Xcode um modelo para ajudar você a começar. O Metal e o Metal 4 são compatíveis.
- 4:21 - Efeitos de foco
Depois de adotar a nova API de loop de renderização, comece a implementar efeitos de destaque nos objetos interativos. Com os efeitos de foco, as pessoas podem ver quais objetos são interativos e antecipar os alvos de suas ações. O sistema destacará dinamicamente o objeto que a pessoa estiver olhando. Um jogo de quebra-cabeça pode destacar as peças que o jogador pode selecionar. Para isso, use a nova textura de áreas de rastreamento, que define as diferentes regiões interativas na cena. Há algumas considerações adicionais se você estiver usando MSAA (multisample antialiasing).
- 10:50 - Qualidade de renderização dinâmica
Você pode desenhar seu conteúdo com uma fidelidade ainda maior. Ao usar a qualidade de renderização dinâmica, você pode ajustar a resolução do conteúdo com base na complexidade das cenas. Ela se baseia na renderização foveada, priorizando a densidade de pixels onde o espectador está olhando. Defina a qualidade máxima de renderização e ajuste a qualidade em tempo de execução nesse intervalo. Qualidade mais alta melhora a clareza, mas aumenta o uso de memória e energia. Equilibrar qualidade e desempenho é fundamental. Encontre o equilíbrio com ferramentas como o Instruments e o depurador do Metal.
- 14:44 - Imersão progressiva
Novidade este ano: renderize conteúdo dentro de um portal imersivo progressivo. Com isso, as pessoas controlam o nível de imersão girando a Digital Crown. Isso as conecta ao ambiente real e pode ajudar a proporcionar mais conforto na visualização de cenas complexas com movimento. Para implementar isso, o sistema deve fornecer um buffer de estêncil para mascarar o conteúdo fora dos limites do portal. O sistema aplica um efeito de esmaecimento nas bordas do portal, criando uma transição suave entre os ambientes real e renderizado. Os pixels fora da visão do portal não são renderizados, poupando poder de processamento. Os detalhes da implementação são compartilhados.
- 18:32 - Renderização espacial para macOS
A renderização espacial no macOS permite aproveitar o poder do Mac para renderizar e transmitir conteúdo imersivo para o Apple Vision Pro. Esse recurso permite que apps Mac sejam aprimorados com experiências imersivas, como pré-visualizações em tempo real de modelagem 3D. O ARKit e o worldTrackingProvider agora estão disponíveis no macOS. Isso permite consultar a localização do Vision Pro no espaço. O RemoteImmersiveSpace do macOS hospeda o CompositorLayer e o ARKitSession, como um app nativo do visionOS faria. Há um novo remoteDeviceIdentifier que será usado para conectar a sessão ARKit do Mac ao Vision Pro. E todas as APIs relevantes têm equivalentes nativas em C.
- 23:51 - Próximas etapas
Essas funcionalidades do Metal e dos Compositor Services no visionOS permitem oferecer melhor interatividade, maior fidelidade e o novo estilo de imersão progressiva aos apps e jogos. Confira “Defina a cena com SwiftUI no visionOS” e “Novidades do visionOS 26”.