4장 부분이 내용과 코드가 많아서 나눠서 올리겠습니다.

 


[ Goal ] 

  1. 하드웨어와 Direct3D의 관계
  2. COM의 정의 및 역할
  3. 저장방식, 페이지 전환, 깊이 버퍼링, 다중표본화 같은 기초 그래픽 개념

1. 기본지식

  • Direct3D 12의 개요
    • Direct3D 는 GPU를 제어하고 프로그래밍 하는 저수준 API ( application programming inerface )
      • 저수준일수록 GPU와 가깝게 동작하므로 더욱 빠른 동작이 가능함.
      • 고수준 API는 디스플레이 생성하는 드라이버를 필요로 한다.\
    • Direct3D 12와 11의 차이점
      • 다중 스레드 지원 개선  
      • CPU부담 크게 줄임
      • 추상화가 줄고 개발자가 관리할게 많아져 어려웠지만 성능 개선

 

 


COM ( Component object Model )

  • DirectX의 프로그래밍 언어 독립성과 하위 호환성을 가능하게 하는 기술
    • 예를 들어 C++에서 만들고 Visual Basic 문서화된 컨트롤을 사용하는 JScript 애플리케이션을 작성할 수 있습니다.
  • COM 객체를 C++ 클래스로 간주하면 이해하기 쉽다.
  • 알아야 할 것 : COM 인터페이스를 가르키는 포인터 얻는 방법
    1. 함수 이용
    2. COM 인터페이스의 메서드를 이용
  • 생성시 new 키워드를 사용할 필요는 없지만 delete가 아닌 release 키워드를 통해 메모리 해제해야 한다.
    • Mirocsoft::WRL::ComPtr 클래스를 사용하면 자동으로 release 되어 사용자가 직접 release 할 필요가 없다.
      • WRL : Windows Runtime Library ( 사용하기 위해 wrl.h 헤더가 필요함 )
      • ComPtr은 쉽게 생각해 COM 객체를 위한 똑똑한 포인터라 할 수 있다.
      • ComPtr의 주요 메소드
        1. Get : COM 인터페이스를 가르키는 포인터 반환
        2. GetAddressOf : COM 인터페이스를 가르키는 포인터의 값( 주소 )를 반환
        3. Reset : ComPtr 인스턴스를 nullptr로 설정하고 바탕 COM 인터페이스의 참조 횟수를 1 감소
  • COM 인터페이스들은 이름이 대문자 I로 시작한다.

 

 

 


텍스처 형식

  • 텍스처는 행렬 ( 배열 ) 이라 생각하면 된다. 
  • 2차원 텍스처는 이미지 자료를 저장하는데 사용된다.
  • 사실 텍스처가 단순한 자료 배열은 아니다. ( 여러 방법에 사용됨 )
    • 텍스처에는 밉맵 수준들이 존재 할 수 있다.
    • GPU 필터링, 다중 표본화 등의 특별한 연산에도 사용된다.
  • 특정 자료형들만 저장할 수 있다.

 

 

 


교환 사슬과 페이지 전환

  • 용어 정리
    • 버퍼 : 주기억 장치와 주변장치 사이에서 데이터를 주고받을 때, 둘 사이의 전송속도 차이를 해결하기 위해 전송할 정보를 임시로 저장하는 고속 기억장치
  • 애니메이션이 껌벅이는 현상을 피하기 위한 과정 ( 이중 버퍼링 )
    1. 한 프레임 전체를 화면 바깥에서 그린다. ( 후면 버퍼 )
    2. 후면 버퍼가 완성이 되면 하나의 완전한 프레임으로써 화면이 표시한다. ( 후면 버퍼 -> 전면 버퍼 )
    3. 표시 되는동안 다시 화면 바깥에서 후면 버퍼를 준비한다.
  • 위와 같은 과정을 하기위해 계속 후면 버퍼를 생성하기에는 메모리 낭비이다.
  • 해결하기 위해서는 버퍼를 2개만 생성해두고 교차해 가며 화면에 표시하는 방법이다.
  • 즉 각 프레임마다 버퍼 포인터를 교차해 가면서 화면에 표시해 나가면 된다. ( 교환 사슬 생성 )
  • 만약 버퍼를 3개를 사용하면 삼중 버퍼링이라 하며 일반적으로는 버퍼 두개로 충분하다.

 

 

 


깊이 버퍼링

  • 용어 정리
    • 렌더링 : 컴퓨터 프로그램을 사용하여 모델 또는 이들을 모아놓은 장면인 씬 파일(scene file)로부터 영상을 만들어내는 과정
  • 각 물체의 깊이 정보를 담는다. ( 즉 거리를 계산한다. )
  • 이미지 자료를 담지 않는 텍스처의 한 예시
  • 픽셀의 값은 0~1 사이의 값으로 값이 클수록 멀리 있는 물체에 해당된다.
    • 예를 들어 1280 X 1024 해상도에서는 깊이 버퍼는 1280 X 1024 개의 원소들로 구성되어있다.
  • Direct3D는 깊이 버퍼링 또는 z-버퍼링이라 하는 기법을 사용한다.
  • 깊이 버퍼링을 통해 한 픽셀에 그려질 물체가 정해지는 과정 ( 렌더링 과정 )
    1. 깊이 버퍼값을 1.0 값으로 초기화하고 색상은 검은색 또는 흰색으로 초기화한다.
    2. 렌더링 순서대로 물체를 가져온다.
    3. 렌더링 할 물체의 깊이 버퍼 값이 기존의 깊이 버퍼값과 비교하여 깊이 판정을 수행한다.
      • 기존 깊이보다 낮으면 색과 깊이 버퍼값을 갱신한다.
      • 만약 크다면 갱신하지 않고 다음 렌더링 물체를 가져온다.
    4. 렌더링 할 물체 모두 가져와 갱신한다.
    5. 최종적으로는 가장 가까운 물체가 해당 픽셀을 차지하게 된다.
  • 깊이 버퍼는 하나의 텍스처 이므로 생성 시 특정한 자료형식을 지정할 필요가 있다.

 

 

 


