Birisi colum vs row major çarpma / birleştirme üzerindeki etkilerini (nedenlerini) açıklayabilir mi?


11

Görünüm ve projeksiyon matrislerinin nasıl oluşturulacağını öğrenmeye çalışıyorum ve matrisler için iki standart hakkındaki karışıklığım sayesinde uygulamamda zorluklara ulaşmaya devam ediyorum. Bir matrisin nasıl çarpılacağını
biliyorum ve çarpmadan önce aktarmanın sonucu tamamen değiştireceğini, dolayısıyla farklı bir sırayla çarpma ihtiyacını görebiliyorum.

Anlamadığım şey sadece 'notasyonel konvansiyon' ile kastedilmektedir - buradaki ve buradaki makalelerden yazarlar, matrisin nasıl saklandığını veya GPU'ya aktarıldığını fark etmediğini iddia ediyorlar, ancak ikincisinde matrisin satır-binbaşı için bellekte nasıl düzenleneceğine açıkça eşdeğer olmadığı sayfa ; ve programımdaki kalabalık bir matrise baktığımda 4., 8. ve 12. elemanları işgal eden çeviri bileşenlerini görüyorum.

Verilen:

"sütun-büyük matrislerle çarpma sonrası satır-büyük matrislerle ön çarpma ile aynı sonucu verir."

Neden aşağıdaki kod snippet'inde:

        Matrix4 r = t3 * t2 * t1;
        Matrix4 r2 = t1.Transpose() * t2.Transpose() * t3.Transpose();

R! = R2 ve neden pos3! = Pos :

        Vector4 pos = wvpM * new Vector4(0f, 15f, 15f, 1);
        Vector4 pos3 = wvpM.Transpose() * new Vector4(0f, 15f, 15f, 1);

Çarpma işlemi, matrislerin satır veya sütun ana olmasına bağlı olarak mı değişiyor yoksa sadece sıra mı (eşdeğer bir etki için)?

Bunun daha net hale gelmesine yardımcı olmayan bir şey, DirectX'e sağlandığında, sütun ana WVP matrisimin, köşeleri HLSL çağrısı ile dönüştürmek için başarıyla kullanılması: vul (vektör, matris) . row-major , matematik kütüphanem tarafından sağlanan sütun major matrisi nasıl çalışır?



Yanıtlar:


11

programımdaki kalabalık bir matrise bakarsam 4., 8. ve 12. elemanları işgal eden çeviri bileşenlerini görüyorum.

Ben başlamadan önce, anlamak önemlidir: bu matrisler demektir majör satır . Bu nedenle, bu soruya cevap veriyorsunuz:

benim sütun majör WVP matris başarıyla HLSL çağrısı ile köşeleri dönüştürmek için kullanılır: mul (vektör, matris) vektörün satır-majör olarak ele alınması gerekir, bu yüzden matematik kütüphanem tarafından sağlanan sütun majör matrisi nasıl çalışabilir?

oldukça basittir: matrisleriniz satırbaşıdır.

Birçok kişi satır-büyük veya transpozisyonlu matrisler kullanır, matrislerin doğal olarak bu şekilde yönlendirilmediğini unuturlar. Böylece bir çeviri matrisi görüyorlar:

1 0 0 0
0 1 0 0
0 0 1 0
x y z 1

Bu, aktarılmış bir çeviri matrisidir . Normal bir çeviri matrisi böyle görünmüyor. Çeviri dördüncü satıra değil 4. sütuna gider . Bazen bunu tamamen çöp olan ders kitaplarında bile görürsünüz.

Bir dizideki matrisin satır veya sütun-büyük olup olmadığını bilmek kolaydır. Satır-büyükse, çeviri 3, 7 ve 11. endekslerde saklanır. Sütun-büyükse, çeviri 12, 13 ve 14. endekslerde saklanır. Tabii ki sıfır bazlı endeksler.

Karışıklıklarınız, aslında satır-büyük olanları kullanırken sütun-büyük matrisler kullandığınıza inanmaktan kaynaklanmaktadır.

Satır ve sütun majör ifadelerinin yalnızca gösterimsel bir kural olduğu ifadesi tamamen doğrudur. Matris çarpımı ve matris / vektör çarpımının mekaniği, kural ne olursa olsun aynıdır.

Değişen, sonuçların anlamıdır.

Sonuçta 4x4 matris sadece 4x4 sayı ızgarasıdır. O değil sahip koordinat sistemi değişikliği başvurmak için. Bununla birlikte, belirli bir matrise anlam atadığınızda , şimdi neyin içinde depolandığını ve nasıl kullanılacağını bilmeniz gerekir.

