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ı ARObjectScanningConfigurationkullanı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:

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.Scanningekli 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.csbenzer:

// 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:

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 = &amp;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:

point_cloud

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:

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:

çevresel metin oluşturma tanıtım görüntüsü

Çevre metne dönüştürmeyi kullanmak için:

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:

  1. Dosyadan verileri yükleyin,
  2. Bir nesne olarak arşivleyin ARWorldMap ,
  3. Ö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 ARAnchorbir öğ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, NVector4kurala 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:

satır-ana ve sütun-major karşılaştırması

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.