Tüm Yazılar
KategorivisionOS
Okuma Süresi
16 dk okuma
Yayın Tarihi
...
Kelime Sayısı
3.264kelime

Kahveni hazırla - bu içerikli bir makale!

visionOS 2 yeni API"ler, Environment API, hand tracking, gaze tracking, RealityKit 4 ve immersive experience tasarımı — gerçek production deneyimi.

visionOS 2: Spatial Computing ile Production App Geliştirme 2026

# visionOS 2: Spatial Computing ile Production App Geliştirme 2026

Apple Vision Pro ve visionOS 2, spatial computing'i gerçek bir geliştirici platformuna dönüştürdü. İlk nesil visionOS'un bazı kısıtlamaları ve "deneysel" hissiyatı, visionOS 2 ile yerini olgun, production-ready bir API setine bıraktı. Bu rehberde, gerçek bir visionOS 2 uygulaması geliştirirken öğrendiğim her şeyi paylaşıyorum: Environment API'den hand tracking'e, RealityKit 4'ten Metal 4 shader optimizasyonuna kadar.

💡 Pro Tip: visionOS 2 geliştirirken en büyük tuzak, spatial computing'i ekran tabanlı düşünmek. "Bu pencere nerede görünecek?" yerine "Kullanıcı bu nesneyle nasıl etkileşecek?" diye düşünmeye başladığınızda, uygulamanızın kalitesi dramatik biçimde yükseliyor.

İçindekiler

  • visionOS 2 Mimarisi: Window Types
  • Environment API ve Persistent Anchors
  • ARKit 7 Hand Tracking (27-Joint Detection)
  • Gaze Tracking ve Privacy Layer
  • RealityKit 4 Component Sistemi
  • ImmersiveSpace Lifecycle
  • Metal 4 Compute Shaders
  • USDZ ile 3D Content Pipeline
  • Spatial Gesture Recognizers
  • Physics Simulation
  • Custom Shaders ile MaterialX
  • Polygon Budget ve Optimizasyon

1. visionOS 2 Mimarisi: Window Types

visionOS 2'de üç temel window tipi var ve bunların doğru kullanımı, uygulamanızın deneyim kalitesini doğrudan belirliyor.

WindowGroup (2D Windows): Geleneksel iOS/macOS tarzı pencereler. Kullanıcı bunları uzayda istediği yere yerleştirebiliyor. Birden fazla WindowGroup aynı anda açık olabiliyor.

Volumetric Windows: 3D içerik için özel pencere tipi. Pencere boyutlarını siz belirleyebiliyorsunuz ancak kullanıcı pencereyi döndürebiliyor. Shared space'de çalışıyor, yani diğer uygulamalarla aynı anda görünebiliyor.

ImmersiveSpace: Tam immersive deneyim. Kullanıcının tüm alanı size ait oluyor. Shared space ve full space olmak üzere iki modu var.

swift
1import SwiftUI
2import RealityKit
3 
4@main
5struct SpatialApp: App {
6 var body: some Scene {
7 // Standard 2D pencere
8 WindowGroup {
9 ContentView()
10 }
11 
12 // 3D volumetric pencere
13 WindowGroup(id: "3D-Model-Viewer") {
14 ModelViewerView()
15 }
16 .windowStyle(.volumetric)
17 .defaultSize(width: 0.8, height: 0.8, depth: 0.4, in: .meters)
18 
19 // Immersive deneyim
20 ImmersiveSpace(id: "Main-Immersive") {
21 ImmersiveView()
22 }
23 .immersionStyle(
24 selection: .constant(.mixed),
25 in: .mixed, .progressive, .full
26 )
27 }
28}
29 
30// Immersive space'e geçiş
31struct ContentView: View {
32 @Environment(\.openImmersiveSpace) private var openImmersiveSpace
33 @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
34 @Environment(\.openWindow) private var openWindow
35 
36 @State private var immersiveSpaceIsShown = false
37 
38 var body: some View {
39 NavigationStack {
40 VStack(spacing: 20) {
41 Button("3D Model Görüntüle") {
42 openWindow(id: "3D-Model-Viewer")
43 }
44 .buttonStyle(.bordered)
45 
46 Button(immersiveSpaceIsShown ? "Çık" : "Immersive Moda Geç") {
47 Task {
48 if immersiveSpaceIsShown {
49 await dismissImmersiveSpace()
50 immersiveSpaceIsShown = false
51 } else {
52 let result = await openImmersiveSpace(id: "Main-Immersive")
53 immersiveSpaceIsShown = result == .opened
54 }
55 }
56 }
57 .buttonStyle(.borderedProminent)
58 }
59 .navigationTitle("Spatial App")
60 .padding()
61 }
62 }
63}