자원과 서술자

  • 용어 정리
    • 바인딩 : 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위를 의미한다. 예를 들어 함수를 호출하는 부분에서 실제 함수가 위치한 메모리를 연결하는 것도 바로 바인딩이다.
    • 파이프라인 : 파이프라인(영어: pipeline)은 한 데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태로 연결된 구조를 가리킨다.
    • 서술자 ( Descriptor 또는 View ) : 하나의 리소스에 대한 정보를 담은 자료구조
  • 렌더링 과정에서 GPU는 자료를 기록하거나 자료를 읽어온다.
  • GPU가 그리기 명령을 수행하기 전에 먼저 해당 그리기 호출이 참조할 자원들을 렌더링 파이프라인에 바인드 해야한다.
  • 실제로 바인딩 되는것은 해당 자원을 참조하는 서술자이다. 실제 자원이 바인드 되는것이 아니다.
    • 이처럼 서술자들을 거치는 추가적인 간접층을 두는 이유는 GPU자원이라는 것이 범용적인 메모리 조각이기 때문이다. 즉 같은 자원들 서로 다른 렌더링 단계에서 사용 될 수 있기 때문이다.
    • 또한 자원의 일부 영역만 사용하거나 이 자원이 깊이로 사용되어야할지 렌더 대상으로 사용되어야 할지 GPU는 모르기 떄문에 이러한 정보를 서술자가 가지고 있다.
  • 서술자의 형식
    1. CBV/SRV/UAV : 순서대로 contant buffer( 상수 버퍼 ), shader resource ( 셰이더 자원 ), unorderd aceess view ( 순서 없는 접근 ) 을 가르킨다.
    2. RTV : render target ( 렌더 대상 ) 을 가르킨다.
    3. DSV : depth/stencil ( 깊이. 스텐실 ) 자원을 가르킨다.
    4. 표본 추출기 서술자는 텍스처 적용에 쓰이는 표본 추출기 자원을 서술한다.
  • DirectX SDK 문서화에는 무형식 자원은 유연성이 꼭 필요할 때에만 만들고 그렇지 않은 경우에는 형식을 완전히 지정해서 자원을 만들어야 한다고 서술한다.
    • 즉 어떤 용도이든 텍스처를 사용하기 위해서는 초기화 시점에 그 텍스처의 Resource View를 생성해야 한다.
  • Resource View의 역할
    1. DirectEd에게 Resource의 사용 방식 즉 PipeLine의 어떤 단계에서 바인드 할것인지 알려주는 것
    2. 생성 시점에서 Typeless Resource의 Tpye을 지정하는것

 

 

더보기

이 부분은 아직 이해가 잘 안되어서 책을 한번 다 읽어보고 다시 이해해봐야겠다.

 


다중표본화

  • 용어 정리
    • 엘리어싱 : 컴퓨터의 이미지 표현 방식이 아날로그 방식이 아닌 디지털 방식이기 떄문에 정확한 선을 나타낼때 계단처럼 끊어지는 현상이 나타난다. 이를 앨리어싱 현상이라 한다.
  • 앨리어싱 현상을 방지 하기 위해서 렌더링 과정에서 화면 해상도의 4배만큼으로 후면 버퍼의 크기를 결정한다.
  • 이후 색을 결정하고 전면 버퍼로 옮길때 화면 해상도의 크기로 다시 환원하는데 이를 하향 표본화라 한다.
  • 색을 결정하는 방법에 따라 그리고 계산하는 방법에 따라 2가지 기법이 나뉜다.
초과 표본화 다중 표본화
4배만큼 커진 후면 버퍼에서 각 픽셀마다 색의 값을 계산한다.

이후 하향 표본화 시 4개의 픽셀의 평균으로 해당 픽셀의 값을 결정한다.
4배만큼 커진 후면 버퍼에서 4개의 픽셀의 색을 하나의 색으로 통일시킨다.

이후 4개의 픽셀에 대한 가시성, 포괄도를 계산하여 최종 색상을 결정한다.
좀더 정확한 표현이 가능하지만 자원을 많이 쓴다.

각 픽셀별로 계산하기 때문에
덜 정확한 표현을 하지만 자원을 조금 쓴다.

4개의 픽셀에 대해 중심의 색으로 색을 결정하기 때문에

 

  • Direct3D 의 다주 표본화
    • 다중 표본화를 위해서는 DXGI_SAMPLE_DESC 라는 구조체를 적절히 채워야 한다.
typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;            // 픽셀당 추출할 표본의 갯수
    UINT Quality;          // 품질 수준 ( 하드웨어 제조사마다 다를 수 있다. )
} DXGI_SAMPLE_DESC;

 

 

 


DXGI

  • 용어 정리
    • 디스플레이 어댑터 : 물리적인 하드웨어 장치 ( 예를 들어 그래픽 카드 )
    • DXGI : Direct3D와 함께 쓰이는 API.
  • DXGI는 여러 가지 공통적인 그래픽 기능성을 처리한다. 
    • 전체화면 모드 전환, 디스플레이 어댑터나 모니터, 지원되는 디스플레이 모드 같은 그래픽 시스템 정보의 열거 등
  • 초기화 과정에서 쓰이는 인터페이스
    1. IDXGIFactory : DXGI 객체들을 생성하는 메소드를 구현
      • 소프트웨어 어댑터 생성
      • 스왑체인 생성
      • 어댑터 열거
      • 전체화면으로 전환을 제어하는 윈도우 반환
      • WIndow Association 생성
    2. IDXGIAdapter : 컴퓨터의 하드웨어 및 소프트웨어 기능을 추상화 한것
      • 시스템이 그래픽 구성 요소에 대한 장치 인터페이스를 지원하는지 확인
      • 어댑터 출력을 열거
      • 어댑터의 DXGI 1.0 설명을 가져옴

 

더보기

이 부분은 아직 이해가 잘 안되어서 책을 한번 다 읽어보고 다시 이해해봐야겠다.

  •  

참고 : DirectX 12 를 이용한 3D 게임 프로그래밍 입문 / 한빛미디어

 

'독학 > DirectX' 카테고리의 다른 글

[ DirectX ] 기초 수학 3. 변환  (0) 2022.11.09
[ DirectX ] 기초 수학 2. 행렬 대수  (1) 2022.11.09
[ DirectX ] 기초 수학 1. 벡터 대수  (0) 2022.11.09

[ Goal ] DirectXMath 라이브러리 가 제공하는 여러 변환 행렬 함수 숙지


1. 변환

  • 3차원 그래픽은 물체의 외부 표면을 근사하는 일단의 삼각형들로 물체를 표현한다.
  • 이러한 삼각형들에 대해 이동변환, 회전변환, 비례변환 등을 통해 3차원 그래픽 구조를 움직이게 한다.

 

  • 선형 변환
    • 아래와 같은 성질이 성립되면 선형 변환이라 부른다.
    • 이를 왜 맨처음에 소개를 하냐면 선형변환이 보장 되어야 우리가 변환에 대한 행렬을 구할수 있기 때문이다.

선형 변환의 성질 ( 필요충분 조건 )
선형 변환을 통해 변환행렬을 구하는 방법

 

 

 

  • 비레 변환
    • 물체의 크기를 바꾸는 효과를 낸다.
    • 비례 변환 또한 선형 변환이므로 변환 행렬이 존재한다. 변환 행렬 구하는 방법은 위의 식을 통해 구할수 있다.

 

