Örnek olay incelemesi-farklı performansa sahip cihazlarda Datascape ölçeklendirme

Datascape, Microsoft 'ta, teryağmur verilerinin üzerine hava durumu verilerini görüntülemeye odaklandığımız bir Windows Mixed Reality uygulamasıdır. Uygulama, kullanıcıyı holographic veri görselleştirmesi ile çevreleyerek, verileri karma gerçeklik 'te bulmaktan elde edilecek benzersiz Öngörüler olduğunu araştırır.

Datascape için Microsoft HoloLens farklı donanım özelliklerine sahip çeşitli platformları, Windows Mixed Reality derinlikli kulaklıklar ve düşük güç kullanan bilgisayarlardan yüksek kaliteli GPU ile en son bilgisayarlara hedeflemek istiyorduk. Ana zorluk, büyük bir kare hızına göre çalışırken farklı grafik özelliklerine sahip cihazlara görsel açıdan çarpıcı bir şekilde işlenmekte.

Bu örnek olay incelemesi, karşılaştığı sorunları ve bu sorunların nasıl geldiğini açıklayan, daha fazla GPU yoğun sistemlerimizin bazılarını oluşturmak için kullanılan süreç ve teknikleri adım adım gösterecektir.

Saydamlık ve fazla çizim

Saydamlık, GPU üzerinde pahalı olabildiğinden, ana oluşturma, saydamlıkla dağıtılır.

Derin geometri, derinlik arabelleğine yazılırken ön arkaya oluşturulabilir ve bu pikselin arkasında yer alan gelecekteki piksellerin atılmasına durdurulur. Bu, gizli piksellerin piksel gölgelendirici yürütmesini önler, işlemi önemli ölçüde hızlandırın. Geometri en iyi şekilde sıralanmışsa, ekrandaki her bir piksel yalnızca bir kez çizilir.

Saydam geometrinin, arka arkaya sıralanması gerekir ve bu, ekrandaki geçerli piksele piksel gölgelendiricisinin çıkışını karıştırmasına dayanır. Bu, ekrandaki her bir pikselin kare başına birden çok kez çizilmekte ve fazla çizim olarak adlandırılmakta olabilir.

HoloLens ve temel bilgisayarlarda, ekran yalnızca birkaç kez doldurulabilir ve saydam işleme sorunlu olabilir.

Datascape sahne bileşenlerine giriş

Sahnemiz için üç önemli bileşen vardı; Kullanıcı arabirimi, haritave Hava durumu. Hava durumu etkilerimizin tüm GPU süresini gerektirdiğini sunduk, bu nedenle Kullanıcı arabirimini ve teretlerini, tüm fazla çizimi azaltacak şekilde tasarlıyoruz.

Kullanıcı arabirimini birkaç kez yeniden çalıştık ve bu, üretebilecek fazla çizim miktarını en aza indirir. Işıyan düğmeler ve harita genel bakışları gibi bileşenler için, birbirleriyle ilgili saydam resimleri önemli bir şekilde yerleştirmek yerine, daha karmaşık geometrinin yanında solız.

Eşleme için gölgeler ve karmaşık aydınlatma gibi standart Unity özelliklerini, basit bir tek Sun aydınlatma modeliyle ve özel bir sis hesaplamasına göre değiştiren özel bir gölgelendirici kullandık. Bu, basit bir piksel gölgelendiricisi üretti ve GPU döngülerini boşaltacaktır.

Hem Kullanıcı arabirimini hem de eşlemeyi, donanımınıza bağlı olarak bunlara herhangi bir değişiklik gerekmediği, bütçe olarak işlemeye yönelik olarak yönetiyoruz; Bununla birlikte, belirli bulut işlemede, hava durumu görselleştirmesi, daha fazla zorluk sergilemelidir!

Bulut verilerinde arka plan