2. Environment API ve Persistent Anchors

visionOS 2'nin en güçlü yeni özelliği Environment API. Bu API, uygulamanızın gerçek dünya yüzeylerini (duvar, zemin, masa) algılamasını ve nesneleri bu yüzeylere kalıcı olarak "sabitlemesini" sağlıyor.

swift
1import ARKit
2import RealityKit
3import SwiftUI
4 
5class EnvironmentTracker: ObservableObject {
6 private let arSession = ARKitSession()
7 private let planeDetection = PlaneDetectionProvider(
8 alignments: [.horizontal, .vertical]
9 )
10 private let sceneReconstruction = SceneReconstructionProvider(
11 modes: [.classification]
12 )
13 
14 @Published var detectedPlanes: [PlaneAnchor] = []
15 @Published var meshAnchors: [MeshAnchor] = []
16 
17 func startTracking() async {
18 guard PlaneDetectionProvider.isSupported,
19 SceneReconstructionProvider.isSupported else {
20 print("Environment API not supported")
21 return
22 }
23 
24 do {
25 try await arSession.run([planeDetection, sceneReconstruction])
26 } catch {
27 print("ARKit session error: \(error)")
28 return
29 }
30 
31 // Plane detection updates
32 for await update in planeDetection.anchorUpdates {
33 await handlePlaneUpdate(update)
34 }
35 }
36 
37 @MainActor
38 private func handlePlaneUpdate(_ update: AnchorUpdate<PlaneAnchor>) {
39 switch update.event {
40 case .added:
41 detectedPlanes.append(update.anchor)
42 case .updated:
43 if let index = detectedPlanes.firstIndex(
44 where: { $0.id == update.anchor.id }
45 ) {
46 detectedPlanes[index] = update.anchor
47 }
48 case .removed:
49 detectedPlanes.removeAll { $0.id == update.anchor.id }
50 }
51 }
52}
53 
54// Persistent anchor: Nesneleri gerçek dünyaya sabitleme
55class PersistentAnchorManager {
56 private let worldTracking = WorldTrackingProvider()
57 private let arSession = ARKitSession()
58 
59 // visionOS 2: Persistent anchor kaydetme
60 func saveAnchor(
61 entity: Entity,
62 in realityView: RealityView<some View>
63 ) async throws -> UUID {
64 // WorldAnchor oluştur
65 var anchor = WorldAnchor(
66 originFromAnchorTransform: entity.transformMatrix(
67 relativeTo: nil
68 )
69 )
70 
71 // Anchor"ı kaydet
72 try await worldTracking.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())
73 
74 return anchor.id
75 }
76 
77 // Kaydedilmiş anchor"ı geri yükle
78 func loadSavedAnchor(id: UUID) async -> WorldAnchor? {
79 for await update in worldTracking.anchorUpdates {
80 if update.anchor.id == id {
81 return update.anchor
82 }
83 }
84 return nil
85 }
86}

Persistent anchors, kullanıcının bir nesneyi masasına veya duvarına yerleştirip uygulamayı kapatıp açtığında nesnenin aynı yerde durmasını sağlıyor. Bu özellik, productivity uygulamaları için game-changing bir UX sunuyor.


3. ARKit 7 Hand Tracking (27-Joint Detection)

visionOS 2, ARKit 7 ile birlikte 27-noktalı hand tracking sunuyor. Her el için 27 joint noktasının pozisyon ve rotasyon verisi gerçek zamanlı alınabiliyor. Bu, önceki versiyonlara kıyasla çok daha doğru ve ayrıntılı gesture recognition imkânı sağlıyor.