Yukarıda gösterdiğim çeviri matrisini ele alalım. Bu geçerli bir matris. Bu matrisi float[16]iki yoldan biriyle depolayabilirsiniz :

float row_major_t[16] =    {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};
float column_major_t[16] = {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};

Ancak, bu çeviri matrisinin yanlış olduğunu söyledim, çünkü çeviri yanlış yerde. Özellikle, bu şekilde görünmesi gereken çeviri matrislerinin nasıl oluşturulacağına ilişkin standart kurallara göre aktarıldığını söyledim:

1 0 0 x
0 1 0 y
0 0 1 z
0 0 0 1

Bunların nasıl saklandığına bakalım:

float row_major[16] =    {1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1};
float column_major[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1};

Bildirim column_majorolduğu tam olarak aynı şekilde row_major_t. Dolayısıyla, eğer uygun bir çeviri matrisi alırsak ve bunu sütun-majör olarak saklarsak, bu matrisi transpoze etmek ve onu satır-majör olarak depolamakla aynıdır.

Yalnızca notasyonel bir sözleşme olmakla kastedilen budur. Gerçekten iki kural dizisi vardır: bellek depolama ve aktarma. Bellek depolama sütun-satır büyük iken, transpozisyon normal veya transpoze edilir.

Satır-büyük sırayla oluşturulmuş bir matrisiniz varsa, o matrisin sütun-büyük eşdeğerini transpoze ederek aynı etkiyi elde edebilirsiniz. Ve tam tersi.

Matris çarpımı yalnızca tek bir yolla yapılabilir: iki matris, belirli bir sırayla, belirli değerleri birlikte çarpar ve sonuçları depolarsınız. Şimdi, A*B != B*Aancak asıl kaynak kodu A*B, koduyla aynıdır B*A. Her ikisi de çıktıyı hesaplamak için aynı kodu çalıştırır.

Matris çarpım kodu, matrislerin sütun-büyük veya satır-büyük sırada depolanıp saklanmadığını umursamaz.

Aynı şey vektör / matris çarpımı için söylenemez. İşte nedeni bu.

Vektör / matris çarpımı yanlıştır; yapılamaz. Ancak, olabilir başka matris ile bir matris çarpın. Bir vektörün bir matris olduğunu varsayarsanız, o zaman etkili bir şekilde matris / matris çarpımı yaparak vektör / matris çarpımını yapabilirsiniz.

Bir 4D vektörü bir kolon-vektörü veya bir satır-vektörü olarak düşünülebilir. Yani, 4D vektörü 4x1 matrisi (hatırlayın: matris notasyonunda önce satır sayısı gelir) veya 1x4 matrisi olarak düşünülebilir.

Ama işte şu: İki A ve B matrisi göz önüne alındığında A*B, yalnızca A sütunlarının sayısı B'nin satır sayısıyla aynı ise tanımlanır. Bu nedenle, A 4x4 matrisimizse, B 4 sıralı bir matris olmalıdır içinde. Bu nedenle, A*xx'in bir satır-vektörü olduğu gerçekleştirilemez . Benzer şekilde, x*Ax'in bir sütun-vektör olduğu yerde gerçekleştiremezsiniz .

Bu nedenle, çoğu matris matematik kütüphanesi bu varsayımı yapar: bir vektörü bir matrisle çarpırsanız, gerçekten mantıklı değil, gerçekten çalışan çarpımı yapmak istersiniz .

Herhangi bir 4D vektörü x için aşağıdakileri tanımlayalım. C'nin sütun-vektör matris formu xve' Rnin satır-vektör matris formu olmalıdır x. Bu göz önüne alındığında, herhangi bir 4x4 matris A için, A*CA'yı sütun vektörü ile çarparak matrisi temsil eder x. Ve R*Asatır-vektörünü xA ile çarparak matrisi temsil eder .

Ancak buna katı matris matematiği kullanarak bakarsak, bunların eşdeğer olmadığını görürüz . R*A olamaz aynı olacak A*C. Bunun nedeni, bir satır vektörünün bir sütun vektörü ile aynı şey olmamasıdır. Aynı matris değiller, bu yüzden aynı sonuçları üretmiyorlar.

Ancak, bunlar bir şekilde ilişkilidir. Bu doğru R != C. Ancak, bu da doğru yerde, T devrik operasyonudur. İki matris birbirinin aktarımlarıdır.R = CT

