누군가가 곱셈 / 연결에서 열 대 행의 열에 미치는 영향을 설명 할 수 있습니까?


11

뷰 및 프로젝션 매트릭스를 구성하는 방법을 배우고 매트릭스의 두 표준에 대한 혼란으로 인해 구현에 어려움을 겪고 있습니다. 행렬을 곱하는 방법
알고 있으며 곱하기 전에 전치하면 결과가 완전히 바뀌므로 다른 순서로 곱해야한다는 것을 알 수 있습니다.

비록 내가 이해하지 못하는 것은 '표기법'만 의미하는 것입니다. 여기여기 의 기사 에서 저자는 매트릭스가 저장되거나 GPU로 전송되는 방식에 차이가 없지만 두 번째에는 그 행렬은 행 전공을 위해 메모리에 배치되는 방법과 분명히 같지 않습니다 . 내 프로그램에서 채워진 행렬을 보면 변환 구성 요소가 4, 8 및 12 번째 요소를 차지하는 것을 볼 수 있습니다.

을 고려하면:

"열 주요 행렬을 사용한 후 곱셈은 행 주요 행렬을 사용한 미리 곱셈과 동일한 결과를 생성합니다."

다음 코드 스 니펫에서 왜?

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

합니까 R = R2!대한 pos3 = POS를하지 왜! :

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

곱셈 프로세스가 행렬이 행 또는 열 메이저인지에 따라 변경됩니까 아니면 동등한 순서입니까?

DirectX에 제공 될 때 열 주요 WVP 행렬이 HLSL 호출을 사용하여 꼭짓점을 변환하는 데 성공적으로 사용된다는 점은 mul (vector, matrix) 입니다. row-major 이므로 수학 라이브러리에서 제공하는 열 주 행렬은 어떻게 작동합니까?



답변:


11

프로그램에서 채워진 행렬을 보면 변환 구성 요소가 4, 8 및 12 번째 요소를 차지하는 것을 볼 수 있습니다.

시작하기 전에 이해하는 것이 중요합니다. 이는 행렬이 행 메이저 임을 의미합니다 . 따라서이 질문에 대답하십시오.

내 열 주요 WVP 행렬은 HLSL 호출 mul (vector, matrix)로 정점을 변환하는 데 성공적으로 사용됩니다. 이로 인해 벡터가 행 주요로 처리되어야하므로 수학 라이브러리에서 제공하는 열 주요 행렬은 어떻게 작동합니까?

아주 간단합니다. 행렬이 행을 크게합니다.

많은 사람들이 행 전공 또는 전치 행렬을 사용하므로 행렬이 자연스럽게 그렇게 지향되지 않는다는 사실을 잊어 버립니다. 그래서 그들은 번역 행렬을 다음과 같이 봅니다 :

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

이것은 바뀐 번역 행렬 입니다. 그것은 일반적인 변환 행렬이 아닙니다. 번역은 4 간다 이 아닌 네 번째 행. 때로는 교과서에서 이것을 볼 수도 있습니다.

배열의 행렬이 행인지 열인지를 쉽게 알 수 있습니다. 행이 큰 경우 변환은 3, 7, 11 번째 인덱스에 저장됩니다. 열이 큰 경우 번역은 12, 13 및 14 번째 인덱스에 저장됩니다. 물론 제로 기준 지수.

혼란은 실제로 행 주요 행렬을 사용할 때 열 주요 행렬을 사용하고 있다는 믿음에서 비롯됩니다.

행 대 열 전공은 표기법에 불과하다는 진술은 전적으로 사실입니다. 행렬 곱셈과 행렬 / 벡터 곱셈의 역학은 규칙에 관계없이 동일합니다.

변화의 결과는 결과의 의미입니다.

결국 4x4 매트릭스는 단지 4x4 그리드 숫자입니다. 좌표계 변경을 언급 할 필요 는 없습니다 . 그러나 일단 특정 행렬에 의미 를 부여 하면 이제 저장된 내용과 사용 방법을 알아야합니다.

위에서 보여준 변환 행렬을 보자. 유효한 행렬입니다. 다음 float[16]두 가지 방법 중 하나로 해당 매트릭스를 저장할 수 있습니다 .

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};