swift
1import ARKit
2import simd
3 
4class HandTracker: ObservableObject {
5 private let handTracking = HandTrackingProvider()
6 private let arSession = ARKitSession()
7 
8 @Published var leftHand: HandSkeleton?
9 @Published var rightHand: HandSkeleton?
10 @Published var detectedGesture: CustomGesture = .none
11 
12 enum CustomGesture {
13 case none
14 case pinch
15 case openPalm
16 case pointingUp
17 case fist
18 case thumbsUp
19 }
20 
21 func startTracking() async {
22 guard HandTrackingProvider.isSupported else {
23 print("Hand tracking not supported")
24 return
25 }
26 
27 do {
28 try await arSession.run([handTracking])
29 } catch {
30 print("Hand tracking error: \(error)")
31 return
32 }
33 
34 for await update in handTracking.anchorUpdates {
35 await handleHandUpdate(update)
36 }
37 }
38 
39 @MainActor
40 private func handleHandUpdate(_ update: AnchorUpdate<HandAnchor>) {
41 let hand = update.anchor
42 
43 switch hand.chirality {
44 case .left:
45 leftHand = hand.handSkeleton
46 case .right:
47 rightHand = hand.handSkeleton
48 if let skeleton = hand.handSkeleton {
49 detectedGesture = detectGesture(skeleton: skeleton)
50 }
51 }
52 }
53 
54 // 27-joint data ile custom gesture detection
55 private func detectGesture(skeleton: HandSkeleton) -> CustomGesture {
56 // Parmak joint'lerini al
57 let thumbTip = skeleton.joint(.thumbTip)
58 let indexTip = skeleton.joint(.indexFingerTip)
59 let middleTip = skeleton.joint(.middleFingerTip)
60 let ringTip = skeleton.joint(.ringFingerTip)
61 let littleTip = skeleton.joint(.littleFingerTip)
62 
63 let thumbKnuckle = skeleton.joint(.thumbKnuckle)
64 let indexKnuckle = skeleton.joint(.indexFingerKnuckle)
65 
66 guard thumbTip.isTracked, indexTip.isTracked else {
67 return .none
68 }
69 
70 // Pinch detection: thumb ve index arası mesafe
71 let thumbPos = simd_float3(thumbTip.anchorFromJointTransform.columns.3.x,
72 thumbTip.anchorFromJointTransform.columns.3.y,
73 thumbTip.anchorFromJointTransform.columns.3.z)
74 let indexPos = simd_float3(indexTip.anchorFromJointTransform.columns.3.x,
75 indexTip.anchorFromJointTransform.columns.3.y,
76 indexTip.anchorFromJointTransform.columns.3.z)
77 
78 let pinchDistance = simd_distance(thumbPos, indexPos)
79 
80 if pinchDistance < 0.02 { // 2cm
81 return .pinch
82 }
83 
84 // Thumbs up detection
85 let thumbTipPos = simd_float3(
86 thumbTip.anchorFromJointTransform.columns.3.x,
87 thumbTip.anchorFromJointTransform.columns.3.y,
88 thumbTip.anchorFromJointTransform.columns.3.z
89 )
90 let thumbKnucklePos = simd_float3(
91 thumbKnuckle.anchorFromJointTransform.columns.3.x,
92 thumbKnuckle.anchorFromJointTransform.columns.3.y,
93 thumbKnuckle.anchorFromJointTransform.columns.3.z
94 )
95 
96 let thumbDirection = simd_normalize(thumbTipPos - thumbKnucklePos)
97 if thumbDirection.y > 0.8 && pinchDistance > 0.05 {
98 return .thumbsUp
99 }
100 
101 // Open palm: tüm parmaklar uzanmış
102 let allExtended = [indexTip, middleTip, ringTip, littleTip].allSatisfy {
103 $0.isTracked
104 }
105 
106 if allExtended && pinchDistance > 0.06 {
107 return .openPalm
108 }
109 
110 return .none
111 }
112}
113 
114// Custom gesture ile RealityKit etkileşimi
115struct HandTrackingRealityView: View {
116 @StateObject private var handTracker = HandTracker()
117 @State private var selectedEntity: Entity?
118 
119 var body: some View {
120 RealityView { content in
121 // 3D model yükle
122 if let model = try? await Entity(
123 named: "robot",
124 in: .main
125 ) {
126 model.name = "robot"
127 model.position = [0, 0, -0.5]
128 content.add(model)
129 }
130 }
131 .task {
132 await handTracker.startTracking()
133 }
134 .onChange(of: handTracker.detectedGesture) { _, gesture in
135 handleGesture(gesture)
136 }
137 }
138 
139 private func handleGesture(_ gesture: HandTracker.CustomGesture) {
140 switch gesture {
141 case .pinch:
142 // Nesneleri pinch ile tutma
143 print("Pinch detected — grab object")
144 case .thumbsUp:
145 // Onay aksiyonu
146 print("Thumbs up — confirm")
147 case .openPalm:
148 // Menu aç
149 print("Open palm — show menu")
150 default:
151 break
152 }
153 }
154}