Bulut verilerimiz NOAA sunucularından ( https://nomads.ncep.noaa.gov/ ) indirildi ve her biri bulutun üst ve alt yüksekliğine ve her bir kılavuzun her bir hücresi için bulut yoğunluğunu içeren üç farklı 2B katmanda geldi. Veriler, GPU 'daki kolay erişim için, her bileşenin kırmızı, yeşil ve mavi bir bileşende depolandığı bir bulut bilgi dokusundaki işlenir.

Geometri bulutları

Düşük güç tüketen makinelerimizin bulutlarımızı işleyebilmesine olanak sağlamak için, aşırı çizimi en aza indirmek üzere Solid Geometry kullanan bir yaklaşım ile başlatmaya karar verdik.

İlk olarak, şekli oluşturmak için her bir katman için bir köşe başına bulut bilgisi dokusunun yarıçapını kullanarak her bir katman için katı bir heightmap ağı oluşturarak bulutlar oluşturmaya çalıştık. Her ikisinin de en üstte ve en altında bulunan köşeleri oluşturmak için bir geometri gölgelendiricisi kullandık. Daha yoğun bulutlara yönelik daha koyu renklerle bulutu renklendirmek için dokudaki yoğunluk değerini kullandık.

Köşeleri oluşturmak için gölgelendirici:

v2g vert (appdata v)
{
    v2g o;
    o.height = tex2Dlod(_MainTex, float4(v.uv, 0, 0)).x;
    o.vertex = v.vertex;
    return o;
}
 
g2f GetOutput(v2g input, float heightDirection)
{
    g2f ret;
    float4 newBaseVert = input.vertex;
    newBaseVert.y += input.height * heightDirection * _HeigthScale;
    ret.vertex = UnityObjectToClipPos(newBaseVert);
    ret.height = input.height;
    return ret;
}
 
[maxvertexcount(6)]
void geo(triangle v2g p[3], inout TriangleStream<g2f> triStream)
{
    float heightTotal = p[0].height + p[1].height + p[2].height;
    if (heightTotal > 0)
    {
        triStream.Append(GetOutput(p[0], 1));
        triStream.Append(GetOutput(p[1], 1));
        triStream.Append(GetOutput(p[2], 1));
 
        triStream.RestartStrip();
 
        triStream.Append(GetOutput(p[2], -1));
        triStream.Append(GetOutput(p[1], -1));
        triStream.Append(GetOutput(p[0], -1));
    }
}
fixed4 frag (g2f i) : SV_Target
{
    clip(i.height - 0.1f);
 
    float3 finalColor = lerp(_LowColor, _HighColor, i.height);
    return float4(finalColor, 1);
}

Gerçek verilerin üzerine daha fazla ayrıntı almak için küçük bir gürültü deseninin tanıtıldık. Yuvarlak bulut kenarları oluşturmak için, enterpolasyonlu yarıçap değeri, neredeyse sıfır değerleri atmak üzere bir eşiğe ulaşıldığında piksel Gölgelendiricisinde pikselleri kırptık.

Geometri bulutları

Bulutlar Solid Geometry olduğundan, daha fazla kare hızını artırmak için, altındaki pahalı harita piksellerini gizleyebilmek için bu değerler işlenebilir. bu çözüm, en az bir özellik olan tüm grafik kartlarında, katı geometri işleme yaklaşımının yanı sıra, HoloLens de yüksek kaliteli grafik kartlarına kadar iyi çalışır.

Katı parçacık bulutları

Artık bulut verilerimizden oluşan bir gösterimi üreten bir yedekleme çözümüdür, ancak "Wow" faktöründe bir bit Lackluster vardı ve yüksek kaliteli makinelerimiz için istediğimiz Volumetric iyini iletmedik.

Bir sonraki adımımız, daha organik ve Volumetric Look üretmek için bunları yaklaşık 100.000 parçacık ile temsil ederek bulutlar oluşturuyor.

Parçacık düz kalır ve önden arkaya diziyorsanız, daha önce işlenmiş parçacıkların arkasındaki piksellerin derinlik arabelleği yüzey kaldırma özelliğinden faydalanabilir ve bu da fazla beraberlik 'yi azaltır. Ayrıca, parçacık tabanlı bir çözüm sayesinde, farklı donanımları hedeflemek için kullanılan parçacık miktarını da değiştirebilirsiniz. Ancak, tüm piksellerin hala derinlemesine bir test olması gerekir, bu da bazı ek yüke neden olur.

İlk olarak, başlangıçta deneyimin merkez noktası etrafında parçacık konumları oluşturduk. Parçacıkların merkezinde daha seyrek olarak dağıtıldık ve uzaklığın daha az olması. En yakın parçacıkların ilk olarak işlenmesi için, tüm parçacıkların merkezinden geri doğru sıralandık.

Bir işlem gölgelendirici, her parçacığı doğru bir yüksekliğe göre konumlandırmak ve yoğunluğu temel alarak renklendirmek için Cloud Info dokusunu örneklemenizi sağlar.

Parçacık verilerinin her zaman GPU 'da kalmasını sağlamak için, Parçacık başına dörtlü bir parçacık işlemek üzere Drawyordamla kullandık.

Her parçacık hem yükseklik hem de yarıçap içeriyordu. Yükseklik, Cloud Info dokusundaki örneklenmiş bulut verilerine dayalıdır ve yarıçap, en yakın komşuna yatay mesafeyi depolamak için hesaplanacak ilk dağıtımı temel alır. Qutik bu verileri, kullanıcılar yatay olarak görünzaman, yüksekliğinin gösterilmesi ve kullanıcıların bir üst alta baktığı durumlarda, komşuları arasındaki alan kapsanacak şekilde yüksekliğe göre aşağı doğru yönlendirmek için kullanır.

Parçacık şekli

Dağıtımı gösteren gölgelendirici kodu:

ComputeBuffer cloudPointBuffer = new ComputeBuffer(6, quadPointsStride);
cloudPointBuffer.SetData(new[]
{
    new Vector2(-.5f, .5f),
    new Vector2(.5f, .5f),
    new Vector2(.5f, -.5f),
    new Vector2(.5f, -.5f),
    new Vector2(-.5f, -.5f),
    new Vector2(-.5f, .5f)
});
 
StructuredBuffer<float2> quadPoints;
StructuredBuffer<float3> particlePositions;
v2f vert(uint id : SV_VertexID, uint inst : SV_InstanceID)
{
    // Find the center of the quad, from local to world space
    float4 centerPoint = mul(unity_ObjectToWorld, float4(particlePositions[inst], 1));
 
    // Calculate y offset for each quad point
    float3 cameraForward = normalize(centerPoint - _WorldSpaceCameraPos);
    float y = dot(quadPoints[id].xy, cameraForward.xz);
 
    // Read out the particle data
    float radius = ...;
    float height = ...;
 
    // Set the position of the vert
    float4 finalPos = centerPoint + float4(quadPoints[id].x, y * height, quadPoints[id].y, 0) * radius;
    o.pos = mul(UNITY_MATRIX_VP, float4(finalPos.xyz, 1));
    o.uv = quadPoints[id].xy + 0.5;
 
    return o;
}

Parçacıkların arka arkaya sıralanması ve saydam pikselleri kırpmak için yine de düz bir stil gölgelendiricisi kullandığımızdan, bu teknik, yüksek miktarda parçacık uygular ve bu da düşük maliyetli makinelerde bile çok fazla sayıda çizmeden daha fazla yük devri önler.

Saydam parçacık bulutları

Katı parçacık, bulutların şekline uygun bir organizasyon sunuyordu, ancak yine de bulutların akıcı bir şekilde satılmasını sağlamak için gereken bir şey sağladı. Saydamlık sunabileceğiz yüksek kaliteli grafik kartları için özel bir çözüm denemeye karar verdik.

Bunu yapmak için, parçacıkların ilk sıralama sırasını değiştirdik ve gölgelendiriciyi, dokular Alpha kullanacak şekilde değiştirdi.

Floffy bulutları

Her pikselin ekranda yüzlerce kez oluşturulmasına neden olacağından, büyük bir yandan da en zorlu makinelerde çok ağır bir şekilde bakıyordu!

Daha düşük çözünürlüklü ekran üzerinde işleme

Bulutlar tarafından işlenen piksel sayısını azaltmak için, bunları bir çeyrek çözünürlük arabelleğinde işlemeye başladık (ekrana kıyasla) ve tüm parçacık çizildikten sonra son sonucu ekrana geri uzatacağız. Bu, yaklaşık olarak bir 4X hızlı bir şekilde bir, ancak birkaç uyarılarla birlikte geldi.

Ekran oluşturma için kod:

cloudBlendingCommand = new CommandBuffer();
Camera.main.AddCommandBuffer(whenToComposite, cloudBlendingCommand);
 
cloudCamera.CopyFrom(Camera.main);
cloudCamera.rect = new Rect(0, 0, 1, 1);    //Adaptive rendering can set the main camera to a smaller rect
cloudCamera.clearFlags = CameraClearFlags.Color;
cloudCamera.backgroundColor = new Color(0, 0, 0, 1);
 
currentCloudTexture = RenderTexture.GetTemporary(Camera.main.pixelWidth / 2, Camera.main.pixelHeight / 2, 0);
cloudCamera.targetTexture = currentCloudTexture;
 
// Render clouds to the offscreen buffer
cloudCamera.Render();
cloudCamera.targetTexture = null;
 
// Blend low-res clouds to the main target
cloudBlendingCommand.Blit(currentCloudTexture, new RenderTargetIdentifier(BuiltinRenderTextureType.CurrentActive), blitMaterial);

İlk olarak, bir ekran arabelleğinin üzerine işlerken, ana sahemizdeki tüm derinlik bilgilerini kaybettik ve bu, dağ 'ın en üstünde dağılmasına neden olan parçacıkların gerisinde.

İkincisi, arabelleği uzatarak, çözümleme değişikliğinin dikkat çekici olduğu bulutlarımızın kenarlarındaki yapılar da tanıtılmıştır. Sonraki iki bölüm, bu sorunları nasıl çöztiğimiz hakkında konuşur.

Parçacık derinliği arabelleği

Bir Sıradağlar ya da nesnenin arkasındaki parçacıkların bulunduğu dünya geometrisi ile iş parçacıkların birlikte olmasını sağlamak için, ekranın dışı arabelleğini, ana sahnenin geometrisini içeren bir derinlik arabelleği olarak doldurduk. Bu tür bir derinlik arabelleği oluşturmak için ikinci bir kamera oluşturduk ve sahnenin yalnızca katı geometrisini ve derinliğini işledik.

Daha sonra yeni dokuyu, bulutların piksel Gölgelendiricisinde occlude piksel olarak kullandık. Bir bulut pikselin arkasındaki geometriye mesafeyi hesaplamak için aynı dokuyu kullandık. Bu mesafeyi kullanarak ve pikselin Alpha ' ına uygulayarak, artık, parçacıkların kapalı olduğu, parçacık ve terdiler 'in karşılamadığı tüm sabit bir zabotları kaldırarak bulutların soluklaşma etkisini sunuyoruz.

Teryağma karıştırılan bulutlar

Kenarları Keskinleştirme

Uzatılmış bulutlar, parçacıkların merkezinde veya çakışan, ancak bulut kenarlarındaki bazı yapıtlar gösterilen normal boyut bulutlarıyla neredeyse özdeş olarak bakıyordu. Aksi halde, keskin kenarlar bulanık görünebilir ve kamera taşındığında diğer ad etkileri tanıtılmıştır.

Bu, büyük değişiklikler gerçekleştiği yeri (1) belirtmek için, ekran arabelleğinde basit bir gölgelendirici çalıştırarak bunu çöztik. Büyük değişiklikler içeren pikselleri yeni bir kalıp arabelleğine (2) yerleştirtik. Daha sonra, ekran arabelleğini ekrana geri uygularken bu yüksek karşıtlıklı alanların maskesini sağlamak için kalıp arabelleğini kullandık, bu da bulutlar (3) içinde ve etrafında boşluklar oluşmasına neden olur.

Daha sonra tüm parçacıkların tamamını tam ekran modunda yeniden oluşturduk, ancak bu süre, her şeyi maskelemek için şablon arabelleğini kullandı, ancak kenarlarının en az piksel kümesine dokunmasını sağlar (4). Komut arabelleği zaten parçacık için oluşturulduğundan, bunu yeni kameraya bir daha işlemek zorunda kaldık.

Bulut kenarlarını işleme ilerleme durumu

Nihai sonuç, bulutların ucuz merkezi bölümleriyle birlikte keskin kenarlardır.

Bu, tüm parçacıkların tam ekranda işlenmesinden çok daha hızlı olsa da, kalıp arabelleğine göre bir pikselin test edilmesine ilişkin bir maliyet de vardır. bu nedenle, büyük miktarda fazla sayıda fazla çizim çalışmaya devam eder.

Parçacık yüzey kaldırma

Rüzgar efektimiz için, bir işlem gölgelendiricide uzun üçgen şeritler oluşturmuş ve dünyanın dört bir yanındaki rüzgar ISS 'leri oluşturuyor. Skinny şeritleri nedeniyle, rüzgar efekti, doldurdukları şeritler üzerinde ağır olmadığından, köşe gölgelendiricisine yönelik ağır yükün ortaya çıkmasına neden olan yüzlerce yüz binlerce köşe üretti.

Çizilecek rüzgar şeritleri alt kümesini akışa almak için, işlem gölgelendiricisine ekleme arabellekleri tanıtıldık. Bilgi işlem Gölgelendiricisinde bazı basit görünüm Frustum yüzey kaldırma mantığıyla, bir şeridinin kamera görünümü dışında olup olmadığını tespit etmemiz ve gönderim arabelleğine eklenmesini engelleyebilirsiniz. Bu, büyük miktarda, GPU üzerinde bazı gerekli döngüleri boşaltmak kadar azalmıştır.

Ekleme arabelleği gösteren kod:

Bilgi işlem gölgelendiricisi:

AppendStructuredBuffer<int> culledParticleIdx;
 
if (show)
    culledParticleIdx.Append(id.x);

C# kodu:

protected void Awake() 
{
    // Create an append buffer, setting the maximum size and the contents stride length
    culledParticlesIdxBuffer = new ComputeBuffer(ParticleCount, sizeof(int), ComputeBufferType.Append);
 
    // Set up Args Buffer for Draw Procedural Indirect
    argsBuffer = new ComputeBuffer(4, sizeof(int), ComputeBufferType.IndirectArguments);
    argsBuffer.SetData(new int[] { DataVertCount, 0, 0, 0 });
}
 
protected void Update()
{
    // Reset the append buffer, and dispatch the compute shader normally
    culledParticlesIdxBuffer.SetCounterValue(0);
 
    computer.Dispatch(...)
 
    // Copy the append buffer count into the args buffer used by the Draw Procedural Indirect call
    ComputeBuffer.CopyCount(culledParticlesIdxBuffer, argsBuffer, dstOffset: 1);
    ribbonRenderCommand.DrawProceduralIndirect(Matrix4x4.identity, renderMaterial, 0, MeshTopology.Triangles, dataBuffer);
}

Bulut parçacıcisinde aynı tekniği kullanmayı denedik, burada işlem Gölgelendiricisinde onları yeniden seçeceğiz ve yalnızca işlenecek görünen parçacıkların göndeririz. Bu teknik, en büyük darboğazda, köşeleri hesaplama maliyeti değil, ekranda işlenen miktardaki pikseller olduğundan, gerçekte GPU 'ya çok daha fazla tasarruf etmedi.

Bu teknikle ilgili diğer sorun, ekleme arabelleğinin, parçacıkların bilgi işlem sırasında, üretilen parçacıkların sıralanmasına neden olacak şekilde rastgele sırada doldurulduğu ve bu nedenle, bulut parçacıkların görüntülenmesine neden oldu.

Anında iletme arabelleğini sıralamak için teknikler vardır ancak, yüzey kaldırma parçacıkların yetersiz olması büyük olasılıkla ek bir sıralama ile denkleştiriyoruz, bu nedenle bu iyileştirmeyi sağlamadık.

Uyarlamalı işleme

Bulutlu gibi değişen işleme koşullarına sahip bir uygulamada sürekli bir kare hızına sahip olmak için uygulama üzerinde Uyarlamalı işleme tanıtıldık.

Uyarlamalı işlemenin ilk adımı GPU 'YU ölçmektir. Bu, bir işlenen çerçevenin başındaki ve sonundaki, hem sol hem de sağ göz ekran zamanını yakalayan, GPU komut arabelleğine özel kod ekleyerek yaptık.

İşleme harcanan süreyi ölçerek, istenen yenileme oranımız ile kıyaslayarak, kareleri bırakma işleminin nasıl yapıldığını anladık.

Çerçeveleri bırakırken, oluşturma işlemini daha hızlı hale getirmek için uyarlıyoruz. Tek bir basit yol, ekranın görünüm boyutunu değiştirirken, daha az piksel oluşturulmasını gerektirir.

UnityEngine. XR. XRSettings. renderViewportScale kullanarak sistem hedeflenen Görünüm penceresini küçültür ve sonucu ekrana sığacak şekilde otomatik olarak uzatır. Ölçekte küçük bir değişiklik, dünya geometrisi üzerinde belirgin bir şekilde görülür ve 0,7 ölçekli bir faktör, işlenecek piksel miktarının yarısını gerektirir.

%70 ölçek, piksel yarısı

Çerçeveleri bırakmak üzere yaptığımız olduğunu algıladığımızda ölçeği sabit bir sayıya düşürdük ve yeterince hızlı bir şekilde çalıştığında geri arttık.

Başlatma sırasında donanımın grafik özelliklerine göre hangi bulut tekniğinin kullanılmasına karar verdik, ancak sistemin uzun süredir düşük çözünürlükte kalmasını engellemek için GPU ölçümünden verileri temel alabilir, ancak bu, Datascape içinde araştırmaya yönelik bir süredir.

Son düşünce

Çeşitli donanımların hedeflenme zor olur ve bazı planlama gerektirir.

Sorun alanı hakkında bilgi sahibi olmak ve tüm makinelerinizde çalışacak bir yedekleme çözümü geliştirmek için düşük güç gerektiren makineleri hedeflemeyi başlatmanız önerilir. En iyi değerli kaynağınız olacak şekilde, çözümünüzü bir veya daha fazla doldur ile tasarlayın. Saydamlık üzerinde Solid Geometry hedefleyin.

Bir yedekleme çözümüyle, daha sonra, yüksek kaliteli makineler için katmanlama daha karmaşıklıkta başlayabilir veya yalnızca Yedekleme çözümünüzün çözünürlüğünü geliştirebilirsiniz.

En kötü durum senaryolarına yönelik tasarım ve ağır durumlar için uyarlamalı işleme kullanmayı düşünebilirsiniz.

Yazarlar hakkında

Robert Ferrese resmi Robert Ferrese
Yazılım mühendisi @Microsoft
Dan Andersson resmi Dan Andersson
Yazılım mühendisi @Microsoft

Ayrıca bkz.