그러나 번역이 잘못된 위치에 있기 때문에이 변환 행렬이 잘못되었다고 말했습니다. 필자는 번역 행렬을 작성하는 방법에 대한 표준 규칙과 관련하여 다음과 같이 바뀌 었습니다.

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

이들이 어떻게 저장되는지 봅시다 :

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};

공지 사항 column_major입니다 동일row_major_t. 따라서 적절한 변환 행렬을 가져 와서 열 주요로 저장하면 해당 행렬을 바꾸고 행 주요로 저장하는 것과 같습니다.

그것이 표기법 일 뿐이라는 의미입니다. 메모리 저장과 전치라는 두 가지 규칙이 있습니다. 메모리 스토리지는 열 대 행 메이저이며, 조옮김은 정상 대 조옮김입니다.

행 주요 순서로 생성 된 행렬이있는 경우 해당 행렬과 동등한 열 주요 변환을 변환하여 동일한 효과를 얻을 수 있습니다. 그 반대.

행렬 곱셈은 한 가지 방법으로 만 수행 할 수 있습니다. 두 개의 행렬이 지정된 순서대로 특정 값을 곱하고 결과를 저장합니다. 자, A*B != B*A하지만, 실제 소스 코드 A*B에 대한 코드와 동일합니다 B*A. 둘 다 동일한 코드를 실행하여 출력을 계산합니다.

행렬 곱셈 코드는 행렬이 열 주요 또는 행 주요 순서로 저장되는지 여부를 신경 쓰지 않습니다.

동일 없는 벡터 / 행렬 곱셈라고 할. 그리고 여기에 이유가 있습니다.

벡터 / 행렬 곱셈은 잘못된 것입니다. 할 수 없습니다. 그러나 행렬에 다른 행렬을 곱할 수 있습니다 . 따라서 벡터가 행렬 인 경우 행렬 / 행렬 곱셈을 수행하여 벡터 / 행렬 곱셈을 효과적으로 수행 할 수 있습니다.

4D 벡터는 열 벡터 또는 행 벡터로 간주 될 수 있습니다. 즉, 4D 벡터는 4x1 행렬 (행렬 표기법에서는 행 개수가 먼저 됨) 또는 1x4 행렬로 생각할 수 있습니다.

두 가지 행렬 A와 B가 주어지면 A A*B의 열 수가 B의 행 수와 동일한 경우에만 정의됩니다. 따라서 A가 4x4 행렬 인 경우 B는 4 개의 행이있는 행렬이어야합니다. 그 안에. 따라서을 수행 할 수 없습니다 A*x. 여기서 x는 행 벡터 입니다. 마찬가지로 x*Ax가 열 벡터 인 경우 수행 할 수 없습니다 .

이 때문에 대부분의 행렬 수학 라이브러리는 다음과 같은 가정을합니다. 벡터에 행렬을 곱하면 벡터는 실제로 작동 하는 곱셈을 의미합니다.

4D 벡터 x에 대해 다음을 정의합시다. C열 - 벡터한다 매트릭스 의 형태 x, 및 R, 행 벡터한다 매트릭스 형태 x. 이를 감안할 때, 4x4 행렬 A*CA에 대해 A에 열-벡터를 곱한 행렬을 나타냅니다 x. 그리고 R*A행 벡터 x에 A를 곱한 행렬을 나타냅니다 .

그러나 우리가 엄격한 행렬 수학을 사용하여 이것을 살펴보면 이것들이 동등하지 않다는 것을 알 수 있습니다 . 와 같을 R*A 수 없습니다A*C . 행 벡터는 열 벡터와 같지 않기 때문입니다. 그것들은 동일한 행렬이 아니므로 동일한 결과를 생성하지 않습니다.

그러나 한 가지 방식으로 관련되어 있습니다. 사실이다 R != C. 그러나 여기서 T 는 전치 연산입니다. 두 행렬은 서로 바뀝니다.R = CT

재미있는 사실이 있습니다. 벡터는 행렬로 취급되기 때문에 열 대 행 주요 저장 문제가 있습니다. 문제는 둘 다 똑같이 보인다는 것 입니다. float의 배열은 동일하므로 데이터를 보면 R과 C의 차이점을 알 수 없습니다. 에만 차이를 구별하는 방법은 그들이 사용하는 방법입니다.