27-joint hand tracking ile pratik olarak el hareketlerinin tamamını algılayabiliyorsunuz. Önemli not: Hand tracking, Persona özelliğiyle çakışıyor. Kullanıcı Persona aktifken hand tracking verisi kısıtlanıyor, bu yüzden uygulamanız Persona modunda da çalışacak şekilde graceful degradation planlamalısınız.


4. Gaze Tracking ve Privacy Layer

visionOS 2, gaze tracking için kullanıcı gizliliğini ön plana alan bir API sunuyor. Kullanıcının tam olarak nereye baktığını değil, neyle etkileşime girebileceğini belirlemenizi sağlayan bir soyutlama katmanı var.

Önemli: visionOS'ta eye tracking verisi doğrudan uygulamalara verilmiyor. Sistem, kullanıcının hangi UI elementine baktığını otomatik olarak "hover" state'ine çeviriyor. Bu privacy-first yaklaşım, uygulamaların hassas göz hareketi verisi toplamasını engelliyor.

Uygulamanız için gaze-based interaction şu şekilde çalışıyor: SwiftUI elementleri için hoverEffect() modifier yeterli. RealityKit entityleri için InputTargetComponent ve HoverEffectComponent kullanılıyor.

swift
1import RealityKit
2import SwiftUI
3 
4// RealityKit entity"inde hover ve gaze interaction
5struct GazeInteractiveView: View {
6 var body: some View {
7 RealityView { content in
8 // Interaktif küre oluştur
9 let sphere = ModelEntity(
10 mesh: .generateSphere(radius: 0.1),
11 materials: [SimpleMaterial(
12 color: .systemBlue,
13 isMetallic: false
14 )]
15 )
16 
17 // visionOS 2: Input target ve collision ekle
18 sphere.components.set(InputTargetComponent())
19 sphere.components.set(CollisionComponent(
20 shapes: [.generateSphere(radius: 0.1)]
21 ))
22 
23 // Hover effect — gaze ile etkileşim
24 sphere.components.set(HoverEffectComponent(
25 .highlight(.init(
26 color: .white,
27 strength: 3.0
28 ))
29 ))
30 
31 sphere.position = [0, 1.5, -0.5]
32 content.add(sphere)
33 }
34 .gesture(
35 SpatialTapGesture()
36 .targetedToAnyEntity()
37 .onEnded { value in
38 handleTap(on: value.entity)
39 }
40 )
41 }
42 
43 private func handleTap(on entity: Entity) {
44 // Tap animasyonu
45 var transform = entity.transform
46 transform.scale = SIMD3<Float>(1.2, 1.2, 1.2)
47 
48 entity.move(
49 to: transform,
50 relativeTo: entity.parent,
51 duration: 0.1,
52 timingFunction: .easeOut
53 )
54 
55 // Geri küçül
56 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
57 var originalTransform = entity.transform
58 originalTransform.scale = SIMD3<Float>(1, 1, 1)
59 entity.move(
60 to: originalTransform,
61 relativeTo: entity.parent,
62 duration: 0.1,
63 timingFunction: .easeIn
64 )
65 }
66 }
67}

5. RealityKit 4 Component Sistemi

RealityKit 4, Entity-Component-System (ECS) mimarisini daha da güçlendirdi. Custom component'lar ve sistem tanımlamak artık çok daha temiz.

