Xamarin.iOS'ta ARKit 2
ARKit, iOS 11'de geçen yıl tanıtılmasından bu yana önemli ölçüde olgunlaştı. Her şeyden önce, artık dikey ve yatay düzlemleri algılayabilirsiniz, bu da iç mekan artırılmış gerçeklik deneyimlerinin pratikliğini büyük ölçüde artırır. Buna ek olarak, yeni özellikler vardır:
- Referans görüntüleri ve nesneleri gerçek dünya ile dijital görüntüler arasındaki birleşim olarak tanıma
- Gerçek dünya aydınlatma simülasyonu sağlayan yeni bir aydınlatma modu
- AR ortamlarını paylaşma ve kalıcı hale getirmek
- AR içeriğini depolamak için tercih edilen yeni dosya biçimi
Başvuru nesnelerini tanıma
ARKit 2'deki bir vitrin özelliği, başvuru görüntülerini ve nesnelerini tanıyabilme özelliğidir. Başvuru görüntüleri normal görüntü dosyalarından yüklenebilir (daha sonra ele alınmıştır), ancak başvuru nesnelerinin geliştirici odaklı ARObjectScanningConfiguration
kullanılarak taranması gerekir.
Örnek uygulama: 3B nesneleri tarama ve algılama
Örnek, aşağıdakileri gösteren bir Apple projesinin bağlantı noktasıdır:
- Nesneleri kullanarak
NSNotification
uygulama durumu yönetimi - Özel görselleştirme
- Karmaşık hareketler
- Nesne tarama
- Depolama
ARReferenceObject
Bir başvuru nesnesini taramak pil ve işlemci yoğunlukludur ve eski cihazlar genellikle kararlı izleme elde etme konusunda sorun yaşacaktır.
NSNotification nesnelerini kullanarak durum yönetimi
Bu uygulama, aşağıdaki durumlar arasında geçiş yapılan bir durum makinesi kullanır:
AppState.StartARSession
AppState.NotReady
AppState.Scanning
AppState.Testing
Ayrıca içinde olduğunda AppState.Scanning
ekli bir durum ve geçiş kümesi kullanır:
Scan.ScanState.Ready
Scan.ScanState.DefineBoundingBox
Scan.ScanState.Scanning
Scan.ScanState.AdjustingOrigin
Uygulama, durum geçişi bildirimlerini gönderip bu bildirimlere NSNotificationCenter
abone olan reaktif bir mimari kullanır. Kurulum şu kod parçacığına ViewController.cs
benzer:
// Configure notifications for application state changes
var notificationCenter = NSNotificationCenter.DefaultCenter;
notificationCenter.AddObserver(Scan.ScanningStateChangedNotificationName, State.ScanningStateChanged);
notificationCenter.AddObserver(ScannedObject.GhostBoundingBoxCreatedNotificationName, State.GhostBoundingBoxWasCreated);
notificationCenter.AddObserver(ScannedObject.GhostBoundingBoxRemovedNotificationName, State.GhostBoundingBoxWasRemoved);
notificationCenter.AddObserver(ScannedObject.BoundingBoxCreatedNotificationName, State.BoundingBoxWasCreated);
notificationCenter.AddObserver(BoundingBox.ScanPercentageChangedNotificationName, ScanPercentageChanged);
notificationCenter.AddObserver(BoundingBox.ExtentChangedNotificationName, BoundingBoxExtentChanged);
notificationCenter.AddObserver(BoundingBox.PositionChangedNotificationName, BoundingBoxPositionChanged);
notificationCenter.AddObserver(ObjectOrigin.PositionChangedNotificationName, ObjectOriginPositionChanged);
notificationCenter.AddObserver(NSProcessInfo.PowerStateDidChangeNotification, DisplayWarningIfInLowPowerMode);
Tipik bir bildirim işleyicisi kullanıcı arabirimini güncelleştirir ve büyük olasılıkla nesne tarandıkça güncelleştirilen bu işleyici gibi uygulama durumunu değiştirir:
private void ScanPercentageChanged(NSNotification notification)
{
var pctNum = TryGet<NSNumber>(notification.UserInfo, BoundingBox.ScanPercentageUserKey);
if (pctNum == null)
{
return;
}
double percentage = pctNum.DoubleValue;
// Switch to the next state if scan is complete
if (percentage >= 100.0)
{
State.SwitchToNextState();
}
else
{
DispatchQueue.MainQueue.DispatchAsync(() => navigationBarController.SetNavigationBarTitle($"Scan ({percentage})"));
}
}
Son olarak, Enter{State}
yöntemler modeli ve UX'i yeni duruma uygun şekilde değiştirir:
internal void EnterStateTesting()
{
navigationBarController.SetNavigationBarTitle("Testing");
navigationBarController.ShowBackButton(false);
loadModelButton.Hidden = true;
flashlightButton.Hidden = false;
nextButton.Enabled = true;
nextButton.SetTitle("Share", UIControlState.Normal);
testRun = new TestRun(sessionInfo, sceneView);
TestObjectDetection();
CancelMaxScanTimeTimer();
}
Özel görselleştirme
Uygulama, algılanan yatay düzleme yansıtılan sınırlayıcı kutunun içinde bulunan nesnenin alt düzey "nokta bulutunu" gösterir.
Bu nokta bulutu özelliğindeki ARFrame.RawFeaturePoints
geliştiriciler tarafından kullanılabilir. Nokta bulutunu verimli bir şekilde görselleştirmek karmaşık bir sorun olabilir. Noktaları yinelemek, ardından her nokta için yeni bir SceneKit düğümü oluşturmak ve yerleştirmek kare hızını sonlandırır. Alternatif olarak, zaman uyumsuz olarak yapılırsa bir gecikme olur. Örnek, üç bölümden oluşan bir stratejiyle performansı korur:
- Verileri yerinde sabitlemek ve verileri baytların ham arabelleği olarak yorumlamak için güvenli olmayan kod kullanma.
- Bu ham arabelleeğe dönüştürme
SCNGeometrySource
ve "şablon"SCNGeometryElement
nesnesi oluşturma. - Ham verileri ve şablonu kullanarak hızla "birleştirme"
SCNGeometry.Create(SCNGeometrySource[], SCNGeometryElement[])
internal static SCNGeometry CreateVisualization(NVector3[] points, UIColor color, float size)
{
if (points.Length == 0)
{
return null;
}
unsafe
{
var stride = sizeof(float) * 3;
// Pin the data down so that it doesn't move
fixed (NVector3* pPoints = &points[0])
{
// Important: Don't unpin until after `SCNGeometry.Create`, because geometry creation is lazy
// Grab a pointer to the data and treat it as a byte buffer of the appropriate length
var intPtr = new IntPtr(pPoints);
var pointData = NSData.FromBytes(intPtr, (System.nuint) (stride * points.Length));
// Create a geometry source (factory) configured properly for the data (3 vertices)
var source = SCNGeometrySource.FromData(
pointData,
SCNGeometrySourceSemantics.Vertex,
points.Length,
true,
3,
sizeof(float),
0,
stride
);
// Create geometry element
// The null and bytesPerElement = 0 look odd, but this is just a template object
var template = SCNGeometryElement.FromData(null, SCNGeometryPrimitiveType.Point, points.Length, 0);
template.PointSize = 0.001F;
template.MinimumPointScreenSpaceRadius = size;
template.MaximumPointScreenSpaceRadius = size;
// Stitch the data (source) together with the template to create the new object
var pointsGeometry = SCNGeometry.Create(new[] { source }, new[] { template });
pointsGeometry.Materials = new[] { Utilities.Material(color) };
return pointsGeometry;
}
}
}
Sonuç şuna benzer:
Karmaşık hareketler
Kullanıcı hedef nesneyi çevreleyen sınırlayıcı kutuyu ölçeklendirebilir, döndürebilir ve sürükleyebilirsiniz. İlişkili hareket tanıyıcılarında iki ilginç şey vardır.
İlk olarak, tüm hareket tanıyıcıları yalnızca bir eşik geçirildikten sonra etkinleştirilir; örneğin, bir parmak çok fazla piksel sürükledi veya döndürme bir açıyı aşıyor. Bu yöntem, eşik aşılana kadar taşımayı biriktirip artımlı olarak uygulamaktır:
// A custom rotation gesture recognizer that fires only when a threshold is passed
internal partial class ThresholdRotationGestureRecognizer : UIRotationGestureRecognizer
{
// The threshold after which this gesture is detected.
const double threshold = Math.PI / 15; // (12°)
// Indicates whether the currently active gesture has exceeded the threshold
private bool thresholdExceeded = false;
private double previousRotation = 0;
internal double RotationDelta { get; private set; }
internal ThresholdRotationGestureRecognizer(IntPtr handle) : base(handle)
{
}
// Observe when the gesture's state changes to reset the threshold
public override UIGestureRecognizerState State
{
get => base.State;
set
{
base.State = value;
switch(value)
{
case UIGestureRecognizerState.Began :
case UIGestureRecognizerState.Changed :
break;
default :
// Reset threshold check
thresholdExceeded = false;
previousRotation = 0;
RotationDelta = 0;
break;
}
}
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
if (thresholdExceeded)
{
RotationDelta = Rotation - previousRotation;
previousRotation = Rotation;
}
if (! thresholdExceeded && Math.Abs(Rotation) > threshold)
{
thresholdExceeded = true;
previousRotation = Rotation;
}
}
}
Hareketlerle ilgili olarak yapılan ikinci ilginç şey, sınırlayıcı kutunun algılanan gerçek dünya uçaklarına göre taşınma şeklidir. Bu özellik bu Xamarin blog gönderisinde ele alınmıştı.
ARKit 2'deki diğer yeni özellikler
Daha fazla izleme yapılandırması
Şimdi, karma gerçeklik deneyiminin temeli olarak aşağıdakilerden herhangi birini kullanabilirsiniz:
- Yalnızca cihaz ivmeölçer (
AROrientationTrackingConfiguration
, iOS 11) - Yüzler (
ARFaceTrackingConfiguration
, iOS 11) - Başvuru Görüntüleri (
ARImageTrackingConfiguration
, iOS 12) - 3B nesneleri tarama (
ARObjectScanningConfiguration
, iOS 12) - Görsel inertial odometri (
ARWorldTrackingConfiguration
iOS 12'de geliştirilmiş)
AROrientationTrackingConfiguration
, bu blog gönderisinde ve F# örneğinde tartışılır, en sınırlı olandır ve cihazı ve ekranı gerçek dünyaya bağlamaya çalışmadan yalnızca cihazın hareketine göre dijital nesneler yerleştirdiğinden kötü bir karma gerçeklik deneyimi sağlar.
, ARImageTrackingConfiguration
gerçek dünya 2B görüntülerini (resimler, logolar vb.) tanımanıza ve bunları kullanarak dijital görüntüleri tutturmanıza olanak tanır:
var imagesAndWidths = new[] {
("cover1.jpg", 0.185F),
("cover2.jpg", 0.185F),
//...etc...
("cover100.jpg", 0.185F),
};
var referenceImages = new NSSet<ARReferenceImage>(
imagesAndWidths.Select( imageAndWidth =>
{
// Tuples cannot be destructured in lambda arguments
var (image, width) = imageAndWidth;
// Read the image
var img = UIImage.FromFile(image).CGImage;
return new ARReferenceImage(img, ImageIO.CGImagePropertyOrientation.Up, width);
}).ToArray());
configuration.TrackingImages = referenceImages;
Bu yapılandırmanın iki ilginç yönü vardır:
- Verimlidir ve potansiyel olarak çok sayıda başvuru görüntüsüyle kullanılabilir
- Dijital görüntü, gerçek dünyada hareket etse bile görüntüye sabitlenir (örneğin, bir kitabın kapağı tanındıysa, raftan çekilirken, serilirken vb.) kitabı izler.
ARObjectScanningConfiguration
daha önce ele alınmıştı ve 3B nesneleri taramak için geliştirici merkezli bir yapılandırmadır. Yüksek oranda işlemci ve pil yoğunlukludur ve son kullanıcı uygulamalarında kullanılmamalıdır.
Son izleme yapılandırması olan ARWorldTrackingConfiguration
, çoğu karma gerçeklik deneyiminin iş atıdır. Bu yapılandırma, gerçek dünyadaki "özellik noktalarını" dijital görüntülerle ilişkilendirmek için "görsel atal odometri" kullanır. Dijital geometri veya spritler, gerçek dünya yatay ve dikey düzlemlerine göre ya da algılanan ARReferenceObject
örneklere göre sabitlenir. Bu yapılandırmada, dünya kaynağı kameranın uzaydaki orijinal konumudur ve Z ekseni yerçekimine hizalanmış ve dijital nesneler gerçek dünyadaki nesnelere göre "yerinde kalır".
Çevre metne dönüştürme
ARKit 2, aydınlatmayı tahmin etmek için yakalanan görüntüleri kullanan ve hatta parlak nesnelere belirtik vurgular uygulayan "ortam dokulama"yı destekler. Çevre küp haritası dinamik olarak oluşturulur ve kamera her yöne baktıktan sonra etkileyici bir gerçekçi deneyim üretebilir:
Çevre metne dönüştürmeyi kullanmak için:
- Nesnelerinizin
SCNMaterial
ve içinMetalness.Contents
0 ile 1 arasında bir değer kullanmasıSCNLightingModel.PhysicallyBased
veRoughness.Contents
ataması gerekir - İzleme yapılandırmanız şunları ayarlamalıdır
EnvironmentTexturing
=AREnvironmentTexturing.Automatic
:
var sphere = SCNSphere.Create(0.33F);
sphere.FirstMaterial.LightingModelName = SCNLightingModel.PhysicallyBased;
// Shiny metallic sphere
sphere.FirstMaterial.Metalness.Contents = new NSNumber(1.0F);
sphere.FirstMaterial.Roughness.Contents = new NSNumber(0.0F);
// Session configuration:
var configuration = new ARWorldTrackingConfiguration
{
PlaneDetection = ARPlaneDetection.Horizontal | ARPlaneDetection.Vertical,
LightEstimationEnabled = true,
EnvironmentTexturing = AREnvironmentTexturing.Automatic
};
Yukarıdaki kod parçacığında gösterilen mükemmel yansıtıcı doku bir örnekte eğlenceli olsa da, çevre dokulama büyük olasılıkla kısıtlama ile birlikte daha iyi kullanılır, ancak "tekinsiz vadi" yanıtını tetikler (doku yalnızca kameranın kaydettiğine göre bir tahmindir).
Paylaşılan ve kalıcı AR deneyimleri
ARKit 2'ye bir diğer önemli ekleme de dünya izleme verilerini paylaşmanıza veya depolamanıza olanak tanıyan sınıfıdır ARWorldMap
. Veya GetCurrentWorldMap(Action<ARWorldMap,NSError>)
ile ARSession.GetCurrentWorldMapAsync
geçerli dünya haritasını alırsınız:
// Local storage
var PersistentWorldPath => Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "/arworldmap";
// Later, after scanning the environment thoroughly...
var worldMap = await Session.GetCurrentWorldMapAsync();
if (worldMap != null)
{
var data = NSKeyedArchiver.ArchivedDataWithRootObject(worldMap, true, out var err);
if (err != null)
{
Console.WriteLine(err);
}
File.WriteAllBytes(PersistentWorldPath, data.ToArray());
}
Dünya haritasını paylaşmak veya geri yüklemek için:
- Dosyadan verileri yükleyin,
- Bir nesne olarak arşivleyin
ARWorldMap
, - Özelliğinin değeri
ARWorldTrackingConfiguration.InitialWorldMap
olarak bunu kullanın:
var data = NSData.FromArray(File.ReadAllBytes(PersistentWorldController.PersistenWorldPath));
var worldMap = (ARWorldMap)NSKeyedUnarchiver.GetUnarchivedObject(typeof(ARWorldMap), data, out var err);
var configuration = new ARWorldTrackingConfiguration
{
PlaneDetection = ARPlaneDetection.Horizontal | ARPlaneDetection.Vertical,
LightEstimationEnabled = true,
EnvironmentTexturing = AREnvironmentTexturing.Automatic,
InitialWorldMap = worldMap
};
Yalnızca ARWorldMap
görünür olmayan dünya izleme verilerini ve ARAnchor
nesneleri içerir, dijital varlıkları içermez. Geometriyi veya görüntüleri paylaşmak için, kullanım örneğinize uygun kendi stratejinizi geliştirmeniz gerekir (belki de geometrinin yalnızca konumunu ve yönlendirmesini depolayarak/ileterek ve statik SCNGeometry
nesnelere uygulayarak veya belki de serileştirilmiş nesneleri depolayarak/ileterek). 'nin ARWorldMap
avantajı, paylaşılan ARAnchor
bir öğesine göre yerleştirildikten sonra varlıkların cihazlar veya oturumlar arasında tutarlı bir şekilde görünmesidir.
Evrensel Sahne Açıklaması dosya biçimi
ARKit 2'nin son başlık özelliği Apple'ın Pixar'ın Evrensel Sahne Açıklaması dosya biçimini benimsemesi. Bu biçim, ARKit varlıklarını paylaşmak ve depolamak için tercih edilen biçim olarak Collada'nın DAE biçiminin yerini alır. Varlıkları görselleştirme desteği iOS 12 ve Mojave'de yerleşiktir. USDZ dosya uzantısı, USD dosyalarını içeren sıkıştırılmamış ve şifrelenmemiş bir zip arşividir. Pixar , USD dosyalarıyla çalışmak için araçlar sağlar ancak henüz çok fazla üçüncü taraf desteği yoktur.
ARKit programlama ipuçları
El ile kaynak yönetimi
ARKit'te kaynakları el ile yönetmek çok önemlidir. Bu, yüksek kare hızlarına izin vermekle kalmamakla birlikte, kafa karıştırıcı bir "ekran donması" önlemek için de gereklidir . ARKit çerçevesi yeni bir kamera çerçevesi (ARSession.CurrentFrame
. Akım ARFrame
onu çağırana Dispose()
kadar ARKit yeni bir çerçeve sağlamaz! Bu, uygulamanın geri kalanı yanıt vermesine rağmen videonun "donmasına" neden olur. Çözüm, her zaman bir blokla erişmek ARSession.CurrentFrame
veya el ile çağırmaktırDispose()
.using
öğesinden NSObject
türetilen tüm nesneler Dispose IDisposable
desenini uygular ve NSObject
bu nedenle türetilmiş bir sınıfa uygulamak Dispose
için genellikle bu deseni izlemeniz gerekir.
Dönüşüm matrislerini düzenleme
Herhangi bir 3B uygulamada, bir nesneyi 3B alanda taşımayı, döndürmeyi ve kesmeyi kısa bir şekilde açıklayan 4x4 dönüştürme matrisleriyle uğraşacaksınız. SceneKit'te bunlar nesnelerdir SCNMatrix4
.
özelliği, satır-ana simdfloat4x4
türü tarafından yedeklendiği şekilde için SCNNode
dönüştürme matrisini döndürürSCNMatrix4
.SCNNode.Transform
Bu nedenle, örneğin:
var node = new SCNNode { Position = new SCNVector3(2, 3, 4) };
var xform = node.Transform;
Console.WriteLine(xform);
// Output is: "(1, 0, 0, 0)\n(0, 1, 0, 0)\n(0, 0, 1, 0)\n(2, 3, 4, 1)"
Gördüğünüz gibi konum, alt satırın ilk üç öğesinde kodlanmıştır.
Xamarin'de, dönüştürme matrislerini işlemeye yönelik yaygın tür, NVector4
kurala göre sütun ana şeklinde yorumlanan türüdür. Yani, çeviri/konum bileşeni M41, M42, M43'te değil M14, M24, M34'te beklenir:
Matris yorumlama seçimiyle tutarlı olmak, uygun davranış için çok önemlidir. 3B dönüşüm matrisleri 4x4 olduğundan, tutarlılık hataları herhangi bir derleme zamanı veya hatta çalışma zamanı özel durumu oluşturmaz; yalnızca işlemler beklenmedik şekilde hareket eder. SceneKit / ARKit nesneleriniz takılmış, uçup uçmuş veya titreşimli görünüyorsa, yanlış dönüştürme matrisi iyi bir olasılıktır. Çözüm basittir: NMatrix4.Transpose
öğelerin yerinde bir transpozisyonunu gerçekleştirir.