두 개의 행렬 A와 B가 있고 A가 행 주요로 저장되고 B가 열 주요로 저장되는 경우, 곱하는 것은 완전히 의미없습니다 . 결과적으로 말도 안됩니다. 글쎄,별로. 수학적으로 얻을 수있는 것은하는 것과 같습니다 . 또는 ; 그것들은 수학적으로 동일합니다.AT*BA*BT

따라서 행렬 곱셈은 두 행렬 (그리고 벡터 / 행렬 곱셈은 행렬 곱 임일뿐)이 동일한 주요 순서로 저장된 경우에만 의미가 있습니다.

그렇다면 벡터 열 주요 또는 행 주요입니까? 앞에서 언급했듯이 둘 다입니다. 열 행렬로 사용되는 경우에만 열 주요이며 행 행렬로 사용되는 경우 주요 행입니다.

따라서 주요 열인 행렬 A가 있으면 x*A아무것도 의미하지 않습니다. 다시, 그것은을 의미 하지만 , 그것은 당신이 정말로 원했던 것이 아닙니다. 마찬가지로 행이 큰 경우 곱셈을 바꿉니다 .x*ATA*xA

따라서, 벡터 / 행렬 곱셈의 순서는 않는 데이터의 전공 순서에 따라 변화를 (당신은 전치 행렬을 사용하고 있는지 여부).

다음 코드 스 니펫에서 r! = r2를 수행하는 이유

코드가 깨지고 버그가 있기 때문입니다. 수학적으로 . 이 결과를 얻지 못하면 동등성 테스트가 잘못되었거나 (부동 소수점 정밀도 문제) 행렬 곱셈 코드가 손상되었습니다.A * (B * C) == (CT * BT) * AT

pos3! = pos는 왜

말이되지 않기 때문입니다. 사실이 되는 유일한 방법은 if 입니다. 그리고 그것은 대칭 행렬에만 해당됩니다.A * t == AT * tA == AT


@Nicol, 모든 것이 지금 클릭하기 시작합니다. 내 라이브러리 (Axiom에서 가져온)가 열-주요 (및 모든 곱셈 순서 등)을 선언함에 따라 내가보고있는 것과 내가 생각해야 할 것 사이의 연결 끊김으로 인해 혼란이 있었지만 메모리 레이아웃은 행입니다. -major (번역 지수와 HLSL이 변환되지 않은 행렬을 사용하여 올바르게 작동한다는 사실로 판단); 그러나 지금 이것이 어떻게 충돌하지 않는지 봅니다. 대단히 감사합니다!
sebf

2
"정상적인 변환 행렬이 아닌 것"과 "완전한 쓰레기"라는 말을 거의 -1했습니다. 그런 다음 왜 그들이 완전히 동등한 지에 대해 잘 설명하고 따라서 다른 것보다 더 "자연적인"것도 아닙니다. 처음부터 그 작은 넌센스를 제거하지 않겠습니까? 나머지 답변은 실제로 다소 좋습니다. (또한 관심있는 분들을 위해 : steve.hollasch.net/cgindex/math/matrix/column-vec.html )
imre

2
@imre : 말도 안 돼요. 컨벤션은 두 개의 컨벤션이 혼동되기 때문에 중요합니다. 수학자들은 오래 전에 행렬에 대한 협약을 세웠습니다 . "전치 행렬"(표준에서 전치되기 때문에 명명)은 해당 규칙을 위반합니다. 그것들은 동등하기 때문에 사용자에게 실질적인 이익을주지 않습니다. 그것들은 다르고 오용 될 수 있기 때문에 혼란을 야기합니다. 또는 다른 방법으로, 전치 행렬이 존재하지 않았다면 OP는 결코 이것을 묻지 않았을 것입니다. 따라서이 대체 규칙은 혼란을 야기합니다.
Nicol Bolas

1
@Nicol : 12-13-14로 변환 된 행렬은 여전히 ​​행 전공이 될 수 있습니다. 행 벡터를 함께 사용하면 vM으로 곱할 수 있습니다. DirectX를 참조하십시오. 또는 열 벡터로 사용되는 열 주요 항목으로 볼 수 있습니다 (Mv, OpenGL). 정말 동일합니다. 반대로 행렬이 3-7-11로 변환 된 경우 열 벡터가 포함 된 행 주요 행렬 또는 행 벡터가 포함 된 열 주요 행렬로 볼 수 있습니다. 12-13-14 버전은 실제로 더 일반적이지만 제 생각에는 1) 실제로 표준이 아니며 2) 열을 중요하게 부르는 것이 반드시 그런 것은 아니기 때문에 오도 될 수 있습니다.
11 초

1
@imre : 표준입니다. 번역이 진행되는 실제 훈련 된 수학자에게 물어보십시오. 수학자들은 행렬을 발명했습니다 . 그들은 협약을 설정 한 사람들입니다.
Nicol Bolas

3

직장에서 두 가지 다른 컨벤션 선택이 있습니다. 하나는 행 벡터 또는 열 벡터를 사용하는지 여부이며, 이러한 규칙에 대한 행렬은 서로 바뀝니다.

다른 하나는 행렬 을 행 주요 순서 또는 열 주요 순서 로 메모리 에 저장하는지 여부 입니다. "행 주"와 "열 전공"참고 하지 행 벡터 / 열 벡터 규칙을 논의하기위한 올바른 용어는 많은 사람들이 등을 오용에도 불구하고 .... 행 전공 및 열 전공 메모리 레이아웃 도 조옮김에 따라 다릅니다.

OpenGL은 열 벡터 규칙과 열 주요 저장 순서를 사용하고 D3D는 행 벡터 규칙과 행 주요 저장 순서 (최소한 D3DX, 수학 라이브러리)를 사용하므로 두 조옮김이 취소되고 결과가 나타납니다. OpenGL과 D3D 모두 동일한 메모리 레이아웃이 작동합니다. 즉, 메모리에 순차적으로 저장된 동일한 16 개의 부동 소수점 목록은 두 API에서 동일한 방식으로 작동합니다.

이것은 사람들이 "매트릭스가 저장되거나 GPU로 전송되는 방식에 아무런 영향을 미치지 않는다"는 의미 일 수 있습니다.

코드 스 니펫은 제품 전치 규칙이 (ABC) ^ T = C ^ TB ^ TA ^ T이므로 r! = r2입니다. 조옮김은 곱셈 순서로 곱셈을 분산시킵니다. 따라서 귀하의 경우 r == r2가 아니라 r.Transpose () == r2를 가져와야합니다.

마찬가지로, 곱하기 순서를 바꾸지 않았지만 바꾸지 않았기 때문에 pos! = pos3. wpvM * localPos == localPos * wvpM.Tranpose ()를 가져와야합니다. 벡터는 행렬의 왼쪽에 곱하면 행 벡터로, 행렬의 오른쪽에 곱하면 열 벡터로 자동 해석됩니다. 그 외에는 곱셈이 수행되는 방식에는 변화가 없습니다.

마지막으로, re : "내 열 주요 WVP 행렬은 HLSL 호출을 사용하여 꼭짓점을 변환하는 데 성공적으로 사용됩니다 : mul (vector, matrix)"이것에 대해 잘 모르겠지만 혼동 / 버그로 인해 행렬이 수학 라이브러리는 이미 바뀌 었습니다.


1

3D 그래픽에서는 행렬을 사용하여 벡터와 점을 모두 변환합니다. 변환 행렬에 대해 이야기하고 있다는 점을 고려할 때, 나는 점에 대해서만 이야기 할 것입니다 (벡터로 행렬을 번역 할 수는 없습니다.

에서는 행렬 곱셈 제 행렬의 열의 개수는 두 번째 (만약 mxk anxm 대한 매트릭스를 곱할 수)의 행의 수와 동일해야한다.

점 (또는 벡터)은 3 개의 성분 (x, y, z)으로 표시되며 행 또는 열과 같이 간주 될 수 있습니다.

열 (차원 3 X 1) :

| x |

| y |

| z |

또는

행 (차원 1 X 3) :

| x, y, z |

선호하는 컨벤션을 선택할 수 있습니다. 컨벤션 일뿐입니다. 이것을 변환 행렬이라고하자. 첫 번째 규칙을 선택한 경우 행렬의 점 p를 곱하려면 후행 곱셈을 사용해야합니다.

T * v (차원 3x3 * 3x1)

그렇지 않으면:

v * T (차원 1x3 * 3x3)

저자는 매트릭스가 저장되거나 GPU로 전송되는 방식에 아무런 영향을 미치지 않는다고 주장하는 것처럼 보입니다.

항상 같은 규칙을 사용하면 아무런 차이가 없습니다. 그것은 다른 규칙의 행렬이 동일한 메모리 표현을 가질 것이라는 것을 의미하지는 않지만 두 가지 다른 규칙으로 점을 변환하면 동일한 변환 점을 얻을 수 있습니다.

p2 = B * A * p1; // 첫 번째 규칙

p3 = p1 * A * B; // 두 번째 규칙

p2 == p3;


1

4 번째, 8 번째 및 12 번째 요소를 차지하는 변환 구성 요소 는 행렬이 "잘못된"것을 의미합니다.

변환 구성 요소는 항상 변환 행렬의 항목 # 13, # 14 및 # 15 로 지정됩니다 ( 배열의 첫 번째 요소를 요소 # 1로 계산 ).

행 주요 변환 행렬은 다음과 같습니다.

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

열 주요 변환 행렬은 다음과 같습니다.

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

행 주요 행렬은 행 아래지정됩니다 .

위의 행 주요 행렬을 선형 배열로 선언하면 다음과 같습니다.

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

매우 자연스러운 것 같습니다. 주의를 기울이기 때문에 영어는 "행 전공"으로 작성됩니다. 행렬은 수학에서와 같이 위의 텍스트에 정확하게 나타납니다.

그리고 여기 혼란의 요점이 있습니다.

열 주요 행렬은 열 아래로 지정됩니다.

즉, 코드에서 열 주요 변환 행렬을 선형 배열로 지정하려면 다음을 작성해야합니다.

    COLUMN_MAJOR = {R00, R10, R20, 0, // COLUMN # 1 // 매우 반 직관적
                     R01, R11, R21, 0,
                     R02, R12, R22, 0,
                     tx, ty, tz, 1};

이것은 완전히 반 직관적입니다! 열 주요 행렬에는 선형 배열을 초기화 할 때 열 아래에 항목이 지정 되어 있으므로 첫 번째 행

COLUMN_MAJOR = { R00, R10, R20, 0,

행렬 의 첫 번째 열 을 지정합니다 .

 R00
 R10
 R20
  0 

텍스트의 간단한 레이아웃이 믿기 때문에 첫 번째 행이 아닙니다 . 지정된 첫 4 개의 요소가 실제로 첫 번째 열을 설명하기 때문에 코드에서 볼 때 열 주요 행렬을 정신적으로 바꾸어야합니다. 나는 이것이 많은 사람들이 코드에서 행 주요 행렬을 선호하는 이유라고 생각합니다 (GO DIRECT3D !! 기침).

따라서 변환 구성 요소는 행 메이저 또는 열 메이저 매트릭스를 사용하는지 여부에 관계없이 항상 선형 배열 인덱스 # 13, # 14 및 # 15 (여기서 첫 번째 요소는 # 1)에 있습니다.

코드에서 어떤 일이 발생했으며 왜 작동합니까?

코드에서 일어나는 일은 열 행렬이 있지만 변환 구성 요소를 잘못된 위치에 넣는 것입니다. 행렬을 조바꿈하면 항목 # 4는 항목 # 13, 항목 # 8에서 # 13으로, 항목 # 12에서 # 15로 이동합니다. 그리고 거기 있습니다.


0

간단히 말해서, 차이의 이유는 행렬 곱셈이 정식 적이 지 않기 때문 입니다. 숫자를 정기적으로 곱하면 A * B = C이면 B * A도 = C를 따릅니다. 행렬에는 해당되지 않습니다. 그렇기 때문에 행 주요 또는 열 주요을 선택하는 것이 중요합니다.

문제가 되지 않는 이유 는 최신 API (특히 여기에서 쉐이더를 말하고 있습니다)에서 자신의 컨벤션을 선택하고 자신의 셰이더 코드에서 해당 컨벤션에 대한 올바른 순서로 행렬을 곱할 수 있기 때문입니다. API는 더 이상 귀하를 강제하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.