swift
1import RealityKit
2 
3// Custom component tanımlama
4struct RotationComponent: Component {
5 var speed: Float = 1.0
6 var axis: SIMD3<Float> = [0, 1, 0]
7 var isRotating: Bool = true
8}
9 
10struct FloatingComponent: Component {
11 var amplitude: Float = 0.05
12 var frequency: Float = 1.0
13 var startHeight: Float = 0
14 var timeOffset: Float = 0
15}
16 
17struct GlowComponent: Component {
18 var color: SIMD3<Float> = [1, 0.8, 0.2]
19 var intensity: Float = 2.0
20 var pulseSpeed: Float = 1.5
21}
22 
23// RealityKit 4: System tanımlama
24class RotationSystem: System {
25 static let query = EntityQuery(
26 where: .has(RotationComponent.self)
27 )
28 
29 required init(scene: Scene) {}
30 
31 func update(context: SceneUpdateContext) {
32 for entity in context.entities(
33 matching: Self.query,
34 updatingSystemWhen: .rendering
35 ) {
36 guard var rotation = entity.components[RotationComponent.self],
37 rotation.isRotating else { continue }
38 
39 let angle = rotation.speed * Float(context.deltaTime)
40 let rotationQuaternion = simd_quatf(
41 angle: angle,
42 axis: rotation.axis
43 )
44 entity.orientation = entity.orientation * rotationQuaternion
45 }
46 }
47}
48 
49class FloatingSystem: System {
50 static let query = EntityQuery(
51 where: .has(FloatingComponent.self)
52 )
53 
54 required init(scene: Scene) {}
55 
56 func update(context: SceneUpdateContext) {
57 let time = Float(Date().timeIntervalSince1970)
58 
59 for entity in context.entities(
60 matching: Self.query,
61 updatingSystemWhen: .rendering
62 ) {
63 guard var floating = entity.components[FloatingComponent.self] else { continue }
64 
65 let yOffset = floating.amplitude * sin(
66 floating.frequency * time + floating.timeOffset
67 )
68 entity.position.y = floating.startHeight + yOffset
69 }
70 }
71}
72 
73// Entity factory: Component'ları bir araya getir
74class SpatialEntityFactory {
75 static func createFloatingOrb(
76 at position: SIMD3<Float>,
77 color: UIColor = .systemCyan
78 ) -> ModelEntity {
79 let orb = ModelEntity(
80 mesh: .generateSphere(radius: 0.08),
81 materials: [
82 PhysicallyBasedMaterial.makeEmissive(color: color, intensity: 2.0)
83 ]
84 )
85 
86 orb.position = position
87 
88 // Component'ları ekle
89 orb.components.set(RotationComponent(speed: 0.5))
90 orb.components.set(FloatingComponent(
91 amplitude: 0.03,
92 frequency: 0.8,
93 startHeight: position.y,
94 timeOffset: Float.random(in: 0...Float.pi * 2)
95 ))
96 orb.components.set(InputTargetComponent())
97 orb.components.set(CollisionComponent(
98 shapes: [.generateSphere(radius: 0.08)]
99 ))
100 orb.components.set(HoverEffectComponent())
101 
102 return orb
103 }
104}
105 
106extension PhysicallyBasedMaterial {
107 static func makeEmissive(
108 color: UIColor,
109 intensity: Float
110 ) -> PhysicallyBasedMaterial {
111 var material = PhysicallyBasedMaterial()
112 material.baseColor = .init(tint: color)
113 material.emissiveColor = .init(color: color)
114 material.emissiveIntensity = intensity
115 material.roughness = .init(floatLiteral: 0.1)
116 material.metallic = .init(floatLiteral: 0.0)
117 return material
118 }
119}

6. ImmersiveSpace Lifecycle

ImmersiveSpace lifecycle yönetimi, production uygulamalar için kritik. Kullanıcı başka uygulamaya geçtiğinde, telefon geldiğinde veya notification aldığında immersive space nasıl davranmalı?

