Örnek olay incelemesi - Farklı özelliklere sahip cihazlar arasında uygulamaları ölçeklendirme
Bu örnek olay incelemesi, bir Windows Mixed Reality uygulamasının farklı donanım özelliklerine sahip çeşitli platformları nasıl hedefleyebileceğini açıklar. Datascape, hava durumu verilerini arazi verilerinin üzerinde görüntüleyen Windows Mixed Reality bir uygulamadır. Uygulama, kullanıcıları holografik veri görselleştirmeleriyle çevreler. Kullanıcılar karma gerçeklikteki verileri keşfederek elde ettikleri benzersiz içgörüleri keşfedebilir.
Datascape uygulaması Microsoft HoloLens, Windows Mixed Reality çevreleyici kulaklıkları, düşük güçlü bilgisayarları ve yüksek performanslı bilgisayarları hedefler. Asıl zorluk, yüksek kare hızında yürütülürken, çok farklı performans özelliklerine sahip cihazlarda görsel açıdan çekici bir sahne oluşturmaktı.
Bu örnek olay incelemesi, özellikle bulutlar gibi hava durumunu işleme gibi performans açısından daha yoğun sistemlerden bazılarını oluşturmak için kullandığımız süreç ve tekniklerde yol gösterir. Karşılaştığımız sorunları ve bunları nasıl aşacağımızı açıklıyoruz.
Karma gerçeklik ve Unity uygulamaları için performansla ilgili dikkat edilmesi gerekenler hakkında daha fazla bilgi için bkz:
Örnek olay incelemesine genel bakış
Datascape uygulaması ve zorlukları hakkında arka plan bilgileri aşağıdadır.
Saydamlık ve fazla çizim
Saydamlık pahalı olabileceğinden ana işleme mücadelelerimiz saydamlık ile ele alınıyor.
Derinlik arabelleğine yazarken düz geometriyi önden arkaya işleyebilirsiniz ve bu da gelecekte bu pikselin arkasında bulunan tüm piksellerin işlenmesini durdurur. Bu işlem gizli piksellerin piksel gölgelendiricisini yürütmesini engeller ve işlemeyi önemli ölçüde hızlandırır. Geometriyi en uygun şekilde sıralarsanız, ekrandaki her piksel yalnızca bir kez çizer.
Saydam geometri öne sıralanmalıdır ve piksel gölgelendiricisinin çıkışını ekrandaki geçerli pikselle karıştırmaya dayanır. Bu işlem, ekrandaki her pikselin kare başına birden çok kez çizilmiş olmasına neden olabilir ve buna overdraw adı verilir.
HoloLens ve temel bilgisayarlar için ekranı yalnızca birkaç kez doldurarak saydam işlemeyi sorunlu hale getirebilirsiniz.
Veri manzarası görünümü bileşenleri
Veri Manzarası sahnesinin üç ana bileşeni vardır: kullanıcı arabirimi, harita ve hava durumu. Hava durumu etkilerinin elde edebilecekleri tüm performansa ihtiyaç duyacağını biliyorduk, bu nedenle kullanıcı arabirimini ve haritayı aşırı dolanı azaltmak için tasarladık.
Fazla çizim miktarını en aza indirmek için kullanıcı arabirimini birkaç kez yeniden kullandık. Parlayan düğmeler ve haritaya genel bakış gibi bileşenler için saydam resmi kaplamak yerine daha karmaşık geometri kullanmayı seçtik.
Harita için gölgeler ve karmaşık aydınlatma gibi standart Unity özelliklerini çıkartan özel bir gölgelendirici kullandık. Özel gölgelendirici, bu özellikleri basit, tek bir güneş aydınlatma modeli ve özel bir sis hesaplamasıyla değiştirdi. Bu basit piksel gölgelendiricisi performansı geliştirdi.
Bütçede işlenmek üzere hem kullanıcı arabirimini hem de haritayı aldık, bu nedenle donanıma bağımlı hiçbir değişikliğe ihtiyaç duymadık. Hava durumu görselleştirmesi, özellikle de bulut işlemesi daha zorluydu.
Bulut verileri
ÜÇ ayrı 2B katmanda NOAA sunucularından indirilen bulut verileri. Her katman, kılavuzun her hücresi için bulutun üst ve alt yüksekliğine ve bulut yoğunluğuna sahipti. Verileri, her bileşeni dokunun kırmızı, yeşil ve mavi bileşeninde depolayan bir bulut bilgi dokusunda işledik.
Geometri bulutları oluşturma
Daha düşük güç kullanan makinelerin bulutları işleyebileceğinden emin olmak için yedekleme yaklaşımımız, aşırı çizimleri en aza indirmek için düz geometri kullandı.
Her katman için düz bir heightmap ağı oluşturarak bulutlar ürettik. Şekli oluşturmak için köşe başına bulut bilgi dokusunun yarıçapını kullandık. Bulutların üst ve alt kısmındaki köşeleri üretmek için geometri gölgelendiricisi kullanarak düz bulut şekilleri oluşturmuş olduk. Daha yoğun bulutlar için bulutu daha koyu renklerle renklendirmek için dokudaki yoğunluk değerini kullandık.
Aşağıdaki gölgelendirici kodu köşeleri oluşturur:
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 üzerinde daha fazla ayrıntı elde etmek için küçük bir gürültü deseni kullanıma sunulmuştur. Yuvarlak bulut kenarları oluşturmak için, ilişkilendirilmiş yarıçap değeri eşiğe geldiğinde piksel gölgelendiricisindeki pikselleri kırparak sıfıra yakın değerleri atmış olduk.
Bulutlar düz geometri olduğundan, arazi işlenmeden önce işlenebilir. Bulutların altındaki pahalı harita piksellerini gizlemek kare hızını daha da artırır. Düz geometri işleme yaklaşımı nedeniyle, bu çözüm minimum belirtimlerden üst düzey grafik kartlarına ve HoloLens'te tüm grafik kartlarında iyi çalıştı.
Katı parçacık bulutları kullanma
Çözümümüz, bulut verilerinin düzgün bir gösterimini üretti, ancak biraz yetersizdi. Bulut işleme, üst düzey makinelerimiz için istediğimiz hacimli hissi iletmedi. Sonraki adımımız, bulutları yaklaşık 100.000 parçacıkla temsil ederek daha organik ve hacimsel bir görünüm oluşturmaktı.
Parçacıklar sağlam kalır ve önden arkaya sıralanırsa, daha önce işlenmiş parçacıkların arkasında derinlik tamponu itlaf ederek aşırı çizmeyi azaltırsınız. Ayrıca, parçacık tabanlı bir çözüm farklı donanımları hedeflemek için parçacıkların sayısını değiştirebilir. Ancak, tüm piksellerin hala daha fazla ek yüke neden olacak şekilde derinlemesine test edilmesi gerekir.
İlk olarak, başlangıçta deneyimin merkez noktası etrafında parçacık konumları oluşturduk. Parçacıkları merkeze daha yoğun ve mesafeye daha az dağıttık. Tüm parçacıkları merkezden arkaya önceden sıraladık, böylece en yakın parçacıklar önce işledi.
İşlem gölgelendiricisi bulut bilgi dokusunu örnekleyip her parçacığı doğru yüksekliğe konumlandırdı ve yoğunluğu temel alarak renklendirdi. Her parçacık hem yükseklik hem de yarıçap içeriyordu. Yükseklik, bulut bilgi dokusundan örneklenen bulut verilerini temel alır. Yarıçap, en yakın komşusunun yatay uzaklığını hesaplayan ve depolayan ilk dağılımı temel alır.
Parçacık başına dörtlü işlemek için DrawProcedural kullandık. Dörtlüler, yükseklik açısından kendilerini yönlendirmek için bu verileri kullandı. Kullanıcılar bir parçacıka yatay olarak baktığında yüksekliği gösterir. Kullanıcılar parçacığı yukarıdan aşağıya baktığında, parçacıkla komşuları arasındaki alan kaplanır.
Aşağıdaki gölgelendirici kodu dağılımı gösterir:
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ı önden arkaya sıraladık ve yine de saydam pikselleri kırpmak için düz stil gölgelendirici kullandık, karıştırmadık. Bu teknik, düşük güçlendirilmiş makinelerde bile çok sayıda parçacığı işleyerek yüksek maliyetli aşırı çizimlerden kaçınıyor.
Saydam parçacık bulutlarını deneyin
Katı parçacıklar bulut şekillerine organik bir his sağlasa da yine de bulutların akıcılığını yakalamak için bir şeye ihtiyaç duyuyor. Saydamlık sağlayan üst düzey grafik kartları için özel bir çözüm denemeye karar verdik. Parçacıkların ilk sıralama düzenini değiştirdik ve gölgelendiriciyi dokuları alfa kullanacak şekilde değiştirdik.
Bu çözüm harika görünüyordu, ancak en zorlu makineler için bile çok ağır olduğu kanıtlandı. Her pikselin ekranda yüzlerce kez işlenmesi gerekiyordu.
Daha düşük çözünürlükle ekran dışında işleme
Bulutları işlemeye yönelik piksel sayısını azaltmak için bunları ekran çözünürlüğünün dörtte biri olan bir arabellekte işledik. Tüm parçacıkları çizdikten sonra sondaki sonucu tekrar ekrana gerdik.
Aşağıdaki kod, ekran dışı işlemeyi gösterir:
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);
Bu çözüm dört kat işlemeyi hızlandırdı, ancak birkaç uyarı vardı. İlk olarak, ekran dışı arabelleğe işlenirken ana sahnemizdeki tüm derinlik bilgilerini kaybettik. Dağların arkasındaki parçacıklar dağın üstünde işlendi.
İkincisi, arabelleğin genişletilmesi, çözüm değişikliğinin fark edilebilir olduğu bulutların uçlarında yapıtları kullanıma sunar. Sonraki iki bölümde bu sorunların nasıl çözüldüğü açıklanır.
Parçacık derinliği arabelleği kullanma
Parçacıkların, bir dağın veya nesnenin arkasındaki parçacıkları kapladığı dünya geometrisiyle birlikte var olmasını sağlamamız gerekiyordu. Bu nedenle, ekran dışı arabelleği ana sahne geometrisini içeren bir derinlik arabelleğiyle doldurduk. Derinlik arabelleği oluşturmak için sahnenin yalnızca katı geometrisini ve derinliğini işleyen ikinci bir kamera oluşturduk.
Pikselleri tıkamak için bulut piksel gölgelendiricisindeki yeni dokuyu kullandık. Bulut pikselinin arkasındaki geometriye olan uzaklığı hesaplamak için aynı dokuyu kullandık. Bu uzaklığı kullanarak ve pikselin alfasına uygulayarak, bulutların araziye yaklaştıkça sönen etkisini elde ettik. Bu etki, parçacıkların ve arazinin bir araya geldiği sert kesimleri kaldırır.
Kenarları keskinleştirme
Esnetilmiş bulutlar, parçacıkların merkezlerindeki veya çakışan normal boyutlu bulutlarla neredeyse aynı görünüyordu, ancak bulut kenarlarında bazı yapıtları gösterdi. Keskin kenarlar bulanık görünüyordu ve kamera hareketi diğer ad efektlerini ortaya verdi.
Bu sorunu çözmek için:
- Karşıtlıkta büyük değişikliklerin nerede oluştuğunu belirlemek için ekran dışı arabellekte basit bir gölgelendirici çalıştırdı.
- Büyük değişiklikler içeren pikselleri yeni bir kalıp arabelleğine yerleştirin.
- Ekran dışı arabelleği ekrana geri uygularken bu yüksek karşıtlık alanlarını maskeleme amacıyla kalıp arabelleği kullanılarak bulutlarda ve çevresinde delikler açılır.
- Kenarlar dışında her şeyi maskeleme amacıyla kalıp arabelleği kullanılarak tam ekran modunda tüm parçacıklar yeniden işlendi ve bu da çok az pikselin dokunmasına neden oldu. Parçacıkları işlemek için komut arabelleği oluşturduğumuz için yeni kameraya yeniden işledik.
Sonuç olarak bulutların ucuz merkez bölümlerine sahip keskin kenarlar elde edildi. Bu çözüm tüm parçacıkları tam ekranda işlemekten çok daha hızlı olsa da, yine de kalıp arabelleğine karşı pikselleri test etme maliyeti vardır. Çok büyük miktarda fazla çizim hala pahalıdır.
Cull parçacıkları
Rüzgar etkisi için, bir işlem gölgelendiricisinde uzun üçgen şeritler oluşturarak dünyada birçok rüzgar hızı oluşturduk. Rüzgar, dar şeritler nedeniyle dolgu hızında ağır değildi. Ancak yüz binlerce köşe, köşe gölgelendiricisi için ağır bir yüke neden oldu.
Yükü azaltmak için, çekilecek rüzgar şeritlerinin bir alt kümesini beslemek için işlem gölgelendiricisine ekleme arabellekleri ekledik. Bir şeridin kamera görünümünün dışında olup olmadığını belirlemek için işlem gölgelendiricisinde basit görünüm frustum toplama mantığını kullandık ve bu şeritlerin gönderme arabelleğine eklenmesini engelledik. Bu işlem şerit sayısını önemli ölçüde azaltarak performansı artırdı.
Aşağıdaki kod bir ekleme arabelleği gösterir.
İş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);
}
Bu tekniği bulut parçacıkları üzerinde denedik, işlem gölgelendiricisinde birleştirdik ve yalnızca işlenecek görünür parçacıkları ittirdik. Ancak çok fazla işlemden tasarruf etmedik, çünkü en büyük performans sorunu köşeleri hesaplama maliyeti değil ekranda işlenmek üzere bulut piksellerinin sayısıydı.
Bir diğer sorun, parçacıkların paralel olarak hesaplanması nedeniyle ekleme arabelleğinin rastgele sırada doldurulmasıydı. Sıralanmış parçacıklar sıralanmamış hale geldi ve bu da titreyen bulut parçacıklarına neden oldu. Gönderme arabelleği sıralamak için teknikler vardır, ancak kümeleme parçacıklarından elde edilen sınırlı performans kazancı büyük olasılıkla başka bir sıralamaya göre dengelenebilir. Bulut parçacıkları için bu iyileştirmeyi takip etmemeye karar verdik.
Uyarlamalı işleme kullanma
Buluttan net görünüme kadar değişen işleme koşullarıyla uygulamada sabit bir kare hızı sağlamak için uyarlamalı işleme özelliği kullanıma sunulmuştur.
Uyarlamalı işlemenin ilk adımı performansı ölçmektir. Hem sol hem de sağ göz ekranı süresini yakalamak için işlenen çerçevenin başındaki ve sonundaki komut arabelleğine özel kod ekledik.
Çerçeveleri bırakmaya ne kadar yaklaştığınızı göstermek için işleme süresini istenen yenileme hızıyla karşılaştırın. Çerçeveleri bırakmaya yaklaştığınızda işlemeyi daha hızlı olacak şekilde uyarlayabilirsiniz.
İşlemeyi uyarlamanın basit yollarından biri, işlenmesi daha az piksel gerektirecek şekilde ekran görünüm penceresi boyutunu değiştirmektir. Sistem, hedeflenen görünüm penceresi küçültmek için UnityEngine.XR.XRSettings.renderViewportScale kullanır ve sonucu ekrana sığacak şekilde otomatik olarak yukarı doğru uzatır. Ölçekteki küçük bir değişiklik dünya geometrisinde zar zor fark edilir ve 0,7 ölçek faktörü, işlenecek piksel sayısının yarısını gerektirir.
Çerçeveleri bırakmak üzere olduğumuzu algıladığımızda, ölçeği sabit bir oran ile düşürür ve yeterince hızlı çalışırken yeniden geri yükleriz.
Bu örnek olay incelemesinde başlangıçtaki donanımın grafik özelliklerine göre hangi bulut tekniğinin kullanılacağına karar verdik. Sistemin uzun süre düşük çözünürlükte kalmasını önlemeye yardımcı olmak için bu kararı performans ölçümlerinden alınan verilere de dayandırabilirsiniz.
Öneriler
Farklı donanım özelliklerini hedeflemek zordur ve planlama gerektirir. İşte birkaç öneri:
- Sorun alanı hakkında bilgi edinmek için daha düşük güçle çalışan makineleri hedeflemeye başlayın.
- Tüm makinelerinizde çalışan bir yedekleme çözümü geliştirin. Daha sonra yüksek uç makineler için daha karmaşık bir katman oluşturabilir veya yedekleme çözümünün çözünürlüğünü geliştirebilirsiniz.
- Pikseller en değerli kaynağınız olduğundan, çözümünüzü doldurma oranını göz önünde bulundurarak tasarlarsınız.
- Saydamlık üzerinde düz geometriyi hedefle.
- En kötü senaryolar için tasarım ve ağır durumlar için uyarlamalı işleme kullanmayı göz önünde bulundurun.
Yazarlar hakkında
![]() |
Robert Ferrese Yazılım mühendisi @Microsoft |
![]() |
Dan Andersson Yazılım mühendisi @Microsoft |