비례 변환의 정의

 

비례 행렬
비례 행렬의 역

 

  • 회전 변환
    • 벡터 v를 축 n에 대하여 회전하고 싶을때 사용되는 변환이다.
    • 이러한 변환에서 회전각은 n의 반대방향 기준으로 시계방향으로 측정된다.
    • 회전 변환 행렬의 특징은 역행렬이 전치행렬인 것이다.
      • 이러한 행렬을 정규직교 행렬이라 한다.
      • 정규직교 행렬은 각 행벡터의 크기가 1이며 각 행벡터 서로가 직교인 특징을 가진다.

 

회전 변환의 정의
회전 변환 행렬 이떄 x,y,z는 회전 기준 축의 단위벡터
회전변환 역행렬 = 전치행렬
순서대로 회전축이 x일때, y일때, z일떄의 회전 변환 행렬 ( 4x4 행렬 처럼 보이지만 계산과정을 잘 생각해보면 단순 3x3 행렬과 같다. )

 

 

 

 

 


2. 아핀변환

 

 

2.1 동차 좌표

  • 1x3 의 행렬로 벡터와 점을 표기하기에는 벡터값과 정점을 구분하는데 어려움이 있다. 이를 해결하기 위해 1x4 행렬로 벡터값과 정점을 표현한 것이 동차 좌표이다.
    • 벡터 : ( x, y, z, 0 ) -> 벡터끼리의 합은 벡터이므로 4번째 요소를 0
    • 정점 : ( x, y, z, 1 ) -> 벡터 + 정점은 정점이므로 4번째 요소를 1 ( 정점 + 정점은 수행 불가 )

 

2.2 아핀변환의 정의와 행렬 표현

  • 아핀 변환은 단순히 선형 변환 후 이동벡터를 더한 것이다.

아핀 변환 정의

  • 3x3 행렬이 아닌 4x4행렬로 나타내면 더욱 간단히 나타낼 수 있다. 이를 아핀변환의 행렬 표현이라 한다.
  • 주목할 점은 마지막 성분 계산시 내적하는 벡터가 (0,0,0,1) 이라는 점이다. 이는 아핀변환시 벡터는 벡터로 정점은 정점으로 결과값이 나온다는 말이다.

아핀 변환 행렬

 

 

2.3 이동변환

  • 위의 아핀변환 행렬에서 조금만 생각하면 이동행렬이 무엇일지 예상이 간다.
  • 이동행렬은 변환없이 이동벡터만 더하는 것으로 생각할수 있다.
  • 당연히 역행렬은 -T이다.

이동 변환의 행렬표현

 

 

 


3. 좌표계 변환

  • 좌표계 변환은 간단한 예시를 들자면 섭씨온도와 화씨온도에 대한 변환이라 생각하면 된다.
  • 즉 기준이 되는 값을 그리고 증가 감소 방향을 바꾸는 것이다.
  • 이는 물체의 형태가 바뀌는 변환이 아닌것을 유의하자. ( 물론 크기가 비례해서 증가 감소가 할수 있지만 완전히 다른 형태로 바뀔수는 없다. 위상수학을 배웠다면! 쉽게! 이해! 가능! 물론 안배웠어도 쉽게 이해 가능 )

 

3.1 벡터의 좌표계 변환

  • 벡터는 기준점이 원점인것을 기억하자.
  • A좌표계 안에 있는 p 벡터는 B좌표계에서 어떻게 표현하는지 알고 싶다면 아래의 식을 사용하면 된다.
    • A좌표계의 단위 벡터 : u, v
    • B좌표계의 단위 벡터 : x, y
    • A좌표계에서의 p벡터 값 : ( a, b ) = a*u + b*v
    • B좌표계에서의 p벡터 값 : a*x + b*y
    • 단순히 벡터를 단위벡터의 선형결합으로 나타낸 후 곱해진 단위 벡터를 바꾸고 싶은 좌표계로 바꾸면 된다.

 

 

3.2 점의 좌표계 변환

  • 벡터와 달리 점은 위치 값이 있다. 이를 생각해보면 점의 위치는 좌표계의 원점 값 + 이동벡터라 생각하면 된다.
  • A좌표계 안에 있는 점p 는 B좌표계에서 어떻게 표현하는지 알고 싶다면 아래의 식을 사용하면 된다.
    • A좌표계의 단위 벡터 : u, v
    • B좌표계의 단위 벡터 : x, y
    • A좌표계에서의 점p 값 : ( a, b ) = a*u + b*v + Qa ( Qa는 A좌표계에서 측정한 A좌표계 원점의 좌표값이다. )
    • B좌표계에서의 p벡터 값 : a*x + b*y + Qb ( Qb는 A좌표계에서 측정한 B좌표계 원점의 좌표값이다. )
    • 벡터의 좌표계 변환에다 좌표계의 원점 위치를 더해주면 되는것이다. 이때 더해지는 Qa Qb값이 어떤 좌표계를 기준으로 측정되어있는지 주의하자.

 

 

3.3 좌표계 변환의 행렬

  • 위의 내용을 다시 생각해보면 행렬 표현으로 아래와 같이 표현할수 있다.
  • 벡터의 값은 (x, y, z ,0 ) 과 같은 형식으로 되어있으므로 결과의 4번째 더해지는 값은 항상 0이 된다. 또한 점값은 4번째 더해지는 값이 Qb가 됨을 알수 있다.

각 u, v, w 는 변환할 좌표계의 단위 벡터이다. Qb는 변환할 좌표계의 원점의 위치이다.

  • 또한 좌표변환 행렬이 A->B로 바꾸는 행렬이면 역행렬은 B->A로 변환하는 행렬이다.
    • 물론 역행렬이 존재하지 않는 좌표변환 행렬이 존재할수 있지만 일반적으로 다루지 않는다.

 

 

 

3.4 능동적 변환 행렬과 좌표계 변환 행렬

  • 좌표계변환 행렬과 아핀변환 행렬을 확인해보면 서로 모양이 같다는것을 확인 할 수 있다.
  • 또한 수학적으로 동치 관계이다. ( 즉 둘이 서로 같은거라는 말 )
  • 이 두가지를 모두 배우는 이유는 아래의 예시를 통해 이해하자.
    • 현재 좌표계가 카메라의 좌표계이고 어떤 물체를 플레이어 캐릭터 주변을 회전하게 하고 싶을때 
    • 능동적변환 즉 아핀변환을 사용하면 할수는 있지만 코드 이해하기 쉽지 않다.
      • 회전 -> 플레이어쪽으로 이동
    • 하지만 좌표계 변환을 하면 코드를 쉽게 이해할수 있다.
      • 좌표계 플레이어로 변환 -> 회전
  • 이와같이 모든 변환 행렬은 두 방법을 통해 나타낼수 있다. 하지만 경우에 따라서 어떠한 행렬이 좀더 이해하기 쉬운지 또는 계산하기 편한지를 확인해야한다.

 

 

 

 


4. DirectXMath 에서의 변환 행렬 함수들

  • 아래와 같은 DirectXMath 라이브러리에 관련 함수들이 존재
// 비례행렬 생성
XMMATRIX XM_CALLCONV XMMAtrixScaling(
	float ScaleX,
    float SclaeY,
    float XcaleZ);                  // 각 매개변수는 비례계수이다.
    
    
// 벡터의 성분들로 비례행렬 생성
XMMATRIX XM_CALLCONV XMMatrixScalingFromVector(
	FXMVECTOR Scale);               // 매개변수 : (sx, sy, sz, 0)


// x축에 대한 회전행렬 Rx 생성
XMMATRIX XM_CALLCONV XMMatrixRotationX(
	float Angle);                   // X축에 대해 Angle만큼 회전하는 변환행렬 생성
    
    
// y축에 대한 회전행렬 Ry 생성
XMMATRIX XM_CALLCONV XMMatrixRotationY(
	float Angle);                   // Y축에 대해 Angle만큼 회전하는 변환행렬 생성
    
    
// z축에 대한 회전행렬 Rz 생성
XMMATRIX XM_CALLCONV XMMatrixRotationZ(
	float Angle);                   // Z축에 대해 Angle만큼 회전하는 변환행렬 생성
    
    
// 임의의축에 대한 회전행렬 Rn 생성
XMMATRIX XM_CALLCONV XMMatrixRotationN(
	FXMVECTOR Axis                  // 회전축의 벡터 (ex. x축 : (1,0,0,0) )
    float Angle);                   // X축에 대해 Angle만큼 회전하는 변환행렬 생성
    
    
// 이동행렬 T 생성
XMMATRIX XM_CALLCONV XMMatrixTranslation(
	float OffsetX,
    float OffsetY,
    float OffsetZ);                 // 매개변수는 각 이동 오프셋이다.
    

// 벡터를 이용한 이동행렬 T 생성
XMMATIRX XM_CALLCONV XMMatrixTranslation(
	FXMVECTOR Offset);              // ( offsetX, offsetY, offsetZ, 0 )
    
    
// 벡터와 행렬의 곱 함수 -> 결과 점 ( =  벡터 * 행렬,  != 행렬 * 벡터 )
XMVECTOR XM_CALLCONV XMVector3TransformCoord(
	FXMVECTOR V,                    // 결과가 점이므로 벡터의 4번째 값이 자동으로 1로 설정됨
    CXMMATRIX M);                   // 입력 행렬
    
    
// 벡터와 행렬의 곱 함수 -> 결과 벡터 ( =  벡터 * 행렬,  != 행렬 * 벡터 )
XMVECTOR XM_CALLCONV XMVector3TransformNormal(
	FXMVECTOR V,                    // 결과가 벡터이므로 벡터의 4번째 값이 자동으로 0으로 설정됨
    CXMMATRIX M);                   // 입력 행렬

 

 


참고 : DirectX 12 를 이용한 3D 게임 프로그래밍 입문 / 한빛미디어

 

[ Goal ] DirectXMath 라이브러리 이해


1. 행렬

  • 행벡터, 열벡터 들로 이루어진 배열로써 각 벡터는 원소로 구성되어있다.
  • 3차원 컴퓨터 그래픽에서 행렬은 비례나 회전, 이동같은 기하학적 변환을 간결하게 서술하는데 사용된다.
더보기

행렬에 대해 자세한 내용이 필요하면 아래의 링크를 참고

https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC

  • 행렬 곱셈에서 결합법칙의 중요성
    •  A : 10000 x 3 행렬 ( 좌표값 )     B : 3x3 행렬 ( 회전 행렬 )     C : 3x3 행렬 ( 이동 행렬 )
    • (AxB)xC 의 계산량 : 10000 * 9 + 10000 * 9 = 180000
    • Ax(BxC) 의 계산량 : 10000 * 9 + 3*9 = 90027
    • 즉 결합에 따라 계산량이 반으로 줄어들 수 있다. ( 속도 향상 )

 

  • 행렬식
    • 행렬식은 정방행렬을 입력받아 실수값을 출력하는 특별한 함수이다.
    • 행렬식은 기하학적으로 3차원 입체의 부피와 관련이 있다.

 

  • 역행렬
    • 행렬 대수는 나눗셈을 정의 하지 않지만 곱셈의 역원은 정의한다. 이를 역행렬이라 한다.
    • 역행렬은 방적식의 해를 구할때 유용하게 사용된다.
    • n차원의 역행렬을 구하는 방법은 대학 수준의 선형대수학 수업을 들으면 된다..
    • DirectX에서 주로 다루는 3차원 그래픽에서는 역행렬을 구하는 공식은 거의 필요없다. 이유는 주로 사용되는 행렬의 역행렬이 거의 미리 알수 있는 특별한 형태이기 때문이다.

 

 

  • 전치 행렬
    • 전치행렬은 행렬의 행들과 열들을 맞바꾼 것이다.
    • 주로 행렬곱에 있어서 차원을 맞추기 위해 사용한다.

2. DirectXMath 라이브러리의 행렬

  • 3차원 그래픽에서 점과 벡터를 변환할때는 1x4 벡터와 4x4 행렬을 사용한다.

 

2.1 사용 라이브러리

  1. DirectXMath.h = DirectXMath 라이브러리를 사용하기 위한 필수 라이브러리
  2. DirectXPackedVector.h = 몇가지 추가적인 자료 형식을 위한 라이브러리

 

2.2 행렬 형식들

  • DirectXMath 에서 XMMATRIX 형식을 사용한다. 이는 XMVECTOR가 4개 모인 형식이다.
#if (defined(_M_IX86) || defind(_M_X64) || defined(_M_ARM)) && \
	defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__deslspec(align(16)) struct XMMATRIX