swift
1import SwiftUI
2import RealityKit
3 
4struct ImmersiveView: View {
5 @Environment(\.scenePhase) private var scenePhase
6 @StateObject private var sceneManager = ImmersiveSceneManager()
7 
8 var body: some View {
9 RealityView { content, attachments in
10 await sceneManager.setupScene(content: content)
11 } update: { content, attachments in
12 sceneManager.updateScene(content: content)
13 } attachments: {
14 // SwiftUI view'leri 3D uzaya yerleştir
15 Attachment(id: "info-panel") {
16 InfoPanelView(manager: sceneManager)
17 .frame(width: 400, height: 300)
18 }
19 }
20 .task {
21 await sceneManager.startTracking()
22 }
23 // Scene phase değişimlerini handle et
24 .onChange(of: scenePhase) { _, newPhase in
25 switch newPhase {
26 case .active:
27 sceneManager.resumeExperience()
28 case .inactive:
29 // Telefon geldi veya notification — geçici durdur
30 sceneManager.pauseExperience()
31 case .background:
32 // Kullanıcı başka uygulamaya geçti — tamamen durdur
33 sceneManager.suspendExperience()
34 @unknown default:
35 break
36 }
37 }
38 }
39}
40 
41class ImmersiveSceneManager: ObservableObject {
42 @Published var isExperienceActive = false
43 @Published var frameCount = 0
44 
45 private var updateTask: Task<Void, Never>?
46 
47 func setupScene(content: RealityViewContent) async {
48 // Sahneyi hazırla
49 isExperienceActive = true
50 }
51 
52 func updateScene(content: RealityViewContent) {
53 frameCount += 1
54 }
55 
56 func startTracking() async {
57 // ARKit ve diğer tracking başlat
58 }
59 
60 func pauseExperience() {
61 // Animasyonları duraklat, tracking'i kısıtla
62 isExperienceActive = false
63 updateTask?.cancel()
64 }
65 
66 func resumeExperience() {
67 isExperienceActive = true
68 startUpdateLoop()
69 }
70 
71 func suspendExperience() {
72 pauseExperience()
73 // Kaynakları serbest bırak
74 }
75 
76 private func startUpdateLoop() {
77 updateTask = Task {
78 while !Task.isCancelled && isExperienceActive {
79 await Task.yield()
80 }
81 }
82 }
83}

7. Metal 4 Compute Shaders

visionOS 2, Apple Silicon'un gücünü tam kullanmak için Metal 4 compute shader'larını destekliyor. Özellikle particle sistemleri ve physics hesaplamaları için GPU compute pipeline kritik.

GPU tarafında particle simülasyonu yazmak için MTLComputePipelineState ve custom Metal shader kullanılıyor. visionOS'ta Metal kullanımı iOS'tan farklı: her iki göz için ayrı render pass yönetmeniz gerekiyor. MTKView yerine CAMetalLayer veya doğrudan RealityKit'in render callback'leri üzerinden çalışmak daha performanslı.

Metal 4'ün visionOS 2'ye özgü en önemli özelliği: MTLDevice üzerinde supportsFamily(.metal4) kontrolü artık Vision Pro donanımında true dönüyor. Bu, mesh shading, ray tracing ve argument buffer tier 2 desteği anlamına geliyor.


8. USDZ ile 3D Content Pipeline

visionOS için içerik üretiminin temeli USDZ formatı. Apple"ın Reality Composer Pro aracı, USDZ asset'lerini visionOS'a özel optimize etmenizi sağlıyor.

Production USDZ pipeline:

  1. Kaynak format: Blender veya Maya'da FBX/OBJ olarak export
  2. Dönüştürme: usdzconvert CLI aracı veya Reality Composer Pro
  3. Texture optimizasyonu: ASTC compression (visionOS donanım desteği var)
  4. LOD (Level of Detail): Uzakta düşük polygon, yakında yüksek
  5. Material: UsdPreviewSurface veya MaterialX

USDZ asset"ınızı RealityKit'e yüklerken async loading kullanın, main thread'i bloklamayın:

swift
1import RealityKit
2 
3class ModelLoader {
4 // Async model yükleme
5 func loadModel(named name: String) async throws -> ModelEntity {
6 let entity = try await ModelEntity(
7 named: name,
8 in: .main
9 )
10 
11 // LOD ayarları
12 if var modelComponent = entity.components[ModelComponent.self] {
13 // Uzak mesafe için düşük detail
14 entity.components.set(modelComponent)
15 }
16 
17 return entity
18 }
19 
20 // Birden fazla modeli paralel yükle
21 func loadMultipleModels(names: [String]) async throws -> [ModelEntity] {
22 return try await withThrowingTaskGroup(of: ModelEntity.self) { group in
23 for name in names {
24 group.addTask {
25 try await self.loadModel(named: name)
26 }
27 }
28 
29 var models: [ModelEntity] = []
30 for try await model in group {
31 models.append(model)
32 }
33 return models
34 }
35 }
36 
37 // Texture streaming — büyük asset"ler için
38 func preloadTextures(for entity: ModelEntity) async {
39 // Entity"nin tüm texture"larını önceden belleğe al
40 guard let modelComponent = entity.components[ModelComponent.self] else {
41 return
42 }
43 
44 // Material texture"larını iterate et
45 for var material in modelComponent.materials {
46 // PhysicallyBasedMaterial için texture preload
47 if var pbr = material as? PhysicallyBasedMaterial {
48 // BaseColor texture
49 if let baseColorTexture = pbr.baseColor.texture {
50 _ = baseColorTexture.resource
51 }
52 }
53 }
54 }
55}

