문자열

1. 문자열의 문제점

문자열을 비교하는 함수 strcmp(), 문자열을 복사하는 함수 strcpy() 모드 O(n)의 동작이다.

ⓐ 문자열 클래스

- 문자열 객체를 넘길 때는 값이 아닌 참조로 넘길 것

- 일찍 코드를 프로파일링하고 자주해서 문자열 클래스가 프레임 저하의 주원인이 되지 않게 살필 것

ⓑ 고유 식별자

- 식별자에 문자을 쓰는 것은 자연스러운 일이지만 strcmp()를 사용하는 것은 너무 느리다.

- 그래서 해시 문자열ID를 사용한다.

- 문자열을 정수 처럼 비교하기 때문에 빠르다.

- 해시 테이블에 저장하면 해시 코드를 통해 언제든 문자열을 가져올 수 있다.


* 대부분의 게임 엔진들을 런타임에 해시 값을 계산하지 않는다. ( 해시 함수를 통가하는 속도는 느리기 때문)

* 그렇기 때문에 문자열을 해싱하는 것을 인턴이라고 하는데 인턴을 미리 해두고 저장해서 해시함수를 통과하지 않고 사용하는 것이 좋다.

    

2. 현지화

ⓐ 유니코드

- 영어와 다른 문자를 사용하기 위한 문자 셋 시스템

ⓑ UTF-8

- 각 문자 코드는 8비트지만 어떤 문자들은 1바이트 보다 큰 공간을 차지하기도 한다. ( 멀티 바이트 셋 MBCS)

- 장점은 ANSI 인코딩과 호환이 된다. 멀티바이트의 문자의 첫 바이트는 항상 가장 높은 비트가 1이기 때문이다.

ⓒ UTF-16

- 표준은 더 간단하지만 좀 더 비싼 방법을 사용한다.

- 와이드 캐릭터 셋(WCS) 이라고 부르는데 16비트를 사용한다.


ⓓ 윈도우 환경에서 유니코드

- UTF-16 하나의 데이터 타임은 wchat_t 이고 UTF-8이나 ANSI 문자열을 나타내는 것은 char이다.

- 캐릭터 셋에 무관하게 짤 수 있는 방법은 TCHAR을 사용하는 것이다.

( typedef를 사용해서 ANSI 모드에선 char이 되고 UTF-16(WCS)으로 컴파일 할 때는 wchar_t가 된다.)

- 모든 윈도우 API에서 접미사나 접두사로 w, wcs, W 가 붙은 것들은 와이드 문자를 의미한다.

- t, tcs, T 가 붙은 것들은 현재 사용하는 문자 타입을 의미한다.

ex) std::string은 STL의 ANSI문자열 클래스이고, std::wstring은 와이드 캐릭터 클래스다.

- 윈도우에서 WCS, MBCS 문자열 다루는 함수는 버전이 따로 존재한다.


ⓓ 콘솔에서의 유니코드

- 프로젝트에서 어떤 인코딩을 사용할지 가능한 한 빨리 정한 다음, 그 후 일관되게 사용하기만 한다면 실제로 어떤 것을 사용하는지는 별로 중요하지 않다.

posted by 알쿠미

STL의 장점

 * STL은 풍부한 기능들을 지원한다.

 * 다양한 플랫폼에서 쓸 수 있는 대체적으로 안정적인 구현들이 존재한다.

 * 거의 모든 C++ 컴파일러는 STL을 표준으로서 지원한다.


STL의 단점

 * STL은 처음에 배우기기 힘들다.

 * 구체적인 목적을 위한 자료구조에 비해 성능이 떨어진다.

 * 대부분의 경우 자체 제작한 자료 구조보다 메모리를 더 많이 먹는다.

 * 동적 메모리 할당을 많이 사용하다.

 * 여러 플랫폼을 지원하는 엔진에서 사용하기 힘들다.


1. 동적 배열과 메모리 할당

  동적 할당은 느리기 때문에 동적 배열은 개발기간에 사용하고 버퍼의 크기를 알게 되면 고정크기 배열로 바꾸어 사용하는 것도 좋다.


2. 연결 리스트

  ⓐ 고려사항

    - 빈 리스트에 첫 요소를 추가하는 경우

    - 현재의 머로 요소 앞에 새 요소를 추가하는 경우

    - 현재의 꼬리 요소 뒤에 새 요소를 추가하는 경우

    - 중간에 요소를 추가하는 경우


  ⓑ 링크 자료구조

template< typename ELEMENT >

struct Link

{

Link<ELEMENT>* m_pPrev;

Link<ELEMENT>* m_pNext;

Link<ELEMENT>* m_pElem;

};


  ⓒ 돌출 리스트 (extrusive list)

    - Link 자료구조와 요소 자료 구조가 완전 별개인 연결리스트 이다.

    - 연결리스트에 요소가 추가될 때면 링크를 하나 새로 할당하고 요소에 대한 포인터 및 다음과 이전 링크에 대한 포인터를 맞게 설정한다.

    - 장점은 한 요소가 동시에 여러 연결리스트에서 포함할 수 있다는 것이다.

    - 단점은 링크 객체를 동적할당 해야한다는 것이다. 링크는 언제나 크기가 같기 때문에 풀할당자를 사용하는 경우가 많다.


  ⓓ 함몰 리스트 (Instrusive list)

    - Link 자료구조가 요소의 자료구조 안에 포함되는 연결 리스트다.

    - 링크 객체를 동적 할당하지 않아도 되는 이점이 있다.

    - Link 안에는 요소를 가리키는 포인터가 있어야 하지만, 상속을 사용하면 없어도 된다.


  ⓓ 머리와 꼬리 포인터 : 순환리스트

    - Head 포인터와 tail 포인터를 가지고 있으면 삭제나 추가 시 복잡해진다.

    - 그러므로 이 두개의 포인터를 m_root포인터로 합치면 순환하는 리스트가 된다.


  ⓔ 단순 연결 리스트

    - 이전을 가리키는 포인터가 없기 때문에 제거 동작은 O(n)이고 이중연결리스트에선 O(1)이다.

    - 리스트의 Head나 Tail에서 제거 동작이 발생하는 경우에는 단순 연결로 메모리를 절약할 수 있다.


3. 사전과 해시 테이블

사전은 키-값 쌍의 테이블이다. 

사전은 대부분 이진트리나 해시테이블을 사용하는다.

이진트리의 검색 연산은 O(logn)이다.

해시 테이블은 키를 정수형태로 변환하고 모듈로(modulo) 연산으로 검색하기 때문에 O(1)이다.


  ⓐ 충돌 : 개방형 해시 테이블과 폐쇄형 해시 테이블

    - 개방형 해시 테이블은 충돌을 해결하기 위해 단순하게 인덱스 하나에 여러개의 키-값 쌍을 저장하는 방식을 쓴다.

    - 구현하기 쉽고 저장할 수 있는 키-값 쌍의 수에도 제약이 없지만 새로운 키-값 쌍을 추가할 때 동적 메모리 할당이 발생한다.

  

    - 폐쇄형 해시 테이블은 충돌이 발생하면 빈 슬롯을 찾을 때 까지 탐지하는 과정을 반복한다.

    - 구현이 까다롭고 키-값 쌍의 수에 제한이 있다. 하지만 정해진 양만큼의 메모리를 사용하고 동적 메모리 할당이 필요 없다.


  ⓑ 해시 값 계산

    - 해시 값 계산(Hashing)은 임의의 데이터 타입인 키 값을 정수로 부꾼 후 이 정수와 테이블 크기를 모듈로 연산해 테이블에 대한 인덱스를 얻는 과정이다.

    h = H(k)    i = h mod N

    - 해시 테이블의효율성은 해시 함수 품질에 달려 있다. 좋은 해시 함수란 모든 키값을 테이블 전체에 고르게 배분하는 함수이고, 충돌을 최소화 한다.

    - 결정적이어야 한다. 입력이 같으면 그 출력도 같아야 한다.


  ⓒ 폐쇄형 해시 테이블 구현

    - 폐쇄형 해시 테이블 상식에서는 키-값 쌍을 각 테이블 항목마다 하나씩 연결 리스트에 저장하는것이 아니라 테이블 안에 직접 저장한다.

    - 이렇게 되면 해시 테이블을 사용할 메모리 양을 미리 정확히 알 수 있다.

    - 충돌이 일어나서 탐자 과정을 할때 가장 간단한 방식은 선형 탐지다. 충돌이 일어난 곳 부터 하나씩 탐지하는 것이다.

    ( 요소들이 뭉치게 된다.)

    - 그래서 이차 탐지 알고리즘을 쓰는데 하나씩이 아니라 i의 제곱씩 늘려가며 탐색한다.

    - 폐쇄형 해시는 크기를 소수로 하는 것이 좋다. 이차 탐색을 했을 때 고르게 배치 할 수 있다.




  

'게임 엔진 아키텍처 > 5장 엔진 지원 시스템' 카테고리의 다른 글

문자열  (0) 2016.05.28
메모리 관리  (0) 2016.05.27
하부 시스템 시작과 종료  (0) 2016.05.27
posted by 알쿠미

소프트 웨어의 성능은 어떤 알고리즘을 썼느냐도 중요하지만 어떻게 RAM을 잘 활용했는지도 영향을 받는다.

메모리가 성능에 영향을 끼치는 형태는 다음 두가지가 있다.


* 동적 메모리 할당은 느리다. 동적 메모리할당을 피하거나, 할당비용을 줄일 수 있는 메모리 할당자를 직접 만들어야 한다.


* 메모리를 효율적으로 배치해되 있을수록 속도가 빠르다.


1. 동적 메모리 할당 최적화

 동적 메모리 할당이 느린 이유

  * 힙 할당자는 범용 목적이기 때문에 이렇게 하면 관리하는 부가적인 비용이 들기 때문이다.

  * malloc(), free()를 호출할 때 대부분 운영체제 에서는 먼저 유저 모드에서 커널 모드로 컨텍스트 전환을 하고, 필요한 동작을 수행한 후 다시 프로그램으로 컨텍스트 전환을 해야한다.


 사용자 제작 할당자가 빠른 점

  * 미리 할당된 블록을 이용할 수 있다. 즉, 유저모드에서만 처리하기 때문에 컨텍스트 스위칭이 일어나지 않는다.

  * 사용 패턴을 예측할 수 있기 때문에 범용 힙 할당자에 비해 훨씬 효율적으로 동작한다.


2. 커스텀 할당자의 종류

  ⓐ 스택 할당자 - 데이터를 쌓아가며 할당하고 그러므로 해제할땐 순서대로 해제해야 한다. 해제 시 해제하고 할당 된만큼 꼭대기의 주소를 내려줘야 한다.


  ⓑ 풀 할당자 - 개별 원소들의 크기에 정확히 배수가 되는 큰 메모리 블록을 할당하는 것이다. 처음에는 모든 풀의 원소들이 사용 가능 리스트에 들어가 있다가 할당 요청이 오면 제일 처음 원소를 가져와서 리턴한다.


  ⓒ 정렬된 할당자 - 실제 요청된 것보다 조금 큰 메모리를 할당하고 블록의 주소를 살짝 위로 조정해서 정렬을 맞춘 다음 조정된 주소를 리턴한다.


  ⓓ 단일 프레임 할당자 - 메모리를 해제하지 않고 한 프레임 마다 메모리 블록의 가장 아래로 꼭대기 포인터를 초기화 한다. 프레임 진행 중 할당이 일어나면 블록의 위방향으로 진행한다.


3. 메모리 단편화

 - 메모리가 충분히 있음에도 연속되 있지 않아서 할당에 실패한다.

해결 방안

 ⓐ 스택할당자와 풀할당자를 사용한다.

   - 이 두 할당자는 메모리 단편화를 겪지 않는다.

 ⓑ 조각 모음과 재배치

   - 할당된 메모리들을 메모리 구멍을을 메워가며 앞으로 당긴다.

   - 하지만 메모리를 가리키고 있는 포인터가 있을 경우 문제가 생길 수 있으니 메모리들을 옮기면서 이 과정 또한 다시 재배치 해주어야 한다.

    (스마트 포인터가 스스로 재배치를 하게 하거나 핸들을 사용한다.)

   - 조각 모음이 한 프레임에 모든 블록을 옮긴다면 속도가 굉장히 느릴 것이다 그러므로 한프레임당 8개나 16개 정도 갯수를 조정하는 것이 좋다.


4. 캐시 일관성

 시스템이 메인 RAM에 접근하는 일은 시작부터 끝까지 항상 수천 프로세스 주기가 걸리기 때문에 언제나 느리다.

 대게 수십주기, 어떤 경우는 한주기만에 끝나기도 하는 CPU 레지스터 접근과 비교하면 엄청난 차이다.

 메인 RAM에서 읽고 쓰는 평균적인 비용을 줄이기 위해 오늘날의 프로세서는 고성능 메모리 캐시를 이용한다.

 캐시는 특수한 형태의 메모리로 CPI가 읽고 쓰는 속도가 메인 RAM 보다 훨씬 빠르다.

 메모리 캐싱의 기본적 원리는 메인 RAM의 영역을 처음 읽을 때, 작은 메모리 조각을 고성능 캐시에 읽어 들이는 것이다.

 이 메모리 조각을 캐시라인이라고 하고 보통 8바이트에서 512바이트 사이의 값이다.

 요청한 데이터가 캐시에 있을 땐 캐시에서 바로 읽어 들이고 그렇지 않으면 메인 RAM에 접근한다. 이 경우를 캐시 미스라고 한다.

 캐시 미스가 발생하면 캐시라인을 다시 읽어 들일 때 까지 프로그램은 기다려야 한다.

 RAM에 데이터를 쓰거나 읽어야 하기 때문에 캐시 미스를 완전히 피할 수는 없다.


  ⓐ 명령어 캐시(instruction I 캐시) 데이터(Data D 캐시) 캐시

    - 명령어 캐시는 실행 파일의 명령어 코드가 실행되기 전에 미리 불러오는데 쓰인다.

    - 데이터 캐시는 데이터를 메인 RAM에 읽고 쓰는 속도를 빠르게 하는 데 쓰인다.


  ⓑ 데이터 캐시 미스를 피하는 가장 좋은 방법은 데이터를 잘개 쪼개어 연속적인 블록으로 배치하고 순서대로 접근하는 것이다.


  ⓒ 명령어 캐시 미스를 피하는 방법은 코드의 메모리에서의 모양은 데이터와 링커에 달려 있다 그래서 다음위 규칙을 이용하면 좋다.

    - 함수 하나를 나타내는 기계어 코드는 항상 메모리에서 연속적이다.

      (인라인 함수는 예외)

    - 번역 단위의 소스코드에 나오는 순서대로 함수들이 메모리에 배열된다.

    - 같은 번역 단위 안에 있는 함수들은 거의 대부분 메모리에서 연속적으로 놓인다.

      ( 링커는 여러 번역 단위를 컴파일한 결과물들을 서로 섞어서 놓지 않는다.)

  

  * 성능이 중요한 코드는 가능한 한 기계어 명령어 수가 적어야 한다.

  * 성능에 결정적인 영향을 끼치는 코드에서는 함수 호출을 피애햐 한다.

  * 함수를 반드시 호출해야 하는 경우에는 가능한 현재 함수와 가까운 곳에 배치해야 한다. 다른 번역 단위에 있는 함수를 호출하면 안된다.( 거리를 보장할 수 없기 때문)

  * 인라인 함수를 사용할 때는 조심해야 한다. 인라인 함수는 코드의 크기를 크게 할하기 때문에 결정적 영향을 끼치는 코드가 캐시에 한번에 다 못 들어 가는 경우가 생길 수 있다.



posted by 알쿠미

시작 하는 순서는 하부시스템의 연관성에 따라 묵시적으로 결정된다.

종료하는 순서는 대개 반대로 종료된다.


1. C++ 정적 초기화 순서

  * C++ 에선 정적 객체와 전역 객체가 초기화 되는 순서가 정해져 있지 않다.

    (만약 초기화 순서를 지정할 수 있었다면 싱글턴 클래스 인스턴스를 전역으로 정의해 동적 메모리 할당을 하지 않아도 됐을 것이다.)


2. 주문형 생성

 활용할 수 있는 C++의 트릭이 하나 있는데, 함수 안에서 선언된 정적 변수는 main() 실행 전이 아니라 해당 함수가 처음으로 호출될 때 생성된다는 점이다.


class RenderManager

{

public :

static RenderManager& get()

{

static RenderManager sSingleton;

return sSingleton;

}

}


그리고 동적할당을 이용한 방법

static RenderManager& get()