#endif
{
	// SIMD 활용을 위해, 행렬을 4개의 XMVECTOR로 표현
    XMVECTOR r[4]
    
    XMMATRIX() {}
    
    // 행벡터 4개를 지정하여 행렬을 초기화
    XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, CXMVECTOR R3)
    	{ r[0] = R0, r[1] = R1, r[2] = R2, r[3] = R3 }
    
    // 성분 16개를 지정해서 행렬을 초기화
    XMMATRIX(float m00, float m01, float m02, float m03,
             float m10, float m11, float m12, float m13,
             float m20, float m21, float m22, float m23,
             float m30, float m31, float m32, float m33);
             
    // 부동소수점 수 16개 배열을 지정해서 행렬을 초기화
    explicit XMMATRIX(_In_reads_(16) const float *pArray);
    
    
    
    XMMATRIX    operator+ () const { return *this; }
    XMMATRIX    operator- () const;

    XMMATRIX&   XM_CALLCONV     operator+= (FXMMATRIX M);
    XMMATRIX&   XM_CALLCONV     operator-= (FXMMATRIX M);
    XMMATRIX&   XM_CALLCONV     operator*= (FXMMATRIX M);
    XMMATRIX&   operator*= (float S);
    XMMATRIX&   operator/= (float S);

    XMMATRIX    XM_CALLCONV     operator+ (FXMMATRIX M) const;
    XMMATRIX    XM_CALLCONV     operator- (FXMMATRIX M) const;
    XMMATRIX    XM_CALLCONV     operator* (FXMMATRIX M) const;
    XMMATRIX    operator* (float S) const;
    XMMATRIX    operator/ (float S) const;
    
    friend XMMATRIX     XM_CALLCONV     operator* (float S, FXMMATRIX M);
 };
  • 코드와 같이 XMMATRIX는 SIMD 활용을 위해 XMVECTOR 인스턴스 4개를 사용한다.

 

 

  • XMMATRIX의 여러 생성자 외에 XMMatrixSet이라는 함수로도 인스턴스 생성이 가능하다.
XMMATRIX    XM_CALLCONV     XMMatrixSet(float m00, float m01, float m02, float m03,
                                        float m10, float m11, float m12, float m13,
                                        float m20, float m21, float m22, float m23,
                                        float m30, float m31, float m32, float m33);

 

 

 

 

  • XMVECTOR를 클래스에 저장할 때 자료 멤버의 형식으로 XMFLOATn들을 사용하는것과 같이 XMMATRIX또한 XMFLOAT4X4 형식이 존재한다.
// 4x4 Matrix: 32 bit floating point components
struct XMFLOAT4X4
{
    union
    {
        struct
        {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
            float _41, _42, _43, _44;
        };
        float m[4][4];
    };

    XMFLOAT4X4() = default;

    XMFLOAT4X4(const XMFLOAT4X4&) = default;
    XMFLOAT4X4& operator=(const XMFLOAT4X4&) = default;

    XMFLOAT4X4(XMFLOAT4X4&&) = default;
    XMFLOAT4X4& operator=(XMFLOAT4X4&&) = default;

    XM_CONSTEXPR XMFLOAT4X4(float m00, float m01, float m02, float m03,
                            float m10, float m11, float m12, float m13,
                            float m20, float m21, float m22, float m23,
                            float m30, float m31, float m32, float m33)
        : _11(m00), _12(m01), _13(m02), _14(m03),
          _21(m10), _22(m11), _23(m12), _24(m13),
          _31(m20), _32(m21), _33(m22), _34(m23),
          _41(m30), _42(m31), _43(m32), _44(m33) {}
    explicit XMFLOAT4X4(_In_reads_(16) const float *pArray);

    float       operator() (size_t Row, size_t Column) const { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) { return m[Row][Column]; }
};
  • 마찬가지로 XMFLOAT4X4과 같은 자료형식을 계산에 사용하면 SIMD의 장점을 취할 수 없다.
  • 이를 해결하기 위해 계산 전에 XMMATRIX로 변환하고 계산 후에는 다시 XMFLOAT4X4으로 변환해야한다.
    • 적재 함수 = XMMATRIX로 변환
    • 저장 함수 = XMFLOAT4X4으로 변환
// 로드 함수
XMMATRIX    XM_CALLCONV     XMLoadFloat3x3(_In_ const XMFLOAT3X3* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat4x3(_In_ const XMFLOAT4X3* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat4x3A(_In_ const XMFLOAT4X3A* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat3x4(_In_ const XMFLOAT3X4* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat3x4A(_In_ const XMFLOAT3X4A* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat4x4(_In_ const XMFLOAT4X4* pSource);
XMMATRIX    XM_CALLCONV     XMLoadFloat4x4A(_In_ const XMFLOAT4X4A* pSource);

// 적재 함수
void        XM_CALLCONV     XMStoreFloat3x3(_Out_ XMFLOAT3X3* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat4x3(_Out_ XMFLOAT4X3* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat4x3A(_Out_ XMFLOAT4X3A* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat3x4(_Out_ XMFLOAT3X4* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat3x4A(_Out_ XMFLOAT3X4A* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat4x4(_Out_ XMFLOAT4X4* pDestination, _In_ FXMMATRIX M);
void        XM_CALLCONV     XMStoreFloat4x4A(_Out_ XMFLOAT4X4A* pDestination, _In_ FXMMATRIX M);

 

2.3 행렬 함수

  • DirectXMath 라이브러리에는 아래와 같은 행렬 관련 함수들이 존재한다.
// 단위행렬 I를 반환
XMMATRIX    XM_CALLCONV     XMMatrixIdentity();

// 단위행렬인지 판별여부 반환
bool        XM_CALLCONV     XMMatrixIsIdentity(FXMMATRIX M); 

// 행렬 곱 AxB를 반환
XMMATRIX    XM_CALLCONV     XMMatrixMultiply(FXMMATRIX M1, CXMMATRIX M2);

// 입력된 행렬의 전치행렬 반환
XMMATRIX    XM_CALLCONV     XMMatrixTranspose(FXMMATRIX M);

// 행렬식 반환 ( 반환 자료형 = XMVECTOR )
// (det M, det M, det M, det M)
XMVECTOR    XM_CALLCONV     XMMatrixDeterminant(FXMMATRIX M);

// 역행렬 반환
// 입력 ( 행렬식, 행렬 )
XMMATRIX    XM_CALLCONV     XMMatrixInverse(_Out_opt_ XMVECTOR* pDeterminant, _In_ FXMMATRIX M);
  • XMMATRIX 매개변수를 선언 할때에는 XMVECTOR의 규칙과 같은 규칙이 적용된다. 단 XMMATRIX 하나가 XMVECTOR 4개로 해당되는것이 다르다.
    • FXMMATRIX : 첫 XMMATRIX 변수
    • CXMMATRXI : 이후 XMMATRIX 변수

 

 


3. 예시 코드

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the  "<<" operators so that we can use cout to 
// output XMVECTOR and XMMATRIX objects.
ostream& XM_CALLCONV operator << (ostream& os, FXMVECTOR v)
{
    XMFLOAT4 dest;
    XMStoreFloat4(&dest, v);

    os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ", " << dest.w << ")";
    return os;
}

ostream& XM_CALLCONV operator << (ostream& os, FXMMATRIX m)
{
    for (int i = 0; i < 4; ++i)
    {
        os << XMVectorGetX(m.r[i]) << "\t";
        os << XMVectorGetY(m.r[i]) << "\t";
        os << XMVectorGetZ(m.r[i]) << "\t";
        os << XMVectorGetW(m.r[i]);
        os << endl;
    }
    return os;
}

int main()
{
    // Check support for SSE2 (Pentium4, AMD K8, and above).
    if (!XMVerifyCPUSupport())
    {
        cout << "directx math not supported" << endl;
        return 0;
    }

    XMMATRIX A(1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 2.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 4.0f, 0.0f,
        1.0f, 2.0f, 3.0f, 1.0f);

    XMMATRIX B = XMMatrixIdentity();

    XMMATRIX C = A * B;

    XMMATRIX D = XMMatrixTranspose(A);

    XMVECTOR det = XMMatrixDeterminant(A);
    XMMATRIX E = XMMatrixInverse(&det, A);

    XMMATRIX F = A * E;

    cout << "A = " << endl << A << endl;
    cout << "B = " << endl << B << endl;
    cout << "C = A*B = " << endl << C << endl;
    cout << "D = transpose(A) = " << endl << D << endl;
    cout << "det = determinant(A) = " << det << endl << endl;
    cout << "E = inverse(A) = " << endl << E << endl;
    cout << "F = A*E = " << endl << F << endl;
 
    return 0;
}

참고 : DirectX 12 를 이용한 3D 게임 프로그래밍 입문 / 한빛미디어

 

[ Goal ] DirectXMath 라이브러리 이해


1. 벡터

  • 벡터는 크기와 방향을 값이다.
  • 같은 값을 가진 벡터라 해도 기준 좌표계가 서로 다르면 같은 벡터라 할 수 없다.
  • 벡터를 다룰 떄에는 주어진 벡터의 좌표가 현재 어떤 기준 좌표계에 상대적인지 알 필요가 있다.

 

  • 기본적인 벡터 연산들
    • 벡터 합 = 두 벡터의 합을 나타냄
    • 벡터 차 = 두 벡터의 차를 나타냄 ( 하나의 벡터가 다른 하나의 벡터로 이동하기위한 벡터를 구할때 사용 )
    • 스칼라 곱 = 벡터의 길이 증가, 감소
    • 벡터 내적 = 기하학적 의미로는 두 벡터가 이루는 각도를 구하기 위해 사용 
      • 내적을 이용해 벡터 projection이 가능하다. ( 그람-슈미트 직교화에서 사용 )
    • 벡터 외적 = 기하학적 의미로는 두벡터에 직교인 벡터를 생성할 때 사용

 

  • 직교화
    • 직교화는 주어진 벡터들로 부터 새로운 기준좌표계를 생성할때 주로 사용된다.
    • 1. 그람-슈미트 직교화 ( Gram-Schmidt Orthogonaliztion )는 내적을 이용
    • 2. 외적을 이용한 직교화 방법도 존재한다.
그람-슈미트 직교화 외적을 이용한 직교화

 


2. DirectXMath 라이브러리의 벡터

  • Windows SDK의 일부로써 SSE2명령 집합을 사용한다.
    • SSE2 = Streaming SIMD Extensions 2 ( 즉 SIMD를 사용한다. )
    • SIMD = 128비트 너비의 레지스터들을 이용해 32비트 float또는 int를 한꺼번에 처리 가능한 단위
  • SIMD을 이용하여 4차원 벡터의 연산을 빠르게 할수 있으며 만약 2차원 또는 3차원 벡터 계산시에는 SIMD의 성분에 0을 보내주어 연산을 할 수 있다.

 

2.1 사용 라이브러리

  1. DirectXMath.h = directXMath 라이브러리를 사용하기 위한 필수 라이브러리
  2. DirectXPackedVector.h = 몇가지 추가적인 자료 형식을 위한 라이브러리

 

2.2 벡터 형식들

  • DirectXMath 에서 핵심 벡터 형식은 SIMD 하드웨어 레지스터에 대응되는 XMVECTOR이다.
typedef __m128 XMVECTOR; 
  • 이때 __m128은 특별한 SIMD 형식이다. 벡터 계산시 SIMD의 장점이 발휘되려면 벡터가 반드시 이 형식이어야한다.

 

  • XMVECTOR는 16바이트 ( 128비트 )의 경계에 alignment 되어야 하는데, 지역 변수와 전역변수는 자동으로 이루어진다. 하지만 클래스 자료 멤버에는 이 형식 대신 XMFLOAT2, XMFLOAT3, XMFLOAT4 를 사용하는것을 권장한다.
// XMFLOAT4 구조체 예시
struct XMFLOAT4
{
    float x;
    float y;
    float z;
    float w;


 XMFLOAT4() {}
 XMFLOAT4(float _x, float_y, float_z, float _w) :
    x(_x), y(_y), z(_z), w(_w) {}
    
 explicit XMFLOAT4(_In_reads_ (4) const float *pArray) :
    x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
    
 XMFLOAT4& operator = (const XMFLOAT4 Float4)
 {
  x = Float4.x; y = Float4.y; z = Float4.z; w = Float4.w; return *this;
 }
};
  • 하지만 XMFLOATn과 같은 자료형식을 계산에 사용하면 SIMD의 장점을 취할 수 없다.
  • 이를 해결하기 위해 계산 전에 XMVECTOR로 변환하고 계산 후에는 다시 XMFLOATn으로 변환해야한다.
    • 적재 함수 = XMVECTOR로 변환
    •  
    • 저장 함수 = XMFLOATn으로 변환
// XM_CALLCONV는 레지스터 활용을 위한 호출 규약이 컴파일러마다 
// 다를수 있으므로 의존성을 없애기 위한 호출 규약 지시자이다.

// 적재함수
XMVECTOR XM_CALLCONV XMLoadFloat2(const XMFLOAT2 *pSource);
XMVECTOR XM_CALLCONV XMLoadFloat3(const XMFLOAT3 *pSource);
XMVECTOR XM_CALLCONV XMLoadFloat4(const XMFLOAT4 *pSource);


// 아래에 있는 FXMVECTOR 자료형에 대해서는 바로 아래에 설명

// 로드함수
void XM_CALLCONV XMStoreFloat2(XMFLOAT2 *pDestination, FXMVECTOR V);
void XM_CALLCONV XMStoreFloat3(XMFLOAT3 *pDestination, FXMVECTOR V);
void XM_CALLCONV XMStoreFloat4(XMFLOAT4 *pDestination, FXMVECTOR V);

 

 

정리하면 다음과 같다.

  1. 지역변수나 전역변수에는 XMVECTOR 사용
  2. 클래스 자료 멤버에는 XMFLOAT2, XMFLOAT3, XMFLOAT4를 사용
  3. 계산을 수행하기 전에 적재 함수들을 이용해 XMFLOATn을 XMVECTOR로 변환
  4. XMVECTOR 인스턴스들로 계산 수행
  5. 저장 함수들을 사용해 XMVECTOR를 XMFLOATn으로 변환

 

2.3 매개변수 전달

  • XMVECTOR 인스턴스를 인수로 해서 함수를 호출할 때, 효율성을 위해 XMVECTOR 값을 스택이 아닌 SSE/SSE2 레지스터를 통해 함수에 전달되어야 한다.
  • 이러한 방식으로 전달할수 있는 인수의 갯수는 플랫폼별로 상이하다.
    • 이를 해결하기 위해 XMVECTOR 매개변수에 대해 여러 형식을 사용한다.
    • 1. FXMVECTOR = 처음 세번째 XMVECTOR 매개변수까지 사용
    • 2. GXMVECTOR = 4번째 XMVECTOR 매개변수에 사용 
    • 3. HXMVECTOR = 5,6 번째 XMVECTOR 매개변수에 사용
    • 4. CXMVECTOR = 그 이후 XMVECTOR 매개변수에 사용
  • 또한 SSE/SSE2 이용한 호출 규약 역시 컴팡일러 마다 상이하다.
    • 이를 해결하기 위해 함수 이름 앞에 반듯 XM_CALLCONV 라는 호출규약 지시자를 붙인다.
// 매개변수가 모두 XMVECTOR로 이루어져있을 경우

inline XMMATRIX XM_CALLCONV XMMatrixTransformation(
    FXMVECTOR ScalingOrigin,
    FXMVECTOR ScalingOrientationQuaternion,
    FXMVECTOR Scaling,
    GXMVECTOR RotationOrigin,
    HXMVECTOR RotationQuaternion,
    HXMVECTOR Translation);
    
    
    
// 매개변수사이 XMVECTOR가 아닌 매개변수가 끼어있을 경우 XMVECTOR의 순서만 중요하다.

inline XMMATRIX XM_CALLCONV XMMatrixTransformation2D(
    FXMVECTOR ScalingOrigin,
    float     ScalingOrientation,
    FXMVECTOR Scaling,
    FXMVECTOR RotationOrigin,
    float     Rotation,
    GXMVECTOR Translation);

 

2.4 상수 벡터

  • 상수 XMVECTOR 인스턴스에는 반드시 아래의 매개변수를 사용해야한다.
    • Float32 = XMVECTORF32
    • Int32 = XMVECTORU32
static const XMVECTORF32 g_vHalfVector = { 0.5f, 0.5f, 0.5f, 0.5f};

static const XMVECTORU32 vGrabY = { 0x00000000, 0xFFFFFFFF, 0x00000000, 0x00000000 };

 

2.5 오버로딩 된 연산자들

  • 벡터간 연산 ( 덧셈, 뺄셈, 스칼라곱 ) 등을 위해 오버로딩된 연산자들 존재
XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V);
XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V);

XMVECTOR& XM_CALLCONV operator+= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator-= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator*= (XMVECTOR& V1, FXMVECTOR V2);
XMVECTOR& XM_CALLCONV operator/= (XMVECTOR& V1, FXMVECTOR V2);

XMVECTOR& operator*= (XMVECTOR& V, float S);
XMVECTOR& operator/= (XMVECTOR& V, float S);

XMVECTOR XM_CALLCONV operator+ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator- (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V1, FXMVECTOR V2);
XMVECTOR XM_CALLCONV operator* (FXMVECTOR V, float S);
XMVECTOR XM_CALLCONV operator* (float S, FXMVECTOR V);
XMVECTOR XM_CALLCONV operator/ (FXMVECTOR V, float S);

 

2.6 기타 상수 및 함수

  • 원주율이 포함된 여러 공식의 근삿값을 구하기 위해 아래와 같은 상수들이 정의되어있다.
const float XM_PI = 3.14592654f;
// 근사를 위한 저장되어있는 파이관련 상수들
const float XM_2PI = 6.283185307f;
const float XM_1DVPI = 0.318309886f;
const float XM_1DV2PI = 0.1591543f;
const float XM_PIDIV2 = 1.570796327f;
const float XM_PIDV4 = 0.785398163f;


// 아래와 같이 각도를 라디안 단위로 변환이 가능하다.
inline float XMConvertToRadians(float fDegrees) {

return fDegrees * (XM_PI / 180.0f);

}

inline float XMConvertToDegrees(float fRadians) {

return fRadians * (180.0f / XM_PI);

}

// 최소 최대값을 리턴하는 함수도 있다.
template<class T> inline T XMMin(T a, T b) { return (a < b) ? a : b; }
template<class T> inline T XMMax(T a, T b) { return (a < b) ? a : b; }

 

2.7 설정 함수

  • XMVECTOR 객체의 내용을 설정하는 용도로 아래와 같은 함수들이 존재
  • 즉 초기화 함수라 생각하면 된다.
// 0 Vector 반환
XMVECTOR XM_CALLCONV XMVectorZero();

// Vector (1, 1, 1, 1) 반환
XMVECTOR XM_CALLCONV XMVectorSplatOne();

// Vector (x, y, z, w) 반환
XMVECTOR XM_CALLCONV XMVectorSet(float x, float y, float z, float w);

// Vector (s, s, s, s) 반환
XMVECTOR XM_CALLCONV XMVectorReplicate(float Value);

// Vector (vx, vx, vx, vx) 반환
XMVECTOR XM_CALLCONV XMVectorSplatX(FMVECTOR V);

// Vector (vy, vy, vy, vy) 반환
XMVECTOR XM_CALLCONV XMVectorSplatY(FMVECTOR V);

// Vector (vz, vz, vz, vz) 반환
XMVECTOR XM_CALLCONV XMVectorSplatZ(FMVECTOR V);

 

2.8 벡터 함수들

  • 다양한 벡터 연산이 존재한다.
  • 함수 이름에 2, 3, 4가 붙어 있다면 각 차원의 벡터에 대한 연산이다.
  • 내적의 반환 형식이 XMVECTOR인 이유는 스칼라 연산과 SIMD 벡터 연산의 전환을 최소화 하기위함이다.
// 벡터의 길이 반환
XMVECTOR XM_CALLCONV XMVector3Length(FXMVECTOR V);

// 벡터의 길이 제곱 반환
XMVECTOR XM_CALLCONV XMVector3LengthSq(FXMVECTOR V);

// V1과 V2의 내적 반환
XMVECTOR XM_CALLCONV XMVector3Dot(FXMVECTOR V1, FXMVECTOR V2);

// V1과 V2의 외적 반환
XMVECTOR XM_CALLCONV XMVector3Cross(FXMVECTOR V1, FXMVECTOR V2);

// V 정규화
XMVECTOR XM_CALLCONV XMVector3Normalize(FXMVECTOR V);

// V에 수직인 벡터 반환
XMVECTOR XM_CALLCONV XmVector30rthgonal(FXMVECTOR V);

// V1과 V2 사이의 각도 반환
XMVECTOR XM_CALLCONV XMVector3AngleBetweenVectors(FXMVECTOR V1, FXMVECTOR V2);

void XM_CALLCONV XMVector3ComponentsFromNormal(
XMVECTOR* pParallel,       // proj_n(v) 반환 : n벡터를 기준으로 v를 영사
XMVECTOR* pPerpendicular,  // perp_n(v) 반환 : perp_n(v) = v - proj_n(v)
FXMVECTOR V,               // 입력 v
FXMVECTOR Normal);         // 입력 n

// V1 == V2 반환
bool XM_CALLCONV XMVector3Equal(FXMVECTOR V1, FXMVECTOR V2);

// V1 != V2 반환
bool XM_CALLCONV XMVECTOR3NotEqual(FXMVECTOR V1, FXMVECTOR V2);

 

 

2.9 부동소수점의 오차

  • 컴퓨터는 당연히 숫자를 정확하게 표현 할 수 없다.
  • 이러한 부정확함 떄는에 두 부동소수점 수의 상등을 판정할 때에는 두 수가 근사적으로 같은지를 본다.
  • 이때 사용되는 값은 입실론이다. ( 입실론 = 주관적인 아주 작은 수 )
#include <Windows.h> // XMVerifyCPUSupport에 필요함
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>

using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;


int main()
{
	cout.precision(8);

	// SSE2를 지원하는지 확인
	if (!XMVerifyCPUSupport)
	{
		cout << "DirectXMath를 지원하지 않음" << endl;
		return 0;
	}

	XMVECTOR u = XMVectorSet(1.0f, 1.0f, 1.0f, 0.0f);
	XMVECTOR n = XMVector3Normalize(u);

	float LU = XMVectorGetX(XMVector3Length(n));



	// 수학적으로는 길이가 반드시 1이어야 한다
	cout << LU << endl;

	if (LU == 1.0f)
	{
		cout << "길이 1" << endl;
	}
	else
	{
		cout << "길이 1이 아님" << endl;
	}

	// 1을 임의의 지수로 거듭제곱해도 여전히 1이어야 한다
	float powLU = powf(LU, 1.0e6f);
	cout << "LU^(10^6) = " << powLU << endl;

	return 0;

}

// 실행결과
// 0.9999994
// 길이 1 아님
// LU^(10^6) = 0.94213694
  • 위와 같이 부동소수점에는 오차가 존재한다 이를 해결하기 위해 아래와 같은 입실론을 사용한 코드가 존재한다.
XMFINLINE bool XM_CALLCONV XMVector3NearEqual(
    FXMVECTOR U,
    FXMVECTOR V,
    FXMVECTOR Epsilon
);

// 반환값 :
// abs(U.x-V.x) <= Epsilon.x &&
// abs(U.y-V.y) <= Epsilon.y &&
// abs(U.z-V.z) <= Epsilon.z

 


3. 예시 코드

#include <windows.h> // for XMVerifyCPUSupport
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <iostream>
using namespace std;
using namespace DirectX;
using namespace DirectX::PackedVector;

// Overload the  "<<" operators so that we can use cout to 
// output XMVECTOR objects.
ostream& XM_CALLCONV operator << (ostream& os, FXMVECTOR v)
{
    XMFLOAT3 dest;
    XMStoreFloat3(&dest, v);

    os << "(" << dest.x << ", " << dest.y << ", " << dest.z << ")";
    return os;
}

int main()
{
    cout.setf(ios_base::boolalpha);

    // Check support for SSE2 (Pentium4, AMD K8, and above).
    if (!XMVerifyCPUSupport())
    {
        cout << "directx math not supported" << endl;
        return 0;
    }

    XMVECTOR n = XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f);
    XMVECTOR u = XMVectorSet(1.0f, 2.0f, 3.0f, 0.0f);
    XMVECTOR v = XMVectorSet(-2.0f, 1.0f, -3.0f, 0.0f);
    XMVECTOR w = XMVectorSet(0.707f, 0.707f, 0.0f, 0.0f);

    // Vector addition: XMVECTOR operator + 
    XMVECTOR a = u + v;

    // Vector subtraction: XMVECTOR operator - 
    XMVECTOR b = u - v;

    // Scalar multiplication: XMVECTOR operator * 
    XMVECTOR c = 10.0f*u;

    // ||u||
    XMVECTOR L = XMVector3Length(u);

    // d = u / ||u||
    XMVECTOR d = XMVector3Normalize(u);

    // s = u dot v
    XMVECTOR s = XMVector3Dot(u, v);

    // e = u x v
    XMVECTOR e = XMVector3Cross(u, v);

    // Find proj_n(w) and perp_n(w)
    XMVECTOR projW;
    XMVECTOR perpW;
    XMVector3ComponentsFromNormal(&projW, &perpW, w, n);

    // Does projW + perpW == w?
    bool equal = XMVector3Equal(projW + perpW, w) != 0;
    bool notEqual = XMVector3NotEqual(projW + perpW, w) != 0;

    // The angle between projW and perpW should be 90 degrees.
    XMVECTOR angleVec = XMVector3AngleBetweenVectors(projW, perpW);
    float angleRadians = XMVectorGetX(angleVec);
    float angleDegrees = XMConvertToDegrees(angleRadians);

    cout << "u                   = " << u << endl;
    cout << "v                   = " << v << endl;
    cout << "w                   = " << w << endl;
    cout << "n                   = " << n << endl;
    cout << "a = u + v           = " << a << endl;
    cout << "b = u - v           = " << b << endl;
    cout << "c = 10 * u          = " << c << endl;
    cout << "d = u / ||u||       = " << d << endl;
    cout << "e = u x v           = " << e << endl;
    cout << "L  = ||u||          = " << L << endl;
    cout << "s = u.v             = " << s << endl;
    cout << "projW               = " << projW << endl;
    cout << "perpW               = " << perpW << endl;
    cout << "projW + perpW == w  = " << equal << endl;
    cout << "projW + perpW != w  = " << notEqual << endl;
    cout << "angle               = " << angleDegrees << endl;

    return 0;
}

참고 : DirectX 12 를 이용한 3D 게임 프로그래밍 입문 / 한빛미디어

+ Recent posts