9. Spatial Gesture Recognizers

visionOS 2'de gesture recognition, iOS'tan çok farklı çalışıyor. El hareketleri, göz odağı ve fiziksel dokunuş birleşerek "spatial gesture" oluşturuyor.

swift
1import SwiftUI
2import RealityKit
3 
4struct SpatialGestureView: View {
5 @State private var dragOffset: SIMD3<Float> = .zero
6 @State private var scale: Float = 1.0
7 @State private var rotation: simd_quatf = .init(ix: 0, iy: 0, iz: 0, r: 1)
8 
9 var body: some View {
10 RealityView { content in
11 let box = ModelEntity(
12 mesh: .generateBox(size: 0.2, cornerRadius: 0.02),
13 materials: [SimpleMaterial(
14 color: .systemIndigo,
15 isMetallic: true
16 )]
17 )
18 box.name = "main-box"
19 box.components.set(InputTargetComponent())
20 box.components.set(CollisionComponent(
21 shapes: [.generateBox(size: [0.2, 0.2, 0.2])]
22 ))
23 box.components.set(HoverEffectComponent())
24 content.add(box)
25 }
26 // Drag gesture
27 .gesture(
28 DragGesture()
29 .targetedToAnyEntity()
30 .onChanged { value in
31 let translation = value.convert(
32 value.gestureValue.translation3D,
33 from: .local,
34 to: .scene
35 )
36 value.entity.position = value.startTargetedEntity.position + SIMD3<Float>(translation)
37 }
38 )
39 // Magnify (scale) gesture
40 .gesture(
41 MagnifyGesture()
42 .targetedToAnyEntity()
43 .onChanged { value in
44 let newScale = Float(value.gestureValue.magnification)
45 value.entity.scale = SIMD3<Float>(
46 repeating: max(0.1, min(3.0, newScale))
47 )
48 }
49 )
50 // Rotate gesture
51 .gesture(
52 RotateGesture3D()
53 .targetedToAnyEntity()
54 .onChanged { value in
55 let rotation = simd_quatf(value.gestureValue.rotation)
56 value.entity.orientation = rotation
57 }
58 )
59 }
60}

10. Physics Simulation

RealityKit 4'ün physics engine'i, visionOS 2'de gerçek zamanlı collision detection ve rigid body simulation sunuyor.

swift
1import RealityKit
2 
3class PhysicsSceneBuilder {
4 func buildPhysicsScene(in content: RealityViewContent) {
5 // Zemin düzlemi
6 let floorMesh = MeshResource.generatePlane(
7 width: 2,
8 depth: 2
9 )
10 let floorMaterial = SimpleMaterial(
11 color: .systemGray6,
12 isMetallic: false
13 )
14 let floorEntity = ModelEntity(
15 mesh: floorMesh,
16 materials: [floorMaterial]
17 )
18 floorEntity.position = [0, 0, -1]
19 
20 // Static physics body — hareket etmez ama çarpışma alır
21 floorEntity.components.set(PhysicsBodyComponent(
22 massProperties: .default,
23 material: .generate(
24 staticFriction: 0.6,
25 dynamicFriction: 0.4,
26 restitution: 0.3
27 ),
28 mode: .static
29 ))
30 floorEntity.components.set(CollisionComponent(
31 shapes: [.generateBox(size: [2, 0.01, 2])]
32 ))
33 content.add(floorEntity)
34 
35 // Dinamik objeler
36 for i in 0..<5 {
37 let cube = createPhysicsCube(
38 at: [
39 Float.random(in: -0.4...0.4),
40 1.0 + Float(i) * 0.3,
41 -0.8
42 ]
43 )
44 content.add(cube)
45 }
46 }
47 
48 private func createPhysicsCube(at position: SIMD3<Float>) -> ModelEntity {
49 let size: Float = 0.08
50 let cube = ModelEntity(
51 mesh: .generateBox(size: size, cornerRadius: 0.005),
52 materials: [SimpleMaterial(
53 color: [.systemRed, .systemOrange, .systemYellow,
54 .systemGreen, .systemBlue].randomElement()!,
55 isMetallic: false
56 )]
57 )
58 cube.position = position
59 
60 // Dynamic physics body — yerçekimine tabi
61 cube.components.set(PhysicsBodyComponent(
62 massProperties: .init(mass: 0.1),
63 material: .generate(
64 staticFriction: 0.5,
65 dynamicFriction: 0.3,
66 restitution: 0.4
67 ),
68 mode: .dynamic
69 ))
70 cube.components.set(CollisionComponent(
71 shapes: [.generateBox(size: [size, size, size])]
72 ))
73 cube.components.set(InputTargetComponent())
74 
75 return cube
76 }
77}