{

static RenderManager* gpSingleton = NULL;

if ( gpSingleton == NULL)

{

gpSingleton = new RenderManager;

}

ASSERT( gpSingleton );

return *gpSingleton;

}


이 방법을 이용한 간단한 방법은 생성자와 소멸자에선 아무것도 하지 않으며

생성하는 함수와 종료하는 함수를 따로 두어 전역으로 선언한 뒤 Main에서 시작할때 순서대로 실행하는 것이다. 종료도 마찬가지로 반대로 실행한다.


장점

  * 방식이 단순하고 구현하기 쉽다.

  * 명확하다. 코드를 보기만 하면 바로 시작 순서를 알아낼 수 있다.

  * 디버깅하고 유지하기 쉽다. 제때 시작이 안되거나 너무 일찍 시작되는 것이 있으면 코드 한 줄만 이동하면 된다.

'게임 엔진 아키텍처 > 5장 엔진 지원 시스템' 카테고리의 다른 글

문자열  (0) 2016.05.28
자체 구현 컨테이너 클래스 만들기  (0) 2016.05.28
메모리 관리  (0) 2016.05.27
posted by 알쿠미

1. 오일러 - 짐벌락이 있다.

* 짐벌락 - 축들은 종속적이라 회전 하다 축이 같게되면 회전하지 못하는 짐벌락이 발생한다.


2. 행렬

 회전 변환은 가독성이 좋지 않고 저장공간을 많이 차지한다.


3. 사원수

 회전 변환을 결합할 수 있다는 것과 사원수 곱을 통해 점과 벡터를 회전 변환할 수 있다. LERP, SLERP를 이용해서 회전 변환을 보간할 수 있다.


4. 기타 수학 개념

 평면 방정식 = Ax + By + Cz + D = 0

 여기서 A, B, C는 법선 벡터가 된다. 그리고 A, B, C가 정규화 되있다면 D는 평면과 원점사이의 거리가 된다. d 가 양수이면 법선 벡터가 원점을 향하고 음수이면 법선 벡트는 원점에서 멀어지는 방향이다.

 즉 평면은 법선벡터와 원점과의 거리만 있으면 표현할 수 있다.


5. SIMD single instruction multiple data / SSE Streaming SIMD Extension 

  CPU가 기계어 하나로 병렬 연산 하는 것으로 게임 엔진의 수학 라이브러리에서 널리 쓰인다. 자주 쓰이는 연산을 엄청 빠르게 할 수 있다.


6. 난수

 * 메르센 트위스터

  ⓐ 2^19937 - 1 이라는 긴 주기를 갖게 고안됬다.

  ⓑ 매우 높은 동일 분포 차원을 갖는다. 즉 생성된 난수들 사이에 연관성이 거의 없다.

  ⓒ 여러가지 무작위성 테스트를 통과했다

  ⓓ 빠르다.

'게임 엔진 아키텍처 > 4장 게임에 사용되는 수학' 카테고리의 다른 글

사원수  (0) 2016.05.26
행렬  (0) 2016.05.26
벡터  (0) 2016.05.25
posted by 알쿠미

회전변환에 행렬이 최선이 아닌 이유

  * 회전 변환에서는 피치, 요, 롤이 세가지 변수밖에 없는데, 이 것을 표현하기 위해 9개의 부동소수가 필요하다. 낭비다.

  * 벡터를 회전 변환하려면 행렬 곱셈을 해야하는데, 여기에는 3번의 내적 계산 즉 9번의 곱셈과 6번의 덧셈이 필요하다.

  * 두 회전 변환 사이의 주간 회전 변환을 구해야 할 때 행렬로는 중간 지점을 찾기가 힘들다.


1. 3D 회전 변환을 나타내는 단위 길이 사원수

 q = [qv qs] [asin(θ/2) cos(θ/2)]


2. 사원수 연산

 두 사원수가 있을 때 곱셈은 두 사원수의 회전을 합친 것이다.


 pq = [ (psqv + qspv + pv X qv) (psqs - pv*qv) ]


3. 결레와 역

 사원수의 역은 q^(-1) 이라고 쓰고 그 의미는 원래의 값과 곱하면 스칼라 값 1이 되는 사원수를 뜻한다.

 qq^(-1) = 0i + 0j + 0k +1

 켤레 사원수 = [ -qv qs ]

 사원수의 역 = 켤레 사원수 / 크기 절대값의 제곱


 사원수 곱셈에서

 (qp) 의 켤레 사원수는 반대 순서로 둘의 켤레사원수를 곱한것이며 사원수의 역도 반대 순서로 둘의 역을 곱한 것이다.


4. 사원수의 벡터를 이용한 회전

 벡터 v를 사원수로 바꿔쓰면 v = [ vx vy vz 0 ] 이다.

 이것을 회전한 결과는 v' = qvq^(-1) 단위길이라면 역대신 켤레가 들어가도 된다.

 

 회전을 여러번 할때의 사원수 계산은

 q3q2q1vq1^(-1)q2^(-1)q3^(-1)

 

5. 사원수의 계산으로 보간이 가능하다

하지만 회전에서 보간을 하려면 완전하지 선형보간 LERP를 써야하는데 구면 선형 보간 SLERP를 쓰기엔 너무 느리다.


 


'게임 엔진 아키텍처 > 4장 게임에 사용되는 수학' 카테고리의 다른 글

회전 변환 표현간 비교 / 기타 수학 개념  (0) 2016.05.26
행렬  (0) 2016.05.26
벡터  (0) 2016.05.25
posted by 알쿠미

행렬

행렬은 교환 법칙이 성립 하지 않는다. AB != AB


1. 단위 행렬 - 대각선 성분만 1이고 나머지는 0인 벡터


2. 역행렬 - AA' = A'A = 단위행렬

(모든 행렬에 역행렬이 있는 것은 아니다.)

하지만 모든 아핀행렬(회전, 평행이동,스케일, 층밀림 변환으로만 이뤄진 행렬)은 역행렬이 존재한다.

가우스 소거법이나 LU분해법을 이용하면 역행렬이 존재하는 경우에는 역행렬을 찾을 수있다.


3. 전치 행렬

전치 행렬은 대각선기준으로 행렬을 뒤집어 놓은 것이다. 즉, 원래 행렬의 행은 열이되고 열은 행이 되는 것이다.

 ex) 순수한 회전변환 행렬의 역행렬 = 전치행렬이라 자주 이용된다.

(ABC)t = CtBtAt


4. 동차 좌표

3차원 행렬로 3차원 회전은 표현할 수 있지만 평행이동은 표현할 수 없기에 n+1 행렬로 표현한다.


5. 방향 벡터 변환

동차 좌표에서 점의 w값은 1로 해두고 선의 w값은 0으로 해둔다.

즉, 3차원의 순수 방향 벡트는 4차원 동차 좌표에서는 무한대에 있는 점과 같은 역할을 한다.


6. 기본단위 변환 행렬

  ⓐ 평행 이동 _41, _42, _43

  ⓑ 회전 변환

    - x 축 회전은 _22 = cos, _32 = sin, _23 = -sin, _33 = cos

    - y 축 회전은 _11 = cos, _31 = -sin, _13 = sin, _33 = cos

    - z 측 회전은 _11 = cos, _21 = sin, _12 = -sin, _22 = cos

   * 회전 행렬의 3*3 부분의 1은 항상 회전하는 축에 해당하는 성분에 곱해지고 사인과 코사인은 그렇지 않다.

  * y축에 대한 행렬은 다른 행렬에 비교할 때 전치 행렬 같은 모양을 하고 있다.

  * 회전 변환의 역은 전치 행렬과 같다. cos(-θ) = cos(θ) 이고 sin(-θ) = -sin(θ)이기 따라서 각도의 부호를 바꾸면 사인 값은 위치를 서로 바꾼 것과 같고 코사인 값은 변하지 않는다.

  ⓒ 스케일 변환 _11, _22, _33



'게임 엔진 아키텍처 > 4장 게임에 사용되는 수학' 카테고리의 다른 글

회전 변환 표현간 비교 / 기타 수학 개념  (0) 2016.05.26
사원수  (0) 2016.05.26
벡터  (0) 2016.05.25
posted by 알쿠미

벡터

1. 벡터 연산


방향 + 방향 = 방향

방향 - 방향 = 방향

점 + 방향 = 점

점 - 점 =  방향

점 + 점 = 성립되지 않음

2. 벡터 크기 = 피타고라스의 정리를 이용하면 구할 수 있다.


3. 정규화와 단위 벡터

벡터 / 벡터 크기


4. 법선 벡터 ( 어떤 평면에 수직인 벡터를 법선 벡터라고 한다.)

 - 어떤 면과 그 면에 비치는 빛의 상대적인 각도를 계산하는데 사용된다.


5. 벡터 내적과 투영

 * 벡터 내적( Dot Product , 스칼라 곱)

 * 벡터 외적( Cross Product, 벡터 곱)


a*b = abx + aby + abz = d 스칼라 값 = IaIIbI cos()

a*b = b*a , sa*b = s(a*b)


6. 벡터 투영

벡터가 단위 벡터 일때, 어떤 벡터 a와 u의 내적은 a를 u방향의 무한히 긴 선에 투영한 크기가 된다.


7 내적으로 알아낼 수 있는것

 내적을 이용하면 두 벡터가 평행인지, 수직인지를 알 수 있고, 두 벡터가 가리키는 방향이 비슷한지 아니면 반대인지를 알 수 있다.

 - 평행이면서 같은 방향 벡터 (a * b) = IaIIbI = ab 이면 평행이면서 같은 방향벡터

 - 평행이면서 같은 방향 벡터 (a * b) = IaIIbI = -ab 이면 평행이면서 반대 방향벡터

 - 수직인 벡터는 (a * b) = 0 (두 벡터가 이루는 각이 90도)

 - 같은 방향인 벡터 (a * b) > 0 ( 두 벡터가 이루는 각이 90도 미만)

 - 다른 방향 벡터 ( a * b) < 0 ( 두 벡터가 이루는 각이 90도 보다 크다)


기타 내적의 활용

 - 평면과 점의 거리

 평면에 임의점 q를 잡고 거리를 알고자 하는 점과 벡터를 만들어서 평면의 법선 벡터에 투영하면 된다.


8. 벡터 외적

 두 벡터 외적의 결과는 두 개의 벡터와 수직인 벡터이다. 3차원에서가능

aXb = [(aybz - azby), (azbx - axbz), (axby- aybx)]


외적 벡터의 크기는 두 벡터의 크기 곱하기 두벡터 각도의 sin값이다.


외적 벡터의 방향은 손 좌표계를 따른다.


9. 외적의 활용

 두 벡터의 수직인 벡터, 평면에 수직인 벡터, 토크 N = r X F다.


10. 점과 벡터의 선형 보간

 L = LERP(A, B, k) = (1-k)A + kB

두개의 알려진 지점 사이의 중간 값을 계산 하는 수리연산


'게임 엔진 아키텍처 > 4장 게임에 사용되는 수학' 카테고리의 다른 글

회전 변환 표현간 비교 / 기타 수학 개념  (0) 2016.05.26
사원수  (0) 2016.05.26
행렬  (0) 2016.05.26
posted by 알쿠미

1. 에러 리턴코드 맨 처음 에러를 감지한 함수에서 특정한 에러 코드를 리턴하게 하는 방법


2. 예외 처리

에러처리 함수를 사용하려면 콜스택을 거슬러 내려가서 에러처리 함수까지 가야할수도 있다.

SEH(Structured Exception Handling)  위와 같은 일을 하지 않고 에러를 전달한다.

에러가 발생하면 예외 객차라는 자료 구조에 정보를 넣고 콜 스택을 자동으로 펼쳐 try - catch 블록을 찾았을 경우 예외 객체를 처리할 수있는 catch를 찾고 스택이 펼쳐지면서 자동 변수들의 파괴자는 자동으로 호출된다.


하지만 SEH는 프로그램에 많은 부담을 주므로 게임에선 사용하기 힘들다.


3. Assert

GPG에 비슷한 내용이 있다.

'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

객체 메모리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
수의 표현  (0) 2016.05.24
posted by 알쿠미

클래스 정적멤버


static 키워드란?

* 파일 범위에서 사용하면 변수가 다른 cpp에서 보이지 않고 사용되지 못하게 하고 오직 그 cpp에서만 보이게 된다.


* 함수 범위에서 사용하면 변수가 전역 변수이고 자동 변수가 아니지만 그 함수안에서만 보이게 한다.


* 구조체와 클래스 선언에서 사용하면 변수가 보통 멤버 변수가 아니라 전역 변수처럼 동작한다.


클래스에서 쓰인 static 키워드는 변수의 보이는 범위를 지정하지는 않는다.

대신 일반적인 멤버 변수처럼 각 인스턴스마다 존재할지, 아니면 클래스에만 존재해서 전역 변수 처럼 쓰일지 지정한다.


보이는 범위는 public, protected, private 키워드를 통해 결정된다.

클래스 전역변수는 클래스 네임스페이스에 포함되기 때문에 클래스 이름을 함께 사용해야 한다.

클래스 정적 변수를 위한 메모리는 반드시 cpp파일에 정의돼야 한다.


F32 Foo::sClassStatic = -1.0f; // 이런식으로 메모리 할당



1. 메모리 상의 객체 구조

struct Foo

{

U32 mUnsignedvalue;

F32 mFloatValue;

bool mSignedValue;

};


 +0x0

 mUnsignedvalue

 +0x4

 mFloatValue

 +0x8

 mBlooleanValue


2. 메모리 정렬과 패킹

1. 바이트 정렬 객체는 어떤 메모리 주소에도 올 수 있다.

2. 2바이트 정렬 객체는 짝수인 주소에만 올 수 있다.

3. 4바이트 정렬 객체는 4의 배수가 되는 주소에만 올 수 있다.

4. 16바이트 정렬 객체는 16의 배수가 되는 주소에만 올 수 있다.


* 메모리 정렬이 중요한 이유는 메모리 정렬이 제대로 지켜진 데이터 블록만 읽고 쓸 수 있기 때문이다.


* 구조체 전체를 놓고 봤을 때는 멤버의 정렬 조건중 가장 큰 것이 그 구조체의 정렬 조건이 된다. 남는 메모리는 컴파일러가 패딩으로 채운다.


3. C++ 클래스의 메모리 구조

+0x0

+sizeof(A)

메모리 구조 관점에서 보면 C++클래스는 상속과 가상 함수 때문에 메모리가 더 붙는다. 각 클래스 사이에도 메모리 정렬조건 때문에 패딩이 들어갈 수 있다.


가상함수가 있다면 가상함수테이블을 가리키는 vptr포인터 4바이트가 추가되고

이 vtable은 이 클래스가 선언하거나 상속하는 모든 가상 함수를 가리키는 포인터를 갖고 있다.





'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

에러감지와 처리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
수의 표현  (0) 2016.05.24
posted by 알쿠미

1. 실행파일 이미지

C/C++ 프로그램은 링커가 실행 파일을 만들면서 완성된다.


1. 텍스트 세그먼트

  - 코드 세그먼트라고 불린다. 프로그램에서 정의한 모든 함수의 기계어를 담고 있다.


2. 데이터 세그먼트 

  - 초기 값을 가진 전역 및 정적 변수가 자리한다.

  - 이 형태대로 프로그램이 실행 될 때 메모리에 배치되고 값들은 지정한 대로 초기화 돼있다. 따라서 실행 파일이 메모리에 로드될 때 전역변수와 정적 변수는 바로 사용할 수있다.


3. BSS 세그먼트 (block started by symbol)

  - 초기화 되지 않은 모든 전역변수와 정적변수가 자리한다.

  - C/C++에서는 초기화 되지 않은 전역변수와 정적변수가 0으로 초기화 되는데 몇개가 될지 모르는 0의 값들을 무작정 들고 있기 보다는 링커는 얼마나 많은 마이트의 0이 있는지만 기록한다.

  - 운영체제는 실행 파일이 메모리에 로드돼 시작점이 불리기 직전에 이 값을 보고 공간을 할당하고 0으로 채운다.


4. 읽기 전용 데이터 세그먼트

  - 읽기 전용(constant)인 전역 데이터가 자리한다. 부동 소수인 상수나, const 키워드로 정의된 전역 인스턴스 등이 여기에 해당한다.

  - 정수 타입인 상수는 매니페스트 상수처럼 취급돼 사용되는 기계어 코드위치에 직접 삽입되는 경우가 대부분이다.


5. 프로그램 스택

  - 프로그램이 메모리에 로드될 때 운영체제는 프로그램 스택이라는 메모리 공간을 마련한다.

  - 스택 프레임에는 세가지 종류가 저장된다.

   ⓐ 리턴주소 - 해당 함수를 호출한 곳(호출한 함수)의 주소인데, 이 함수가 리턴한 후 다음 실행될 프로그램 위치다.

   ⓑ CPU 레지스터 - 함수가 호출되기 이전의 레지스터 값들을 스택에 저장함으로써 함수는 이전의 상태를 훼손할 염려없이 레지스터를 자유롭게 사용할 수 있다.

   ⓒ 지역 변수 - 호출된 함수의 지역 변수들은 스택에 자리한다.


6. 동적 할당 힙

 동적으로 할당된 메모리는 힙에 올라간다. 6장에서 좀더 자세하게 다룬다.

'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

에러감지와 처리  (0) 2016.05.24
객체 메모리  (0) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
수의 표현  (0) 2016.05.24
posted by 알쿠미

1. 번역단위 살펴보기

컴파일러는 한번에 한개의 번역 단위만 처리하기 때문에 외부의 전역 변수나 함수를 처리할 때면 그냥 이런 외부참조들이 있을 것이라고 가정하고 진행할 수 밖에 없다.

이 결과 나온 목적파일들을 링커가 미확정 상태인 참조가 진짜 어떤 것인지 알아내려고 시도하고 이 과정이 성공하면 모든 함수와 전역변수, 정적변수, 번역 단위 간 참조가 전부 포함된 실행 파일이 만들어진다.


2. 선언과 정의의 차이

 C/C++ 에서느 변수나 함수를 사용하기 전에 반드시 선언되고, 정의 돼야 한다.

* 선언 데이터 객체나 함수의 형태를 나타낸다.

 - 컴파일러에 '이름'과 데이터 타입 혹은 함수의 서명을 알려 준다.


* 정의 프로그램 안에 고유한 저장 공간을 나타낸다. 이 저장 공간 안에는 변수, 구조체 및 클래스의 인스턴스, 함수의 기계어 등이 들어갈 수 있다.


3. 선언과 정의의 중복 

선언은 여러번 할 수 있지만 정의는 한번 밖에 할 수 없다.

한 번역 단위 안에서 같은 정의가 여러번 나오면 컴파일러가 에러를 낸다.

똑같은 정의가 서로 다른 번역 단위에 있다면 컴파일러는 알아채지 못한다.

이때는 링커가 중복된 심볼 에러를 낸다.


4. 연결성

외부 연결성은 다른 번역 단위에서도 볼수 있고 사용할 수 있다.

내부 연결성은 정의가 위치한 번역 단위에서만 볼 수 있기때문에 그 번역 단위에서만 쓸 수 있다.


기본적으로 모든 정의 는 외부 연결성 이며, static키워드는 정의를 내부 연결성으로 부꿀 때 사용한다. .cpp마다 똑같은 정의가 있더라도 static이 붙어있으면 링커는 이것을 서로 다른 존재라호 인식한다.


인라인 함수는 기본적으로 내부 연결성이다.

'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

객체 메모리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
수의 표현  (0) 2016.05.24
C++ 개념과 사용법  (0) 2016.05.23
posted by 알쿠미

1바이트보다 큰 값들을 멀티바이트 값이라고 한다.

LSB( least significant byte) 최하위 바이트

MSB( most significant byte) 최상위 바이트

* 리틀 엔디언

  - LSB가 MSB보다 낮은 메모리 주소에 저장되는 방법

ex) 0xABCD1234 라면 0x34, 0x12, 0xCD, 0xAB로 순으로 저장됨

* 빅 엔디언

  - MSB가 LSB보다 낮은 메모리 주소에 저장되는 방법

ex) 0xABCD1234 라면 0xAB, 0xCD, 0x12, 0x34로 순으로 저장됨


엔디언 문제를 해결하는 방법

1. 모든 데이터 파일을 텍스트 형태로 저장하고 멀티바이트 숫자는 한 숫자가 한 바이트가 되게 십진수 형태로 저장한다.

2. 툴에서 디스크에 저장하기 직전에 엔디언을 바꾸게 하는 방법
PC가 게임을 돌릴 콘솔과 엔디언이 달라도 무조건 콘솔의 엔디언을 따르게 하는 방법


엔디언을 바꾸는 함수

inline U16 swapU16(U16 value)

{

return ((value & 0x00FF << 8) | ((value & 0xFF00) >> 8);

}


inline U32 swapU32 ( U32 value)

{

return    ((value & 0x000000FF) << 24)

((value & 0x0000FF00) << 8)

((value & 0x00FF0000) >> 8)

((value & 0xFF000000) >> 24)

}



'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

객체 메모리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
수의 표현  (0) 2016.05.24
C++ 개념과 사용법  (0) 2016.05.23
posted by 알쿠미

수의 표현

1. 수 표현

  ⓐ 수의 기초

    - 2진 법은 0b를 앞에 붙여 사용하기도 한다. 오른쪽 부터 왼쪽으로 갈수록 1씩 증가하는 2의 제곱이다.

  ⓑ 16진법은 0x로 시작하며 컴퓨터가 데이터를 저장 할 때는 한 바이트(8비트)단위로 저장하므로, 16진수 하나가 정확히 4비트를 표현할 수 있기때문에 16진수 2개로 한바이트를 표현할 수 있다.

ex) 0xFF = 0b11111111 = 255;

     0xB052 = 45,138;


2. 부호 있는 정수와 부호 없는 정수

  ⓐ 부호 없는 32비트 정수를 나타내려면  0x00000000부터 0xFFFFFFFF(4,294,967,295)까지가 된다.


  ⓑ 부호 있는 32비트 정수를 나타내려면 2의 보수를 사용한다.

    - 0xFFFFFFFF이 -1을 나타내고 음수는 여기서 하나씩 감소하며 나타낸다. 최상위 비트가 1인 수는 음수다.

    - 0x00000000부터 0x7FFFFFFF까지 양의 정수이고 0x80000000부터 0xFFFFFFFF까지 음의 정수다.


2의 보수란 어떤 수를 커다란 2의 제곱수에서 빼서 얻은 이진수이다. 


2. 고정소수점 표현법

  ⓐ 정수부를 표현할 비트와 소수부를 표현할 비트의 수를 임의로 고정한다.

    - 정수부는 1씩 증가하는 2의 제곱을 나타내고 소수부는 1씩 증가하는 2의 제곱의 역을 나타낸다.

(큰수를 표현하기에 무리가 있다. 그래서 부동소수점을 사용한다.)


3. 부동 소수점 표현법

  ⓐ 정소와 소수를 합친수인 가수와 가수 안에서 어디에 소수점이 위치할지를 나타내는 지수, 그리고 부호비트 이렇게 세부분으로 나뉜다.

  ⓑ 표준은 IEEE-754 이고 가장 높은 비트를 부호비트로 사용하고 그 다음 8비트는 지수로, 나머지 23비트를 가수로 사용할 것을 명시한다.

  * v = s * 2^(e-127) * (1+m); 


3. 절댓값과 정확도 간의 균형

  ⓐ 부동소수의 정확도는 절댓값이 작을수록 높아진다. ( 한정도니 비트로 표현된 가수를 정수부와 소수부가 나눠써야하기 때문에.) - 유효자릿수


  ⓑ 머신 엡실론은 2^(-23) 이고 대력 1.192* 10^(-7) 이다. 이보다 작은 값은 표현할 수 없다.


4. 기본적인 데이터 타입

  ⓐ char 보통 8비트 ASCII나 UTF-8 부호를 전부 담을 수 있을 정도의 크기

  ⓑ int, short, long - int 는 대상 플랫폼에서 제일 효율적으로 처리할 수 있는 크기의 부호있는 정수 타입이다.

(short는 int보다 작은 수를 담고 보통 16비트를 쓴다. long은 int와 같더나 크고 32나 64비트를 쓴다.)

  ⓓ float 오늘날 대부분 컴파일러에서 32비트 IEEE-754 부동소수를 사용한다.

  ⓔ double 2배 정밀도(64비트) IEEE-754 부동소수

  ⓕ bool 1비트로는 구현하지 않으며 8비트나 32비트 전부쓰는 컴파일러도 있다.


5. 컴파일러에 따라 크기가 다른 타입

 __int8, __int16, __int32, __int64 와 같은 타입이 있다.


6. SIMD (Single Instruction Multiple Data)

 여러개의 데이터를 병렬적으로 동시에 연산하는 기능 (콘솔에서 많이 사용)




'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

객체 메모리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
C++ 개념과 사용법  (0) 2016.05.23
posted by 알쿠미

1. 클래스의 특성

* 캡슐화 - 객체가 정해진 인터페이스만 공개하고 객체 내부의 상태와 상세한 구현 사항을 숨기는 것을 캡슐화라고 한다.


* 상속 - 어떤 클래스가 이미 존재하는 클래스를 확장하는 것을 상속한다고 말한다.

(상속은 두 클래스 사이에 is-a 관계를 말한다.)


* 다형성(폴리모피즘)

 - 프로그래밍 언어에서 서로 다른 타입의 객체들을 하나의 공통 인터페이스로 다룰 수 있는 기능이다.


* 합성과 집합

 - 상속만 사용할 것이 아니라 클래스 간의 관계를 has-a, uses-a 관계를 형성해서 복잡한 상속 관계를 만들지 않고 문제를 해결하는 것이 좋다.


* 디자인 패턴

(GOF를 보고 정리하자..)


2. 코딩규칙

* 인터페이스를 중시할 것 - 간결하고 단순하며 최소한의 것만 포함해야 한다

(이해하기 쉽고, 주석을 잘 달아야한다.)


* 이름을 잘 지을 것 클래스나 함수, 변수의 목적에 맞는 가장 직관적인 이름을 지어야 한다.


* 전역 네임스페이스를 깔끔하게 유지할 것

  - 네임스페이스나 이름에 붙이는 접두사 등을 사용해 이름이 다른 라이브러리의 이름과 충돌하지 않게 해야 한다.

#define문을 이용해 이름을 정의하는데 주의가 필요하다.

(C++ 전처리기는 단순히 텍스트를 바꿔 칠 뿐이라서 C/C++의 범위나 네임스페이스를 깡그리 무시하기 때문이다.)


* 널리 알려진 C++ 사용법을 따를 것


* 코딩규칙은 일관되야 한다.

  - 코드를 처음부터 짠다면 일관된 규칙을 두고 짜고, 코드를 고친다면 코드의 규칙을 지켜가면서 수정하자.


* 오류를 스스로 드러내는 코드를 작성하라

  - 흔히 저지를 수 있는 프로그래밍 오류를 쉽게 볼 수 있도록 해주는 코드가 좋은 코드이다.




'게임 엔진 아키텍처 > 3장 엔지니어링 기초' 카테고리의 다른 글

객체 메모리  (0) 2016.05.24
C/C++ 메모리 구조  (1) 2016.05.24
선언, 정의, 연결성  (0) 2016.05.24
멀티바이트 데이터와 엔디언  (1) 2016.05.24
수의 표현  (0) 2016.05.24
posted by 알쿠미