İşte komik bir gerçek. Vektörler matris olarak değerlendirildiğinden, satır-büyük depolama sorusuna karşı da bir sütunları vardır. Sorun şu ki, ikisi de aynı görünüyor . Şamandıra dizisi aynıdır, bu yüzden sadece verilere bakarak R ve C arasındaki farkı söyleyemezsiniz. Sadece farkı söylemek yolu nasıl kullanıldıkları gereğidir.

İki A ve B matrisiniz varsa ve A satır-ana ve B sütun-ana olarak depolanırsa, bunları çoğaltmak tamamen anlamsızdır . Sonuç olarak saçmalık elde edersiniz. Pek değil. Matematiksel olarak, elde ettiğiniz şey yapmanın eşdeğeridir . Veya ; matematiksel olarak aynıdırlar.AT*BA*BT

Bu nedenle, matris çarpımı sadece iki matris (ve unutmayın: vektör / matris çarpımı sadece matris çarpımı ise) aynı ana sırada depolanmışsa anlamlıdır.

Peki, bir vektör sütun-büyük veya satır-büyük? Daha önce de belirtildiği gibi, ikisi de değil. Yalnızca bir sütun matrisi olarak kullanıldığında büyük sütun ve bir satır matrisi olarak kullanıldığında büyük satırdır.

Bu nedenle, sütun ana olan bir A matrisiniz varsa x*A... hiçbir şey ifade etmez. Yine, demek istediğim , ama gerçekten istediğin bu değil. Benzer şekilde, satır- büyükse , aktarılmış çarpma yapar .x*ATA*xA

Bu nedenle, vektör / matris çarpma sırası yapar verilerinizin önemli sipariş bağlı olarak değişiklik (ve aktarılmamıştır matrisleri kullanarak ister).

Neden aşağıdaki kod snippet'inde r! = R2

Çünkü kodunuz bozuk ve buggy. Matematiksel olarak ,. Bu sonucu almazsanız, eşitlik testiniz yanlış (kayan nokta hassasiyeti sorunları) veya matris çarpma kodunuz bozulur.A * (B * C) == (CT * BT) * AT

neden pos3! = pos için

Çünkü bu mantıklı değil. Gerçek olmanın tek yolu şuydu . Ve bu sadece simetrik matrisler için geçerlidir.A * t == AT * tA == AT


@Nicol, Her şey şimdi tıklamaya başlıyor. Kütüphanem (Axiom'dan alınmıştır) sütun-major (ve buna göre tüm çarpma emirleri vb.) Bildirir, ancak bellek düzeni sıralıdır - ana (çeviri endeksleri ve HLSL'nin aktarılmamış matris kullanılarak doğru çalıştığı gerçeği); Ancak şimdi bunun nasıl bir çatışma içinde olmadığını görüyorum. Çok teşekkür ederim!
sebf

2
"Normal bir çeviri matrisi böyle görünmüyor" ve "tamamen çöp" gibi şeyler söylediğin için neredeyse -1 verdim. O zaman devam edip neden tamamen eşdeğer olduklarını güzelce açıklıyorsunuz ve bu nedenle ikisi de diğerinden daha "doğal" değil. Neden o küçük saçmalığı baştan çıkarmıyorsun? Cevabınızın geri kalanı aslında oldukça iyi. (Ayrıca, ilgi için: steve.hollasch.net/cgindex/math/matrix/column-vec.html )
Imre

2
@imre: Çünkü saçmalık değil. Konvansiyonlar önemlidir çünkü iki konvansiyona sahip olmak kafa karıştırıcıdır. Matematikçiler uzun zaman önce matrisler sözleşmesine karar verdiler . "Aktarılmış matrisler" (standarttan aktarıldıkları için adlandırılmıştır) bu sözleşmenin ihlalidir. Eşdeğer oldukları için kullanıcıya gerçek bir fayda sağlamazlar. Farklı oldukları ve yanlış kullanılabildikleri için karışıklık yaratır. Ya da başka bir deyişle, transpoze edilen matrisler olmasaydı, OP bunu asla sormazdı. Ve bu nedenle, bu alternatif sözleşme karışıklık yaratır.
Nicol Bolas

1
@Nicol: 12-13-14 arasında çeviriye sahip bir matris yine de satır-büyük olabilir - daha sonra onunla satır vektörleri kullanırsak (ve vM olarak çarparsak). Bkz. DirectX. VEYA, sütun vektörleri (Mv, OpenGL) ile kullanılan sütun-major olarak görülebilir. Gerçekten aynı. Tersine, eğer bir matris 3-7-11'de çevirime sahipse, o zaman ya sütun vektörleri olan bir satır-ana matrisi, ya da satır vektörleri ile bir sütun-ana matrisi olarak görülebilir. 12-13-14 sürümü gerçekten daha yaygındır, ancak bence 1) bu gerçekten bir standart değildir ve 2) bunu sütun-büyük olarak adlandırmak, mutlaka olması gerekmediği için yanıltıcı olabilir.
imre

1
@imre: Standarttır. Herhangi bir eğitimli matematikçiye çevirinin nereye gittiğini sorun, dördüncü sütuna gittiğini söyleyeceklerdir. Matematikçiler matrisler icat ettiler; onlar sözleşmeleri düzenleyenlerdir.
Nicol Bolas

3

Burada iki farklı kongre seçeneği var. Birincisi, satır vektörleri veya sütun vektörleri kullanıp kullanmadığınızdır ve bu kurallar için matrisler birbirlerinin aktarımlarıdır.

Diğeri ise, matrisleri bellekte satır-büyük sırada mı yoksa sütun- büyükünde mi sakladığınızdır . "Satır-büyük" ve "sütun-büyük" ifadelerinin, satır-vektör / sütun-vektör kuralını tartışmak için doğru terimler olmadığını unutmayın . Satır-büyük ve sütun-büyük bellek düzenleri de bir devrik ile değişir.

OpenGL bir sütun vektör kuralı ve sütun-büyük depolama sırası kullanır ve D3D bir satır vektör kuralı ve satır-büyük depolama sırası kullanır (iyi - en azından D3DX, matematik kütüphanesi yapar), böylece iki devir iptal olur ve ortaya çıkar aynı bellek düzeni OpenGL ve D3D için de geçerlidir. Yani, sırayla bellekte saklanan 16 şamandıra listesi, her iki API'de de aynı şekilde çalışacaktır.

Bu, "matrisin nasıl saklandığı veya GPU'ya aktarıldığı konusunda hiçbir fark yaratmaz" diyenlerin ifade ettiği şey olabilir.

Kod snippet'leriniz için, r! = R2 çünkü bir ürünü devretme kuralı (ABC) ^ T = C ^ TB ^ TA ^ T'dir. Transpozisyon, bir sipariş revizyonu ile çarpma üzerine dağıtılır. Yani sizin durumunuzda r == r2 değil r.Transpose () == r2 almalısınız.

Benzer şekilde, poz! = Pos3 çünkü çarpma sırasını aktardınız ancak tersine çevirmediniz. WpvM * localPos == localPos * wvpM.Tranpose () öğesini almalısınız. Vektör, bir matrisin sol tarafında çarpıldığında otomatik olarak bir satır vektörü ve bir matrisin sağ tarafında çarpıldığında bir sütun vektörü olarak yorumlanır. Bunun dışında, çarpma işleminin gerçekleştirilmesinde bir değişiklik yoktur.

Son olarak, yeniden: "benim sütun büyük WVP matris başarıyla HLSL çağrısı ile köşeleri dönüştürmek için kullanılır: mul (vektör, matris)," Bu konuda emin değilim, ama belki karışıklık / bir hata matrisin ortaya çıkmasına neden oldu matematik kütüphanesi zaten aktarılmıştı.


1

3B grafiklerde hem vektörü hem de noktaları dönüştürmek için matris kullanırsınız. Çeviri matrisi hakkında konuştuğunuz gerçeği göz önüne alındığında, sadece noktalar hakkında konuşacağım (bir vektörü bir matrisle çeviremezsiniz veya daha iyi söyleyerek yapabilirsiniz, ancak aynı vektörü elde edersiniz).

Olarak matris çarpımı ilk matrisin sütun sayısı ikinci bir (bir MXK için anxm matris çarpma) satır sayısına eşit olmalıdır.

Bir nokta (veya vektör) 3 bileşenle (x, y, z) temsil edilir ve hem satır hem de sütun gibi düşünülebilir:

sütun (boyut 3 X 1):

| X |

| Y |

| Z |

veya

satır (boyut 1 X 3):

| X, y, z |

Tercih ettiğiniz kongre seçebilirsiniz, bu sadece bir kongre. Buna çeviri matrisi diyelim. İlk kuralı seçerseniz, bir matris için p noktasını çarpmak için bir post çarpımı kullanmanız gerekir:

T * v (boyut 3x3 * 3x1)

aksi takdirde:

v * T (boyut 1x3 * 3x3)

yazarlar matrisin nasıl saklandığını veya GPU'ya aktarıldığını fark etmediğini iddia ediyorlar

Her zaman aynı sözleşmeyi kullanırsanız fark etmez. Farklı konvansiyon matrisinin aynı bellek temsiline sahip olacağı anlamına gelmez, ancak bir noktayı 2 farklı konvansiyonla dönüştürerek aynı dönüştürülmüş noktayı elde edersiniz:

p2 = B * A * p1; // ilk sözleşme

p3 = p1 * A * B; // ikinci toplantı

p2 == p3;


1

4., 8. ve 12. elementleri işgal eden çeviri bileşenlerinin matrislerinizin "yanlış" olduğu anlamına geliyor.

Çeviri bileşenleri her zaman dönüşüm matrisinin # 13, # 14 ve # 15 girişleri olarak belirtilir ( dizinin ilk öğesini öğe # 1 olarak sayar ).

Bir satır büyük dönüşüm matrisi şöyle görünür:

[ 2 2 2 1 ]   R00 R01 R02 0  
              R10 R11 R12 0 
              R20 R21 R22 0 
              t.x t.y t.z 1 

Bir sütun ana dönüşüm matrisi şöyle görünür:

 R00 R01 R02 t.x   2  
 R10 R11 R12 t.y   2 
 R20 R21 R22 t.z   2 
  0   0   0   1    1 

Satır ana matrisleri satırlardan aşağıya doğru belirtilir .

Yukarıdaki satır büyük matris doğrusal bir dizi olarak ilan, ben yazmak:

ROW_MAJOR = { R00, R01, R02, 0,  // row 1 // very intuitive
              R10, R11, R12, 0,  // row 2
              R20, R21, R22, 0,  // row 3
              t.x, t.y, t.z, 1 } ; // row 4

Bu çok doğal görünüyor. Çünkü uyarı, İngilizce "satır-büyük" yazılır - matris yukarıdaki metinde tam olarak matematikte olduğu gibi görünür.

Ve işte kafa karışıklığı.

Sütun ana matrisleri sütunlardan aşağıya doğru belirtilir

Bu, sütun ana dönüşüm matrisini kodda doğrusal bir dizi olarak belirtmek için şunu yazmanız gerekir:

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // çok sezgisel
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

Bu tamamen karşı sezgisel olduğunu unutmayın !! Bir sütun ana matrisi, lineer bir dizi başlatılırken girişlerini sütunlarda belirtmiştir, bu nedenle ilk satır

COLUMN_MAJOR = { R00, R10, R20, 0,

Matrisin ilk sütununu belirtir :

 R00
 R10
 R20
  0 

ve ilk satır değil, metnin basit düzenine inandığınız gibi. Bir sütun büyük matrisini kodda gördüğünüzde zihinsel olarak transpoze etmeniz gerekir, çünkü belirtilen ilk 4 öğe aslında ilk sütunu tanımlar. Sanırım bu yüzden bir çok insan kodda satır-büyük matrisleri tercih ediyor (GO DIRECT3D !! öksürük.)

Dolayısıyla, satır bileşenleri veya sütun ana matrisleri kullanmanıza bakılmaksızın , çeviri bileşenleri her zaman # 13, # 14 ve # 15 (ilk öğenin # 1 olduğu) doğrusal dizi indeksindedir .

Kodunuza ne oldu ve neden çalışıyor?

Kodunuzda olan şey, bir sütun büyük matrisiniz var evet, ancak çeviri bileşenlerini yanlış noktaya koyuyorsunuz. Matrisi devrettiğinizde # 4 girişi # 13, # 8 ila # 13 ve # 12 ila # 15 girişlerine gider. İşte buyur.


0

Basitçe ifade etmek gerekirse, farkın nedeni matris çarpımının değişmeli olmamasıdır . Düzenli sayı çarpımı ile, A * B = C ise, B * A da = C'yi takip eder. Bu, matrislerde durum böyle değildir. Bu nedenle ya satır-büyük ya da sütun-büyük konuları seçmek.

Neden önemli değil , modern bir API'de (ve özellikle burada gölgelendiricilerden söz ediyorum), kendi sözleşmenizi seçebilir ve matrislerinizi kendi gölgelendirici kodunuzda bu kural için doğru sırayla çarpabilirsiniz. API artık sizi veya diğerini zorlamıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.