11. Custom Shaders ile MaterialX

visionOS 2, MaterialX standardını destekliyor. Bu, Pixar gibi stüdyoların kullandığı endüstri standardı shader tanımlama formatı.

MaterialX ile custom shader yazmak, PhysicallyBasedMaterial'in ötesinde tam özelleştirilmiş görsel efektler üretmenizi sağlıyor. Reality Composer Pro'nun ShaderGraph editörü, MaterialX şemalarını görsel olarak oluşturmanıza imkân veriyor.

Production'da en çok kullanılan MaterialX pattern'leri: procedural texture generation (noise, gradient), time-based animation (pulsing, wave), fresnel effect (kenar parlaması), ve subsurface scattering (deri, wax benzeri materyaller).


12. Polygon Budget ve Optimizasyon

visionOS için polygon budget, iOS'tan çok farklı. Her iki göz için ayrı render nedeniyle GPU yükü teorik olarak iki katına çıkıyor. Pratik kural setleri:

Polygon limits (Vision Pro donanımı için):

  • Statik sahne nesneleri: 50K-100K polygon/nesne
  • Dinamik / animasyonlu: 20K-50K polygon/nesne
  • Çok sayıda küçük nesne: 2K-10K/nesne
  • Zemin/çevre: Max 500K toplam scene
  • Particle system: Max 10K particle, shader basit tutun

Texture çözünürlükleri:

  • Yakın nesneler: 2048x2048 (4K bile kabul edilebilir)
  • Orta mesafe: 1024x1024
  • Uzak nesneler: 512x512 veya daha az
  • Normal map: Albedo ile aynı ya da yarısı

Draw call optimizasyonu:

  • Aynı materyal kullanan nesneleri instanced rendering ile birleştirin
  • LOD: 3 seviye (high, medium, low) standart kabul edilebilir
  • Frustum culling RealityKit tarafından otomatik yapılıyor

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

- **Reality Composer Pro:** Asset önizleme ve MaterialX shader geliştirme için zorunlu - **Xcode visionOS Simulator:** Temel UI testi için yeterli ama fizik, hand tracking ve AOD testleri gerçek cihazda yapılmalı - **Metal GPU Frame Capture:** Draw call analizi için Instruments'tan çok daha ayrıntılı bilgi veriyor - **Reality Trace:** RealityKit update loop'unu profillemek için özel instrument, visionOS 2 SDK ile geliyor

Sonuç

visionOS 2, spatial computing geliştirmeyi gerçek anlamda olgunlaştırdı. Environment API ile persistent anchors, 27-noktalı hand tracking ve RealityKit 4'ün ECS mimarisi bir araya gelince gerçekten etkileyici deneyimler oluşturmak mümkün.

En kritik ders: Spatial computing"de kullanıcı deneyimini soyut değil, fiziksel olarak düşünün. Bir butona tıklamak yerine, bir nesneyi tutmak ve çevirmek. Bir liste kaydırmak yerine, uzayda nesneler arasında gezinmek. Bu zihniyet değişimi olmadan, visionOS uygulamaları sadece "3D'de iPad uygulaması" olmaktan öteye geçemiyor.


İlgili Yazılar

Kaynaklar

Etiketler

#visionOS 2#Apple Vision Pro#Spatial Computing#RealityKit#AR#Hand Tracking#2026
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz