회귀분석의 다양한 유형

 

1. 선형 회귀(Linear regression)

 

머신러닝에서 가장 일반적인 회귀분석 유형이라고 할 수 있는 선형 회귀는 예측 변수와 종속 변수로 구성되며, 이 둘은 선형 방식으로 서로 연관지어져 있다. 선형 회귀는 위에서 설명한 대로 가장 적합한 선, 즉 최적적합선을 사용한다.

변수들이 서로 선형적으로 연결되어 있는 경우 선형 회귀를 사용한다. 광고 지출 증가가 판매에 미치는 영향을 예측할 때 등이 예가 될 수 있다.

이와같이 데이터를 가장 잘 표현해주는 하나의 직선을 구하는 것이 선형회귀이다.

 

그러나 선형 회귀분석은 특이치에 영향을 받기 쉬우므로 빅데이터 집합을 분석하는 데 사용해서는 안 된다.

 


2. 로지스틱 회귀(Logistic regression)

종속 변수에 이산 값이 있는 경우, 다시 말해 0 또는 1, 참 또는 거짓, 흑 또는 백, 스팸 또는 스팸 아닌 것 등의 두 가지 값 중 하나만 취할 수 있는 경우 로지스틱 회귀를 사용하여 데이터를 분석할 수 있다.

 

즉 이벤트 발생에대해 확률을 구할 수 있다.

 

하지만 로지스틱 회귀는 비선형 문제를 해결하는 데 사용할 수 없으며 불행히도 오늘날의 많은 시스템은 비선형이다. 또한 로지스틱 회귀는 사용 가능한 가장 강력한 알고리즘이 아니다.


3. 릿지 회귀(Ridge regression)

그러나, 불가피하게 독립 변수들 사이에 높은 상관 관계가 있는 경우라면 리지 회귀가 더 적합한 접근방식이다. 다중 회귀라고도 불리는 리지 회귀는 정규화 또는 규제화(regularization) 기법으로 알려져 있으며 모델의 복잡성을 줄이는 데 사용된다. 또한 ‘리지 회귀 페널티’로 알려진 약간의 편향, 즉 바이어스(bias)를 사용하여 모델이 과대적합(overfitting)에 덜 취약하게 만든다.

 


4. 라쏘 회귀(Lasso regression)

라쏘 회귀는 리지 회귀와 같이 모델의 복잡성을 줄여주는 또 다른 정규화 기법이다. 회귀 계수의 절대 사이즈를 금지함으로써 복잡성을 줄인다. 리지 회귀와는 다르게 아예 계수 값을 0에 가깝게 만든다.

그 장점은 기능 선택을 사용할 수 있다는 것이다. 데이터 집합에서 기능 세트를 선택하여 모델을 구축할 수 있다. 라쏘 회귀는 필요한 요소들만 사용하고 나머지를 0으로 설정함으로써 과대적합을 방지할 수 있다.

 


5. 다항 회귀(Polynomial regression)

다항 회귀는 선형 모델을 사용하여 비선형 데이터 집합을 모델링한다. 이것은 동그란 모양의 구멍에 네모난 모양의 못 또는 말뚝을 끼워 넣는 것과 같다. 다항 회귀는 독립 변수가 여러 개인 선형 회귀를 뜻하는 다중 선형 회귀와 비슷한 방식으로 작동하지만, 비선형 곡선을 사용한다. 즉, 데이터 포인트가 비선형 방식으로 존재할 때 사용한다.

모델은 이 데이터 포인트들을 지정된 수준의 다항식 특성으로 변환하고 선형 모델을 사용하여 모델화한다. 선형 회귀에서 볼 수 있는 직선이 아닌 곡선의 다항식 선을 사용하여 최적적합을 수행한다. 그러나 이 모델은 과대적합으로 나타나기 쉬우므로 이상한 결과치를 피하기 위해서는 끝 부분의 곡선을 분석하는 것이 좋다.

 


회귀 분석에는 위에서 소개한 것들보다 더 많은 종류가 있지만, 이 다섯 가지가 가장 일반적으로 사용되는 것들이다. 가장 적합한 모델을 선택하면 데이터가 가진 잠재력을 최대한 활용하여 더 큰 인사이트를 얻을 수 있다.

 

 

유튜브 영상 따라하며 생긴 문제이다.

 

씬 화면에서는 틈이 보이지 않고 각 타일별로 틈이 없도록 크기와 위치조정을 해둔상태

 

위의 사진을 보면 맵 타일의 크기는 1이고 각 타일간 간격도 1로 설정해뒀다.

 

틈이 발생할수 없지만.. 실행을 시켜보면 틈이 발생한다.

 

실행시 틈이발생

실제로 게임 동작과정에서 이 틈에 오브젝트들이 걸리는 현상도 발생한다.

 

틈에 걸려서 이동시 오브젝트의 방향이 바뀜

 

물론 지나갈때 항상 걸리는것이 아닌 가끔 걸린다..

 

맵을 구성하는 아틀라스의 Fixel Per Unit은 현재 16으로 설정되어있다.

 

16*16 픽셀의 리소스라 16으로 설정시 1유닛에 정확이 맞아 떨어짐

 

조금 크게.. 해볼까.. 생각을해서 Fixel Per Unit을 15로 설정해봤다.

 

실제로 한 오브젝트가 1유닛 크기보다 커짐

하지만.. 동작할때는 크게 문제없고 나타나선 선들도 사라짐...

 

이렇게 해결하는게 맞나..?

 

왜 이런게 발생했는지도 모르겠다..

 

공부좀 해야지..

 

 

 

 


추가로.. 메인카메라를 캐릭터 오브젝트 안으로 들여보내니깐 바로 해결됨... 진짜 왜 발생한거지..?

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 게임 프로그래밍 입문 / 한빛미디어

ML에서 데이터 타입들에 따라 다르게 전처리 한다.

 

예를 들어 범주형과 수치형으로 나눌수 있다.

범주형 수치형
숫자값으로는 의미를 안가짐 숫자값으로 의미를 가짐
소형, 중형, 대형 170cm, 164.5cm , ...

위와 같이 확실히 다른것을 할수 있다..

주로 데이터들이 범주형은 pandas에서 dtypes 함수를 호출했을때 결과값은 object이고

수치형은 float 등 숫자형으로 나올것이다. ( 물론 dataFrame.info() 와 dataFrame.describe() 를 통해 정확히 분류해야함 )

 

그러면 전처리시 범주형과 수치형을 나눠서 해야하는데 인덱스별로 즉 컬럼별로 다 코딩을 해야하는것은 아니다.

 

범주형 컬럼, 수치형 컬럼을 미리 구분해두고 저장해둔다음에 반복문을 돌려두면 된다.

 


범주형, 수치형 column 나누는 방법

 

나누기 위해서 우선 pandas에서 지원하는 dtypes에 대해서 알아볼 필요가 있다.

 

dataFrame.dtypes 의 결과값의 타입은 Series형태이다. 즉 column이 하나인 dataFrame으로 나온다고 봐도 상관없다.

 

Series 로 나오는것을 확인 할 수 있다.

 

 

그러면 이제 data라는 이름의 dataFrame에는 어떤 column이 들어있는지 확인하면 아래와 같다.

 

 

총 2개의 object 타입이 있고 나머지는 int와 float로 구성되어있다.

int나 float형에도 범주형이 있을수 있으므로 describe 함수로 좀더 자세히 확인해보자

 

column의 이름에서도 알수는 있지만 각 수치 그자체로 의미를 가지므로 범주형이라 할 수 없다.

 

그러면 이제 범주형과 수치형을 직접 나눠서 저장하는 방법도 있지만 자동으로 해보는 코드를 살펴본다.

 

아래의 코드를 먼저 확인해보면 float, int, object인지 판별하는 간단한 코드이다.

 

코드 결과

위 사진을 보면 Object를 판별 하는 방법은 'O' 인지 확인하면 된다.

 

그럼 아래의 코드들로 컬럼들을 구분할 수 있다.

 

파이썬 특히 머신러닝 하면서 import 한 라이브러리를 뜯어보고 동작 과정을 살펴 보고싶을때가 있다.

 

또는 import를 할때 라이브러리 전체를 import하기에는 코드가 너무 무거워질까 두려워 특정 부분만 import를 할때도 사용된다!

 

그럴때 사용하는 팁을 설명해볼게!

 

 


1. VScode를 통해 파일 생성 ( 언어에 맞게 )

예시를 위해서 나는 python으로 진행을 해볼게

 

파일 -> 새 파일 -> 언어에 맞게 새로운 파일 생성

 

만약 vscode만 설치되어있고 파이썬이나 다른 필요한 파일이 설치 안되어있다면 설치하자

 

각 언어에 설치 필수 항목들은 구글링 하면 충분히 나와..

 


2. import하기

예시를 위해 sklearn을 import 해볼게

만약 위의 sklearn과 달리 정상적으로 import되지 않았다면 여러분의 언어의 기본 라이브러리에 패키지가 설치되어있지 않아서야!

 

그니깐 import 하는거는 당신의 컴퓨터에서 사용할 언어의 폴더에 있는 라이브러리 폴더에서 가져와 쓰겠다는거를 선언하는건데

 

설치를 안했으니 못하겠지!

 

설치하는 방법은 vscode 터미널을 열어서 

pip install 라이브러리 이름

을 통해 라이브러리 설치해주면 되는거야!

 


3. import 한 패키지의 구성 폴더에 들어가기

라이브러리를 우클릭 하면 저렇게 나오는데 정의로 이동을 눌러!

 

그러면 sklearn 라이브러리의 init 파일이 나올거야

 

init 파일은 github의 README와 비슷하다 생각하면 될거야 그냥 해당 라이브러리에 뭐가 있는지 설명을 해줘!

 

 


4. init 파일 확인하기

좀만 내리면 저렇게 빨간 글씨로 어떤것들이 들어있는지 확인할 수 있어 

 

그리고 위쪽의 경로가 있는 줄을 보면 클릭을 할수 있는데 해당 디렉터리 또는 폴더를 클릭하면 그 파일이 존재하는 디렉토리에 뭐가 있는지 보여줘!

 

빨간색 글씨와 위의 사진 오른쪽 상단의 스크롤 메뉴를 보면 구성요소가 같다는것을 확인 할 수 있을거야

 

여기서 특정 폴더에 있는 기능들만 예를들어 sklearn.preprocessing의 기능만 확인하고 싶으면 sklearn -> preprocessing을 들 들어가서 init 파일을 확인해보는거지

 


5. 특정 디렉토리 init 확인하기

 

이렇게 위와같은 형태의 init파일을 찾을수 있어 여기서 여러분이 원하는것을 import하면 됩니다.

 


추가. 사용하는 함수의 구조 뜯어보기

위에서 더 나아가 함수의 구조까지 확인해보고 싶으면 5번의 init파일에서 당신이 사용할 함수가 어디서 import되는지 확인을 해야해 

 

5번 사진에서는 맨 윗쭐의 from ._ ~~~~ 이부분이지 이후 그 경로를 따라 당신이 사용할 함수파일을 열어보는거야!

 

그래서 똑같이 파일을 열어 뜯어 보는거지

 

 

또는! 그냥 from ._ ~~~~ 에서 우클릭후 정의로 이동하면 쉽게 이동할 수 있어

 

+ Recent posts