제곱근에 대한 이해.

캐릭터와 캐릭터 사이의 거리를 구할때나 피타고라스의 정리의 루트를 제거하기 위해서 정말 많은 제곱근 함수를 사용합니다.

그러니 직접 구현해 보겠습니다.

 

일단 바빌로니아 법을 통해서 제곱근을 구하는 방식에 대해서 알아보겠습니다.

 

바빌로니아 법의 제곱근 구하는 공식 an+1 = (1/2) *(an + k / an)

'게임개발공부 > 직접 구현해 보겠습니다' 카테고리의 다른 글

STR 함수 구현  (0) 2014.07.09
Posted by JJOREG

strcmp와 strlen을 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// STRFUNC.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include <iostream>
 
using namespace std;
 
int mystrlen(char* txt)
{
    int count = 0;
 
    while( *(unsigned char *) txt)
    { 
        ++txt;
        ++count; 
    }
 
    return count;
}
 
int mymystrcmp(const char *src, const char *dest)
{
  int ret = 0;
  while (!(ret = *(unsigned char *) src - *(unsigned char *) dest) && *dest) 
  { 
      ++src, ++dest; 
  }
 
  if (ret < 0)
    ret = -1;
  else if (ret > 0)
    ret = 1 ;
 
  return ret;
}
 
 
class CLASSA
{
public:
    int a;
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    char* txt0 = "1234abcaABCD";
    char* txt1 = "1234abcaABCD";
    char* txt2 = "1234abcf";
    char* txt3 = "1234,abcd,ABCD ";
    char* txt4 = "1234abcaABCDc";
 
    const int a = 0;
 
    int max = strlen(txt0);
    cout << max << endl;
    max = mystrlen(txt0);
    cout << max << endl;
 
    int cmp = strcmp(txt0, txt1);
    cout << "같다 " << cmp << endl;
    cmp = strcmp(txt0, txt2);
    cout << "짧다 " << cmp << endl;
    cmp = strcmp(txt0, txt3);
    cout << "길다 " << cmp << endl;
    cmp = strcmp(txt0, txt4);
    cout << "길이는 같다 " << cmp << endl;
 
    cmp = mymystrcmp(txt0, txt1); 
    cout << "같다 " << cmp << endl; 
    cmp = mymystrcmp(txt0, txt2); 
    cout << "짧다 " << cmp << endl;
    cmp = mymystrcmp(txt0, txt3); 
    cout << "길다 " << cmp << endl;
    cmp = mymystrcmp(txt0, txt4); 
    cout << "길이는 같다 " << cmp << endl;
 
    return 0;
}
 
 


'게임개발공부 > 직접 구현해 보겠습니다' 카테고리의 다른 글

sqrt 제곱근 구하기.  (0) 2014.07.10
Posted by JJOREG

AGP메모리


물음들 : 로컬(비디오) 메모리는 쓰기 속도가 느려서 잦은 갱신에는 적합하지 않고, 시스템 메모리는 그래픽카드쪽에서 읽는게 느려서 문제입니다. 그걸 해결하기 위한게 AGP메모리입니다.


꼭 그렇지는 않습니다. 사실 로컬(비디오) 메모리로 데이터의 Burst 전송이 AGP를 통해 필요할때마다 께작께작 가져가는 데이터보다 더 효율적이리라 추측하고 있습니다. 일단 같은 AGP를 통해 전송되고 한번에 많은 데이터를 보낼경우 주소나 정보를 Burst 해서 보낼 수 있으니까요. 그렇지만 한번 쓰고 버릴 Dynamic 데이터들을 비디오 카드로 전송해서 복사본을 만든다음 쓰는 것이 비효율적이기 때문에 AGP메모리가 사용됩니다. 


참고로 시스템 메모리는 그래픽 카드에서 바로 접근이 불가능합니다. 대부분 H/W들이 그렇지만 I/O 버스와 메모리 버스가 100% 맵핑되는 경우가 없고 데이터 전송에는 DMA에 그외 비슷한 특별한 조치가 필요한 것이죠. AGP 메모리가 그런 특별한 조치중 한가지로 시스템에서 허용한 메모리에 대해 CPU의 도움 없이 그래픽 카드가 멋대로(임의로?) 데이터를 읽어올 수 있는 획기적인(은 아닌것 같지만) 구성입니다. 시스템 메모리의 데이터를 특별한 방법을 사용하지 않고 그래픽 카드로 전송하려면 CPU의 보조가 필수적입니다. 


물음들 : AGP메모리는 일반 램에 영역을 잡아주는 것으로, CPU에서 쓰기속도와 그래픽카드쪽에서 읽기 속도(로컬 메모리보단 약간 느립니다.)가 빠릅니다. 대신 CPU에서 읽기속도는 느리므로 읽는 처리시에는 시스템 메모리가 좋겠지요.


하드웨어에 따라 다르지만 AGP 메모리보다 로컬(비디오) 메모리가 '매우' 빠릅니다. AGP 2X가 533MB/s 정도이고 8X가 대략 2GB/s 정도인데 GeForce 6800 Ultra 의 경우(사악한 비교대상입니다.) 33.6GB/s 의 내부 전송률을 갖고 있습니다. 뭐 하급 기종으로 가면서 절반, 또 절반씩 깍이는 속도이긴 하지만 절반이라 하더라도 AGP 8X의 무려 8배에 달하는 속도를 갖고 있습니다. 게다가!!! AGP 메모리는 시스템 메모리이기 때문에 CPU의 작업률이 올라가면 덩달아 AGP의 효율도 떨어지게 됩니다. 


그리고 AGP 메모리는 캐쉬가 되지 않는 메모리입니다. 간단히 말해서 CPU 캐쉬가 작동하지 않고 작은 메모리 단위로 여러번 읽어올 경우(C/C++코드를 생각하면 곤란하고 어셈 코드단위로 생각하세요.) 캐쉬가 작동하지 않기 때문에 물리적(전기적)으로 메모리에 여러번 접근을 하게 됩니다. 캐쉬를 쓰고 있을때는 크게 느껴지지 않지만 물리적인 메모리에서 데이터를 가져오는 시간은 수십 클럭 이상이 걸리기 때문에 4바이트 또는 12바이트 단위로 데이터에 접근할 경우 4바이트당 수십 클럭을 소모하게 됩니다. PCI도 크게 다르지 않은데 DDraw를 쓰면서 Surface 데이터를 바로 읽어서 알파 블렌딩을 해보신 분들은 무슨 뜻인지 금방 이해 하시리라 생각됩니다.


(메모리 구입시 4-4-4-12 같은 숫자가 바로 이 접근시 걸리는 시간입니다. 최악의 경우 '전부 더한다'정도의 메모리 클럭을 먹는다고 생각하시면 됩니다. CPU는 메모리 버스 클럭의 수배에서 수십배의 속도를 갖고 있으니 최악의 경우 4바이트 접근에 수백 CPU 클럭이 사용될 수도 있습니다.) 


p.s.AGP로 데이터 쓰기 작업에는 WC 버퍼라고 하는 녀석이 쓰여서 쓰기 속도 저하를 상당히 막아주고 있죠. 작은 쓰기 전용 캐쉬 같은거죠.

Posted by JJOREG

다음의 코드를 보자



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
 
int main (void)
{
 
 const int b = 100;
 
 printf("%d\n", b);
 
 int* temp = const_cast<int*>(&b);
 
 *temp = 200;
 
 int* temp2 = temp;
 
 printf("%d\n", b);
 printf("%d\n", *temp);
 printf("%d\n", *temp2);
 
 int* temp3 = const_cast<int*>(&b);
 
 printf("%d\n", *temp3);
 
    return 0;
 
}

const_cast는 참조형의 const나 휘발성을 제거해주는 캐스트방식이다. 즉 위의 const int b는 값형이므로 언뜻보면 캐스트가 안될 수 있지만.
실제 출력되는 값은 temp2의 값이 200으로 변환되어서 컴파일까지 잘 된다.
이게 왜 되는 것인가? const int b의 값을 바꾸는게 되는것인가?
이유는 컴파일러에 있다고 본다. const int b라고 되어있는 부분은 컴파일 시점에서 const int b가 되는 것이 아니라. 100으로 치환되기 때문에 값의 변경과 상관 없이 100의 상수값으로 변경된 것이고 그렇기 때문에 후에 변경하는 값은 b의 값을 아무리 출력해도 100이라는 상수값으로 대체되었기 때문에 100으로 출력되는 것으로 보인다.


Posted by JJOREG

출처 http://forum.falinux.com/zbxe/index.php?document_srl=530445&mid=lecture_tip


1. 프로그램 실행 과정



그림 1. 하드웨어 스팩

 

PC의 하드웨어 스팩입니다.

최상위를 차지하는 F4 있습니다.

* 프로세서 (CPU)

* 운영체제 (OS)

* 메모리 (주기억 장치)

* 하드디스크 (보조 기억장치)

가장 상위에 위치한 것을 보니 상당히 중요한 역할을 하는 것이 분명한데요.

실행에 있어서도 이 녀석을 빼놓고서는 이야기를 할 수가 없습니다.
그럼 이 녀석들로 실행에 대해서 이야기해 보겠습니다.

그림 2. 프로그램 실행과정

 

1. 로딩

윈도우 시스템에서 실행파일은 PE(Portable Excutable)파일 형식을 가지고 있습니다.

Visual studio로 컴파일하면 .exe 의 확장자를 가진 파일이 생성되게 됩니다.

바로 이 파일이 PE파일형식을 가지고 있습니다.

프로그램 실행 명령이 내려지면 OS는 PE파일의 header부분을 분석해서 프로그램의 진입지점을 적절하게 메인 메모리에 로딩하게 한 됩니다.

그 후 프로그램의 명령에 따라 필요한 순차적으로 명령어들을 로딩하게 됩니다.

 

2. 패치(Instruction Fetch)

메모리에 로딩된 프로그램은 CPU를 동작시키기 위한 명령어인 인스트럭션(Instruction)의 집합이라고 할 수 있습니다.

컴파일이라는 것은 결과적으로 우리의 소스 코드를 CPU가 이해할 수 있는 인스트럭션으로 변화시켜 주는 것입니다.

프로그램이 실행되기 위해서는 메모리에 로딩된 인스트럭션들을 순차적으로 CPU로 읽어들여야 합니다.

이러한 과정을 인스트럭션 패치라고 합니다.

 

3. 디코딩(Instruction Decoding)

그림 2번의 8d 44 24 0c 는 16진수로 표현 되었을 뿐 0,1 로 구성된 바이너리 코드입니다.

실행을 위해서는 0,1로 구성된 바이너리 코드를 각 필드별로 분해하여 어떤 종류의 연산을 수행 할 것인지를 결정해야 하고

연산에 필요한 연산자를 레지스터에 인가시키는 작업이 필요합니다.

이러한 작업을 디코딩이라고 합니다.

그림 3.MIPS Instruction Format

출처 :http://www.cise.ufl.edu/~mssz/CompOrg/CDA-lang.html

 

MIPS CPU의 Instruction Format 입니다.

디코딩 과정에서 이러한 각각의 필드를 분석하게 되는 것입니다.

 

세가지의 Instruction Format 을 간단히 설명하면

R 타입은 수행될 명령의 종류를 나타내는 op 코드와 함께 rs , rt 필드에 연산에 사용될 레지스터의 번호가 저장되어 있으며 rd필드에 연산결과가 저장될 레지스터 번호가 저장되어 있는 Instruction format입니다.

I 타입은 rt 필드 대신에 address/immediate 필드를 두어서 상수값을 저장하는 필드로 활용하게 됩니다.

J 타입은 op 필드 6bit 를 제외한 26bit를 상수값으로 사용해서 더욱 넓은 범위의 메모리 address 접근을 할 수 있도록 해줄 때 사용되는 Instruction Format입니다.

 

정말 간단하게 이야기 했을 뿐 책에 더 많은 이야기가 있습니다.

단순히 그림만 보시면 외계어를 보는듯 하실 것 같아서 간단히 필드를 설명했을 뿐 왜 필드구분을 이렇게 했는지는 긴 이야기의 내용이기 때문에 저의 이야기 주제와는 약간 벗어나는 경향이 있습니다.

또한 MIPS CPU는 3가지 종류의 Instruction Format을 구분지어서 사용합니다.

Instruction의 길이를 동일하게 맞춰주는 것인데요.

이것과도 관련해서 파이프라인에 대한 이야기도 있습니다.

더 자세히 알고 싶으시다면 컴구조 관련 책을 참고 하시기 바랍니다.

 

우리 PC에서 주로 사용되는 Intel사 CPU의 Instruction Format 입니다.

그림 4.Intel 64 and IA-32 Architectures Instruction Format

출처 : http://www.intel.com

 

뭔가 복잡합니다. 이래서 초보 공학도들에게는 MIPS가 친구가 되는 것인지도 모르겠습니다.

아무튼 CPU에 따라서 Instruction Format도 달라진다는 것이겠죠.

 

참고로 MIPS Instruction 의 각 필드의 bit를 더하면 32bit 입니다.

흔히 말하는 32bit 머신을 이야기 하는 것 또한 CPU처리 단위를 이야기 하는 것으로 cpu가 한번에 처리가능한 연산 단위를 말하는 것입니다.

MIPS CPU는 인스트럭션 길이의 기준이 CPU의 처리 단위로 정하기 때문에 모든 인스트럭션이 32bit길이로 구성되게 됩니다.

 

4. 실행(Execution)

디코딩 과정에서 수행 될 연산의 종류는 연산코드의 형식으로 CPU에서 실질적인 연산을 수행하는 산술논리 연산장치(arithmetic-logic unit) : ALU 에 입력됩니다.

ALU는 연산코드에 맞춰서 레지스터에 저장된 연산자들을 이용하여 연산을 수행하게 됩니다.

하나의 인스트럭션이 실행을 마치면 다시 새로운 인스트럭션을 패치해 오게 되는 과정을 반복하게 됩니다.

이 과정을 좀더 자세하게 보면 이런 그림인 것입니다.

 

그림 5. Fetch - Decode - Execute Cycle

출처 :http://www.cise.ufl.edu/~mssz/CompOrg/CDA-lang.html

 

앞서서 대략적으로 프로그램의 실행과정에 대해서 간략히 적어봤는데요.

사실 어려운 부분을 모두 빼고 이야기한 내용일 뿐 그 내부에는 어마어마한 이야기가 숨어 있습니다.

정말 자세하게 실행과정에 대해 알기 위해서는 PE실행파일의 원리 와 OS의 전반적인 내용까지 이해해야 합니다.

 

2. PE실행파일

우리는 컴파일을 C소스를 CPU가 인식할 수 있는 기계어(인스트럭션 집합)로 변환하는 것으로 알고 있습니다.

하지만 실질적으로 생성되는 .exe 파일은 단순한 인스트럭션의 집합만으로 이루어져있지는 않습니다.

 

그림 6. PE파일의 기본 구조

 

왼쪽 그림은 PE실행파일의 구조입니다.

그리고 오른쪽은 PE파일이 가상 메모리에 로딩(또는 맵핑) 되었을 때의 그림입니다.

네 드디어 가상메모리라는 이야기가 나왔습니다.

가상메모리는 일단 제쳐두고 중요한 내용은 PE파일의 구조를 간단히 보는 것 입니다.

구조를 크게 나눠보자면 Header 부분 Section 부분으로 나눌 수 있습니다.

 

Header 부분에는 실행에 필요한 정보들이 구조체 형식으로 저장되어 있습니다.

프로그램이 실행되면 OS의 Loader는 Header의 정보를 살펴보면서 가상메모리에 PE실행파일의 내용을 오른쪽의 그림처럼 로딩(맵핑)시키는 것입니다.

NT header에 IMAGE_OPTIONAL_HEADER32를 보면

ImageBase에 가상 메모리내에서 PE파일이 로딩되는 시작주소가 적혀 있습니다.

위 그림에서는 ImageBase 의 값이 0x1000000h 인 것입니다.

또한 각 Section Header에는 각 섹션들이 가상 메모리에서 차지하는 크기와 주소에 맵핑되는 상대적인

주소값(RelativeVirtualAddress)등이 적혀 있습니다.

RVA를 사용하는 이유는 가상메모리에 로딩되는 순간 그 위치에 다른 PE파일이 로딩되어 있을 수도 있기 때문입니다.

RVA가 아닌 절대적인 경로로 저장 되어 있을 경우 이러한 순간에 다른 주소로 재배치 하기가 곤란해지게 되는 것입니다.

또한 상대적인 주소로 되어 있기 때문에 어느 위치에 맵핑 되어도 메모리 참조가 가능하게 됩니다.

 

Section 부분을 보면

 

* Section(".text") = 실행 코드

* Section(".data") = 전역 변수

* Section(".rsrc") = 리소스

 

3가지 섹션 영역들이 있습니다.

Section의 이름에서 유추해 볼 수 있듯이 각각의 섹션에 실행 코드와 전역으로 사용했던 변수, 그리고 리소스 들이 각각 나눠져서

저장되어 있습니다.

한마디로 우리가 C코드를 컴파일을 하면 우리가 한 소스코드 내에서 같이 사용했던 실행코드들과 전역변수들이 각각 분리되어서

따로 저장되는 것이었습니다.

Section 부분에는 우리가 코딩한 실질적인 프로그램의 내용이 저장되어 있는 것입니다.



Posted by JJOREG

인터넷에 좋은 글들이 있지만 저는 제 방식대로 게임만들때에 대비해서 생각하는 편이라 정리해서 올립니다.

일단 이런 규칙들이 생긴 이유에 대해서는 처음에는 몰랐지만 지금은 아주 통감하고 있습니다. 특히 팀프로젝트 할때는 더더욱 그렇습니다.


아래는 http://b-jay.tistory.com/115사이트에서 가져온 도입 이유입니다.


1.     경직성

- 무엇이든 하나를 바꿀 때마다 반드시 다른 것도 바꿔야 하며, 그러고 나면 또 다른 것도 바꿔야 하는 변화의 사슬이 끊이지 않기 때문에 시스템을 변경하기 힘들다.

나 : 팀원에서 묻습니다. 이것좀 바꿔줄 수 있어? 

팀원A : 그걸 바꾸면 구조를 싸그리 고쳐야 해요. 안됨.

나 : 아니 어떻게 짰으면 그거 하나 바꿨다고 구조를 다 바꿔?

팀원A : 이미 그렇게 만들었어요 시간 더든다니까요...

나 : 그래 그럼 뭐 어쩔 수 없지...(아니 무슨 프레임워크의 최상위 클래스에 순수가상함수를 넣자는 것도 아니고 뭐만하면 )

2.     부서지기 쉬움

- 시스템에서 한 부분을 변경하면 그것과 전혀 상관없는 다른 부분이 작동을 멈춘다.

팀원B : 형 제가 이 코드에 이거 바꿨는데 형께 작동 안하는데요.

나 : 응 왜?

팀원B : 그 클레스를 만든게 형이라.

나 : (훑어보고)어 미안 이거 이러저러해서 동작이안되네... 고칠께... (아... 내가 왜 이걸 이렇게 짰지?)

3.     부동성

- 시스템을 여러 컴포넌트로 분해해서 다른 시스템에 재사용하기 힘들다.

나 : 이거 기능 빼서 이 클래스로 다시 만들께.

팀원들 : 안되요 그거 바꾸면 구조다 망가짐.

나 : 아니... 그러니까... 왜...

4.     끈끈함(점착성)

- 개발 환경이 배관용 테이프나 풀로 붙인 것처럼 꽉 달라붙은 상태다. 편집 - 컴파일 - 테스트 순환을 한 번 도는 시간이 엄청나게 길다.

나 : 아 이 클래스 너무 헤더가 많은데... 컴파일!

나 : (10분동안 기다리다가 돌아와서) 아직도! 안끝났어!!!!

5.     쓸데없이 복잡함

- 괜히 머리를 굴려서 짠 코드 구조가 굉장히 많다. 이것들은 대개 지금 당장 하나도 필요 없지만 언젠가는 굉장히 유용할지도 모른다고 기대하며 만들었다.

나 : 이걸 이렇게 이렇게 짜면... 애들이 이러이러 써줄꺼야...

2개월후

나 : 아무도 사용하지 않았군 괜히 만든 기능이네...

6.     필요 없는 반복    

- 코드를 작성한 프로그래머 이름이 마치 ‘복사’와 ‘붙여넣기’ 같다.

나 : 아니 함수객체나 static 클래스로 함수화 시키면 되지 왤케 같은 함수가 많아.

나 : 전에 형이 만든 기능도 한번밖에 안쓰던데요. 그럴줄 알았죠.

7.     불투명함

- 코드를 만든 의도에 대한 설명을 볼 때 그 설명에 ‘표현이 꼬인다’라는 말이 잘 어울린다.

나 : 이건 이러저러해서 이러저러하기 때문에 이러저러한 기능을 담당하는 클래스야...

모두들 : 어려워...


예제코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// TEST.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include <iostream>
 
using namespace std;
 
class 존재
{
private:
    int 세상속의X축위치;
    int 세상속의Y축위치;
    int 세상속의Z축위치;
 
public:
    virtual void 그려지다() { cout << "나 그려진다!" << endl; }
    virtual void 타입을말해봐() { cout << "객체다!" << endl; }
};
 
class 생물 : public 존재
{
private:
    TCHAR* 생물의이름;
    int 공격력;
    int 방어력;
    int 수명;
 
public:
    virtual void 이동() = 0;
    virtual void 사망() = 0;
    void 넌뭐니?() { cout << "난 생물이다!!!!!!" << endl; }
};
 
class 괴물 : public 생물
{
private:
    int 생명력;
 
public:
    virtual void 이동() { cout << "괴물 이동한다." << endl; }
    virtual void 사망() { cout << "괴물 죽는다." << endl; }
    void 포효하다(void);
    void 공격하다(const int 데미지);
};
 
class 사람 : public 생물
{
private:
    int 공격력;
    int 방어력;
 
public:
    virtual void 이동() { cout << "사람 이동한다." << endl; }
    virtual void 사망() { cout << "사람 죽는다." << endl; }
    virtual void 그려지다() { cout << "사람은 다르게 그려진다" << endl; }
    void 말하다(void);
    void 먹다(void);
    void 공격하다(void) { cout << "사람 이동한다." << endl; }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    사람 철수;
 
    철수.타입을말해봐();
    철수.그려지다();
    철수.넌뭐니();
    철수.이동();
    철수.사망();
 
    cout << endl;
 
    생물* 고질라 = new 괴물;
    고질라->이동();
    생물* 울트라맨 = new 사람;
    울트라맨->이동();
 
    return 0;
}


1.      SRP (단일 책임 원칙)


Single Responsibility Principle

모든 클래스는 단 하나의 책임을 가져야 한다는 것이다. 만들 때나 수정할 때 한가지 이상의 이유가 있어서는 안된다.


클래스를 만들다보면 하나의 클래스에서 너무 많은 역할을 담당할 때가 있다. 특히 게임 오브젝트에 대한 내용이 그렇다.

게임 오브젝트는 상호작용, 렌더링, 게임내데이터등 많은 역할을 맡을 때가 많습니다.

또한 그것들이 또 다시 상호작용한다. 

그래서 캐릭터의 렌더링이 이상할때도 게임오브젝트를 보고 충돌이 이상할때도 게임오브젝트를 보게되는 경우가 많습니다.

즉 하나의 클래스에 너무 많은 부담이 가해지는 것이다.

물론 캐릭터에 이상이있다... 라는 점에서는 그것을 담당하는 게임 오브젝트 클래스를 보는게 맞지만. 

캐릭터의 렌더링, 캐릭터의 상호작용 등으로 들어가기 시작하면 또다시 복잡해 지기 시작한다.

이럴때는 각 기능들을 다형성을 이용해서 또 다시 클레스로 세분화시키는 작업을 하게된다.


나와 같은 경우에는 게임의 리소스를 관리하는 리소스관리자 클래스를 만들고 또 그 아래 세부적으로 텍스처를 관리하는 텍스처매니저 클래스, 매쉬를 관리하는 매쉬매니저 클래스를 따로 놓거나 아니면 두가지를 애초부터 분리해 놓기도한다.

즉 텍스처 로딩이 이상한데? 라고 하면 텍스처매니저 클래스를 보면 되고 매쉬가 이상하다! 그렇다면 매쉬매니저 클래스를 보면 된다.


2.      OCP (개방-폐쇄 원칙)


Open Closed Principle
모든 소프트웨어 구성 요소는 확장에 대해서는 개방되어있지만, 수정에 대해서는 폐쇄되어있다는 원칙이다.


이미 만들어진 클래스를 수정하는 것은 항상 문제가 따른다. 이를 잘 지키기 위해서 위해서 가상함수를 잘 이용해야 한다.
생물 클래스는 이동과 사망을 가지고 있다. 생물들은 마땅히 이동할수 있다고 생각했고 공통되는 기능은 상위 클래스에 정의되어 있거나 혹은 순수가상함수로 꼭 구현해야될 기능으로 정의되어 있다.
즉 생물 클래스는 생물클래스를 통해서 사람이나 괴물 같은 하위 클래스를 확장하는데에는 개방되어 있지만.
반대로 생물 클래스의 이동이나 사망을 수정하거나 할 이유는 없으므로 수정에 대해서는 폐쇠되어 있다.

3.      LSP (리스코프 치환 원칙)


Liskov Substitusion Principle

자식 클래스는 언제나 자신의 부모 클래스로 교체될수 있다는 원칙이다.


부모클래스의 자리에 자식클래스가 들어가더라도 잘 작동해야 한다는 것이다.

즉 생물 클래스의 자리에 자식 클래스인 괴물이나 사람이 들어가도 잘 작동해야하고 실제대로 잘 작동한다.

이는 상속의 본질을 의미한다. 이에대해서는 잘 작동 못하게 짜는 것도 굉장히 힘이들지만 기능이 많아지다보면 규칙은 내다버리고 싶은 경우가 다수입니다.

즉 부모의 인터페이스를 자식은 모두 만족할 수 있도록 구현해야 합니다. c++이라면 어떤 부모 클래스형의 포인터가 있을때 그 포인터 형으로 자식 클래스를 넣어 놓습니다.

그 상태에서 어떤 자식 클래스를 넣었을 때는 잘 작동하고 어떤 녀석을 넣었을 때는 작동하지 않는다면 그것은 그 자체로 후일 문제를 일으킬 가능성이 존재합니다.

  • LSP는 상속(Inheritance), 다형성(Polymorphism) 과 관련된 원칙.
상속은 코드 재사용 이라는 이유로 과용 될수 있는 기능. 과잉 사용된 상속은 복잡한 계층구조와 커플링을 타이트하게 함으로써 객체지향으로 얻기 위한 유지관리 비용 감소에 악영향을 미치는 요소 중 하나.

라는 의견이 있으니 참고해 주시기 바랍니다.


4.      DIP (의존 관계 역전 원칙)


Dependency Inversion Principle
고차원의 모듈은 저차원의 모듈에 의존하면 안된다. 이 두 모듈 모두 추상적인 것에 의존해야 한다.
추상화된것은 반대로 구체적인 것에 의존해서는 안된다. 구체적인 것이 추상적인 것에 의존해야 한다.
상위 클래스는 하위 클래스에 의존해서는 안된다는 법칙이다.


고차원의 클래스는 자신의 하위객체에 어떤것도 의존해서는 안됩니다. DLL을 사용할때 이런일이 많이 발생합니다. DLL을 만들어 놓고 클라이언트에 있는 어떤 객체가 필요하다던가 하는 일이 있는데.

애초에 잘못 짠 것입니다. 추상클래스는 하위의 구현될 가능성이 있는클래스에 의존해서는 안됩니다. 

순수가상함수가 있는 클래스는 다른 클래스에 의존해서 작동해서는 안된다는 것입니다.

이는 위의 코드에서도 잘 나와있습니다.

고질라와 울트라맨을 만들어 낼때 둘은 모두 생물이라는 클래스의 포인터 타입으로 동적할당 되었습니다.

여기에서 예를 들자면 꼭 사람으로 할당되어야 하거나 꼭 울트라맨으로 할당되어야 하는 일이 벌어진다면 그것만으로도 객체지향을 해치는 결과가 될 수 있습니다.

또한 이러한 점에 있어서는 함수인자등으로 받을때 하나의 클래스로 대부분이 통용되므로 


5.      ISP (인터페이스 분리 원칙)


Interface Segregation Principle

클라이언트에서 사용하지 않는 메서드는 사용해선 안된다. 


이건 한가지 예를 들어보면 편할 것입니다.

만약 생물 클래스 아래에 사망과 죽음 말고 갑자기 말하다라는 함수가 생성되었습니다. 

하지만 괴물은 말하다에 아무것도 할일이 없습니다. 하지만 추상클래스가 어쩔수 없이 구현만 해놓고 안에는 아무것도 넣어놓지 않았습니다.

하지만 이것을 말그대로 어쩔 수 없이 구현한 것입니다.

그런 어쩔수 없는 구현을 하위 클래스로 내려서 어쩔수 없지 않게 만드는 것. 메소드가 필요 없음에도 다른 기능들 때문에 상위의 클래스를 상속받지 않게 상속구조를 정리하거나

메소드를 분리하는 것이 바로 인터페이스 분리 원칙의 핵심입니다.

'게임개발공부 > C++' 카테고리의 다른 글

const_cast의 난점.  (0) 2014.07.08
OOP객체지향 프로그래밍 1) 4가지 특징  (0) 2014.06.26
C++ 과 C의 차이점  (2) 2014.06.25
Posted by JJOREG

심플하게 말해서 객체지향 프로그래밍은 프로그래밍을 전체의 흐름으로 보지 않고 객체를 기반으로 해서 구현하는 것입니다.

객체지향 프로그래밍에는 4개의 특징과 5가지 원칙이 있습니다 그것에 대해서 예제 코드와함께 설명하겠습니다.


예제코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// TEST.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include <iostream>
 
using namespace std;
 
class 존재
{
private:
    int 세상속의X축위치;
    int 세상속의Y축위치;
    int 세상속의Z축위치;
 
public:
    virtual void 그려지다() { cout << "나 그려진다!" << endl; }
    virtual void 타입을말해봐() { cout << "객체다!" << endl; }
};
 
class 생물 : public 존재
{
private:
    TCHAR* 생물의이름;
    int 공격력;
    int 방어력;
    int 수명;
 
public:
    virtual void 이동() = 0;
    virtual void 사망() = 0;
    void 넌뭐니?() { cout << "난 생물이다!!!!!!" << endl; }
};
 
class 괴물 : public 생물
{
private:
    int 생명력;
 
public:
    virtual void 이동() { cout << "괴물 이동한다." << endl; }
    virtual void 사망() { cout << "괴물 죽는다." << endl; }
    void 포효하다(void);
    void 공격하다(const int 데미지);
};
 
class 사람 : public 생물
{
private:
    int 공격력;
    int 방어력;
 
public:
    virtual void 이동() { cout << "사람 이동한다." << endl; }
    virtual void 사망() { cout << "사람 죽는다." << endl; }
    virtual void 그려지다() { cout << "사람은 다르게 그려진다" << endl; }
    void 말하다(void);
    void 먹다(void);
    void 공격하다(void) { cout << "사람 이동한다." << endl; }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    사람 철수;
 
    철수.타입을말해봐();
    철수.그려지다();
    철수.넌뭐니();
    철수.이동();
    철수.사망();
 
    cout << endl;
 
    생물* 고질라 = new 괴물;
    고질라->이동();
    생물* 울트라맨 = new 사람;
    울트라맨->이동();
 
    return 0;
}


4가지 특징


1. 추상화 

프로그램의 구조를 구조와 단계로 생각하기 전에 추상화 시켜서 생각 할 수가 있다. 이는 상속과도 밀접한 개념으로 객체들이 가진 공통적인 데이터등을 뽑아내는 작업입니다.

당연히 예를 들면 더욱더 편할 것입니다.

먼저 존재라는 클래스를 정의하고 그 존재의 상속을 받는 생물이라는 클래스를 만들고 또 그를 상속받는 괴물이라는 클래스를 만들었습니다.

즉 말그대로 클래스를 정의함에 있어서 공통되는 데이터를 뽑아내며 기능으로서의 클래스가 아니라 현실과 대비하여 추상적으로 프로그래밍을 할 수 있게 되는 특징이 됩니다.


1. 캐릭터를 만든다? 

2. 고민하지 않고 캐릭터 클래스를 만든다. 

3. 어 근데 몬스터와 플레이어가 모두 같은 걸 쓰네? 

4. 공통적인 변수를 빼내어 그 상위 클래스인 생물 클래스를 만든다.


2. 캡슐화

괴물 클래스를 보겠습니다. 생명력과 공격력은 private: 키워드로 묶여있습니다. 저 키워드는 클래스를 만들어도 외부에서 내부의 맴버를 사용할 수 없게 만드는 것입니다.

즉 괴물의 private 맴버인 생명력과 공격력을 직접 바꿀 수는 없습니다.

하지만 바깥으로 들어나야 몬스터를 죽이던가 살리던가 혹은 몬스터가 공격하거나 방어하는 상호작용이 이루어 질 것입니다.

그것을 내부의 함수를 통해서 외부에서 이용하게 합니다. 공격한다! 라는 기능자체를 만들어 내는 것이지요. 

즉 내부의 데이터 값을 기반으로해서 클래스 내부에 있는 기능만을 바깥으로 내보이는 것입니다.


1. 몬스터는 생명력이 있어. 

2. 외부에서 맘대로 수정하지 못해!

3. 외부에 공개된 실행할수 있는 맞는다는 기능 공격한다는 기능만을 외부에 공개할께 너희들은 내가 공개한 것만 사용해.

 

* 보너스 부록

여기에 +하여 정보은닉이라는 특징이 있는데이는 캡슐화와 거의 비슷한 의미를 가진다.

API를 사용하려는 사용자에서 필요 이상의 데이터나 잘못된 사용을 금지하고 사용자에게 필요한 정보만을 공개하며 나머지를 은닉하는 것을 의미한다. 어찌 보면 캡슐화와 거의 같은 개념이며이 녀석을 분리 시킬경우 5대 특징으로 표시한다.


3. 상속성

존재 -> 생물 -> 괴물로 이어지는 상속라인을 보면 알겠지만 괴물은 존재와 생물이 가지고 있는 클래스 구조는 계층적으로 아래로 내려오며 상위의 함수나 맴버변수들을 모두 사용할 수 있습니다. 즉 새로운 기능을 재정의할 필요가 없어지는 것이죠.

예를 들기 위해서 실행 결과를 한번 보겠습니다.




철수는 자신의 상위 클래스의 기능들을 구현해서 사용하고 있습니다.

하지만 무리 없이 사용할 수 있죠. 더 나아가서 자신이 다르게 사용하고 싶은 함수는 자신이 다시 재정의 해서 사용합니다.

상속을 하게 되면 상속을 통해서 괴물이나 사람은 그려지다라는 기능에 대해서 굳이 자신에게 구현을 하지 않아도 부모 클래스의 기능으로 사용이 가능해집니다. 

코드의 재사용성을 높여주게 되며 상속을 통해서 클래스를 추상화시키는데에도 도움이 됩니다.


4. 다형성

마지막으로 울트라맨과 고질라는 같은 생물* 형입니다. 하지만 실행은 고질라는 괴물 이동한다. 울트라맨은 사람 이동한다. 라는 글을 출력합니다.

virtual키워드를 이용해서 구현한 함수는 동적 바인딩이라는 작업을 거칩니다. 

동적 바인딩을 먼저 이해하기 전에 동적할당을 먼저 이해하면 편합니다. 

tmain() 함수를 보면 마지막즈음 포인터를 이용해서 사람을 동적할당 하고 있습니다.

이때 포인터의 타입은 생물* 이지만 할당은 괴물* 사람*입니다. 즉 동적할당을 위해서 받아들인 타입과 상관없이 실제 할당받은 객체타입의 함수를 실행합니다. 

이 모든 과정은 프로그램의 런타임중에 일어나게 됩니다. 이것이 동적 바인딩 입니다. 

즉 하나의 타입으로 보이는 객체가 실제 기능의 실행에서는 각자 다르게 동작하게 되는 것입니다.


virtual 키워드를 설명하기 가장 좋은 특징입니다.


알게된점 1)

C++은 디폴트가 비가상함수이고,
자바는 디폴트가 가상함수이다!!! (Oh ho)

'게임개발공부 > C++' 카테고리의 다른 글

const_cast의 난점.  (0) 2014.07.08
OOP객체지향 프로그래밍 1) 5대 원칙  (1) 2014.06.27
C++ 과 C의 차이점  (2) 2014.06.25
Posted by JJOREG

개념적인 차이


1. 패러다임


C는 절차지향적 언어(Procedural). 

C++은 흔히 객체지향언어(Multi paradigm).


하지만 C++은 엄연히 말해서 멀티패러다임 언어이며 객체지향이건, 절차지향이건 구현이 가능하며 C의 확장팩이라고 생각하면 편합니다. 

(엄밀히 말하면 그렇지 않습니다. C++은 C의 문법처럼 사용할수 있을 뿐이지 이미 오랜시간 C도 확장을 해왔고 C++도 확장을 해왔습니다. 마치 C의 문법처럼 쓰지만... 내부 동작은 전혀 다를 수도 있습니다. 마치 C++배워 놓고 C는 쉽게 배울 수 있을 거라 생각하지만 깊이 들어가면 역시... 끝도 없습니다.)

마찬가지도 C에서도 객체지향적으로 구현을 할 수가 있지만 C++은 그를 구현하기 위한 수많은 문법적 기능이 마련되어 있고

객체지향 중심적이지만 얼마든지 C로 회귀하여 C처럼 프로그래밍을 할 수도 있고 함수 프로그래밍등 다양한 프로그래밍을 소화할 수 있습니다.

하지만 굳이 C로 객체지향적인 코딩을 해야하는가가 문제가 됩니다.

마찬가지로 C++을 말할 때 마치 객체지향프로그래밍 언어의 대명사인 것처럼 말하지만 C++은 멀티패러다임 언어이다.

만약 객체지향형식 언어라고 c++을 정의했다면 저는 절대로 friend같은 키워드는 절대로 만들지 않았을 겁니다.

또한 더욱 깊이 들어간다면 객체지향언어니 절차지향언어니 따지는 것도 소용없다고 봅니다. 

말씀드렸듯이 언어는 언어이고 엄연히 말하자면 객체지향이나 절차적언어 같은 것은 일반적으로 통용되는 개발방식에 대한 내용입니다. 

즉 언어 자체를 그런 단어로 단정짓기는 힘들다는 이야기 입니다.


2. 개발방식


C는 하향식 접근 방법[각주:1]

C++는 상향식 접근 방법[각주:2]


C는 절자척 언어이기 때문에 기본 요소간의 순차적 수행이 되도록 서로간의 연결이 중요하다 단계별 공식화(linked-together) 이후 세부 사항으로 나눠집니다.
하지만 C++은 시작부터 추상적인 개념을 통해서 구성을 한 후 그 다음 그것들을 조합하는 방식으로 만들어 집니다.
이는 클래스와 객체를 통한 프로그래밍이 C++ 에서 주를 이루면서 생겨난 특징입니다.

물론 C++도 하향식 접근방식으로 개발이 될 수도 있고 혹은 그때그때 다를 수도 있지만 기본적으로 C++을 배우게 되면 상향식 접근 방법을 먼저 접하게 될겁니다.

왜냐하면 그게 편하니까요. 당신이 창조주로 세상을 만들어 낼수 있다고 할때 세상 만물이 창조되는 물리법칙, 전자기력, 핵력, 중력, 자기력등을 먼저 만들겠 습니까. 

아니면 태양을 만들고 바람을 만들고 사람을 먼저 만들겠습니까. 물론 프로그래밍 실력이 나아질수록 원론과 근본에 손을 먼저 쓰겠지만 그보다는 우리에게 친숙한 추상적인 객체에서부터 C++ 프로그래밍을 시작하는 것이 일반적입니다.

또한 이점은 구조적 프로그래밍과 객체 지향 프로그래밍의 큰 차이라고 볼 수도 있습니다.


문법적인 차이


1. class


c++ 에서는 class 가 생겼다!

class라는 것은 C++에서 감초 같은 역할을 합니다.

항상 사람들은 c++을 하면서 class 기존 C에서 부터 존재하던 struct와의 차이를 물어보는데 이에 대해서 좀 이야기를 해야할것 같다.


부록이에용~ C++에서의 CLASS와 STRUCT, 그리고 C의 STRCUT에 대해서 설명해 보겠음다.


1. C++의 CLASS는 CLASS내의 함수를 가질 수 있다. STRCUT도 함수를 가질 수 있다. C의 STRCUT는 함수호출을 할수 있다.

2. C++의 CLASS는 CLASS 맴버가 기본적으로 private. STRCUT는 public. C의 STRCUT 접근 지정자 자체가 없다.

3. C++의 CLASS는 상속이 가능하다, STRCUT 또한 상속이 가능하다, C는 개념 자체가 없다.

4. C++의 CLASS는 템플릿 사용 가능, STRCUT 또한 가능, C는 개념 자체가 없다.


음 차이가 없다. 정말 없다... 딱하나 2번 항목을 제외하고 나는 차이를 못느껴봤다.

어느정도로 차이가 없냐고?

심지어 이런짓도 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct 생물
{
 
};
 
class 휴먼 : public 생물
{
    int a;
 
public:
    휴먼()
    {
        a = 0;
    }
};
 
template<class T>
struct 로봇 : public 휴먼
{
 
};

구조체를 클래스가 상속받고 그것을 다시 템플릿을 사용하는 구조체가 다시 상속 받는다. 객체화 다 잘된다.

현재까지 내가 파악한 파악한걸로는... 음... 

1. 어떤 의견으로는 구조체는 값타입 클래스는 참조타입이라는데 C#의 클래스와 구조체의 차이이다. C++에서 그런가? 확인할 수 없었다.

2. 어던 의견으로는 구조체는 new 동작시 디폴트 생성자가 없다면 묵시적으로 생성되는 디폴트 생성자가 호출이 안된다는데... 역시 명확하지 않다. 아니 모든 기능이 똑같은데 구조체라고 다를까 하는 생각이 있다. 


2. 오버로딩을 지원하지 않는다.


C++에서는 같은 함수명은 같지만 함수파라미터는 다른 오버로딩을 지원하지 않는다.


3. C는 참조변수 혹은 레퍼런스변수를 지원하지 않는다..


레퍼런스는 C++에서 생겨난 개념이다. 마찬가지로 C에서는 지원하지않는다.

C만 전문적으로 보신분들이 본다면 포인터와 비슷한 개념이지만 실제 값처럼 취급되는것이 레퍼런스이다.

& 기호를 사용하며 다음과 같습니다.

int a = 10;

int &A = a; 이와 같이 변수만 받을수 있으며 실제 a의 값처럼 사용된다.


---------------------------------


1. 글을 쓰면서 알게된 점 1. C++ 에서는 배열이나 함수가 포인터로 변환되는 것을 Decay(부식: 그러니까 모든 정보를 온전히 갖지 못한다는 이야기)라고 한다.

2. 템플릿으로 이런짓이 가능하다 허허허.... 항상 배운다. 정말...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
template<typename T, size_t n>
void foo(T (&p)[n])
{
    int size = sizeof(p) / sizeof(p[0]);
}
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    int temp[] = {1, 3, 3};
 
    foo(temp);
 
    return 0;
}

3. 배열의 이름과 포인터는 비슷한 동작을 하지만 절대로 같지는 않다. 물론 알고 있는 내용이지만 명확하게 다시한번 확인했다.

  1. 어떤 의사를 결정하거나 어떤 일을 수행하는 과정의 개괄적인 측면에서 시작하여 점차 세분화해 가며 처리하는 방식. 상향식 접근 방식(bottom-up approach)과 대별된다. [본문으로]
  2. 작은 부분부터 개발하고 후에 그것을 조합하여 하나로 만드는 것을 의미한다. [본문으로]
Posted by JJOREG

랜더링 파이프라인이란? ------------------------------------------------------------------------------------

 

기하학 적으로 가상의 3D장면을 구성하고 가상카메라를 설정한 다음 모니터에 2D 표현을 만들어 내는 과정을 랜더링 파이프 라인이라고 한다.

 

랜더링 파이프라인의 단계? ------------------------------------------------------------------------------------

 

객체 ------------------------------------------------------------------------------------

 

3d에서 어떠한 물체는 x y z의 좌표를 가져야 합니다. 이걸 기본 전체로 하나 하나의 물체는 자신을 이루는 면들과.

특정 위치를 가지는 녀석을 객체라고 하겠습니다. 즉 객체란 3차원 공간 상에서 x y z의 값을 가진 녀석입니다.

 

정점(VERTEX) 폴리곤 ------------------------------------------------------------------------------------

 

 

정점이란 간략하게 설명해서 3D 평면상에 존재하는 하나의 점을 의미합니다.

1개의 점은 말그대로 점 2개가 있으면 직선을 만들 수 있습니다. 

하지만 아직까지 3D 화면에서 보이기에는 그다지 적합하지 않은 형태입니다. 여기에 3개의 삼각형이생기게 됩니다.

세개의 정점이 만나서 생겨나는 면을 폴리곤이라고 생각해주시면 되겠습니다.

 

로컬 스페이스 ------------------------------------------------------------------------------------

로컬좌표란 어떠한 물체가 자신을 기준으로한 점이나 버텍스의 정보를 가진 상태로 외부 영향이 없는 상태라고 하겠습니다.

좀더 쉽게 설명하자면 무조건 0.0좌표를 기준으로 가지는 어떠한 객체를 가지고 얼마든지 그것을 늘리고 줄이고 회전시킬 수 있습니다.

즉 주변에 비교할만한 상대적 크기등이 없기 때문에 주변의 다른 객체를 고려하지 않고 모델링을 구현할수 있습니다.

현재까지는 아직 게임에 적용되지 않았다고 보면 됩니다.

월드 스페이스 ------------------------------------------------------------------------------------

 

하지만 모든 물체가 0.0에 존재한다면 모든 물체는 겹쳐있는 것처럼 보입니다.

일반적인 3d게임을 보면 객체와 객체 사이의 구분이 명확합니다.

 

 

 

사진을 보시면 말그대로 하나의 월드에 캐릭터와 나무 등이 배치되어 있는 것이 보일겁니다.

모두 x = 0, y = 0, z = 0의 위치가 아니라. 어떤 기준에 의해서 각 객체들이 각자의 좌표를 가지게 되는 것입니다.

즉 이미 어떠한 기준이 있고 그 기준에서 x y z축으로 얼마나 떨어져 있느냐를 정해준다면 그 객체는 3d 평면상에 객체가 되는 것입니다.

즉 처음의 객체는 0.0 0의 위치를 가지고 있지만 이것을 게임에 사용하기 위해서

하나의 기준 좌표를 가지고 100, 200, 100 <- 이런식으로 실제 배치될 좌표로 이동시키는 단계를 의미합니다.

 

이때 사용하는 함수로는 다음과 같은 함수들이 있습니다.

 

m_Transform.matWorld  -> 어떠한 객체의 행렬

 

D3DXMatrixTranslation(&m_Transform.matTrans, vPos.x, vPos.y, vPos.z); -> 월드 좌표의 이곳으로 이동시킨다.

 

pDevice->SetTransform(D3DTS_WORLD, &m_Transform.matWorld); -> 월드좌표를 기준으로 객체의 현재 행렬을 적용시킨다.

이때 알아두셔야 할것이 SetTransform이라는 함수는 내부에 등록된 월드 좌표들을 기록해 놓는다는 것입니다.

rander() -> 객체를 그린다.

 

뷰 스페이스 (3D 세상은 카메라를 중심으로 돈다.) ------------------------------------------------------------------------------------

 

3D세상에서 각 물체가 위치하고 있다고한다면 그것을 바라보는 눈이 존재할 것입니다.

 

관측되지 않으면 각 위치는 의미가 없습니다.

 

 

 

 

그렇다면 관측은 어떻게 하느냐? 카메라라는 존재가 있다고 생각하고 그 카메라가 위치와 바라보는 위치를 가지고 시야가 존재한다면

그 시야에 비치는 만큼을 3d화면에서 보고 있다고 할수 있습니다.

이때 카메라가 월드의 특정 위치나 방위를 가진다면 이후 있을 투영이나 클리핑 등의 작업이 오히려 연산을 더 사용하게 됩니다.

그래서 다이렉트에서는 카메라를 다시 원점으로 돌리고, 바라보고 있던 방향을 양의 z축을 바라보게 만듭니다.

즉 원점으로 돌리고 전방을 바라보고 회전시킨다는 것이죠.

하지만 카메라만 이동하면 현재 바라보고 있던 객체들은 보이지 않게 될 것입니다.

그러므로 모든 객체들에게 카메라가 이동한 만큼의 행렬을 이동시켜 주고 카메라를 중심으로 모든 행렬을 공전 시켜 줍니다.

즉 모든 객체는 카메라의 위치와 행렬값이 원점으로 돌아온 만큼 이동후 카메라를 중심으로 공전회전을 해줘야 합니다.

 

D3DXMATRIX Cammatrix

D3DXVECTOR3 vectorEye -> 월드 내의 카메라 위치

D3DXVECTOR3 vectorAt -> 월드 내의 카메라가 보는 지점

D3DXVECTOR3 vectorpUp -> 월드의 업백터(0, 1, 0)

 

D3DXMatrixLookAtLH(&Cammatrix, &vectorEye, &vectorAt , &vectorpUp) -> 카메라가 바라보는 시점을 기준으로한 뷰 스페이스 변환 행렬을 만들어 Cammatrix에 넣어준다.

 

pDevice->SetTransform(D3DTS_VIEW, &Cammatrix); 카메라의 행렬을 뷰 행렬로 장치에 세팅한다.

 

후면 추려내기  ------------------------------------------------------------------------------------

 

하나의 폴리곤은 앞면과 뒷면을 가지게 됩니다. 일반적으로 뒷면은 화면에 표시되지 않는 경우가 많습니다.

물론 뒷면도 출력할수 있지만 그렇다면 상당한 낭비라고 할수 있습니다. 일반적으로 게임에서 가장 많은 자원을 소비하는 것은 랜더링작업이며.

그 작업의 부담을 줄이기 위한 방법은 역시 출력하지 않을 것은 출력하지 않는게 최고 입니다.

후면추려내기 작업은 화면에 보이지 않는 면을 출력하지 않게 하는 작업입니다.

컴퓨터는 순차적인 처리를 하는 기계라 폴리곤을 면으로 만들때도 버텍스를 하나하나 읽어들여서 하나의 면을 출력합니다.

다음과 같이 모두 같은 방향으로 정점을 변환하다 보면 면이 뒷면일 경우 읽는 순서가 반대가 될 것입니다.

이러한 면들을 후면이라고 판단하고 추려내는 작업을 하는 것을 후면추려내기라고 합니다.

후면 추려내기는 디바이스를 통해서 옵션을 설정할 수가 있습니다.

 

 Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

 

D3DRS_CULLMODE- > 추려내기작업에 대한 옵션을 설정하겠다는 키 입니다.

D3DCULL_CCW -> 반시계 방향으로 그려지는 면에 대해서 랜더링을 하지 않는 옵션입니다.

D3DCULL_CW -> 시계 방향으로 그려지는 면에 대해서 랜더링을 하지 않는 옵션입니다.

D3DCULL_NONE -> 추려내기를 하지 않습니다.

 

조명(빛이 있으라!!!)  ------------------------------------------------------------------------------------

 

우리가 눈으로 사물의 색과 빛 명암을 구분해낼 수 있는 것은 밫이 있기 때문입니다.

3D프로그래밍에서도 마찬가지 입니다. 각 면에 빛이 비춰져야 어떠한 색깔인지 모양을 가지고 있는지 알수가 있습니다.

조명이란 이렇게 폴리곤에 빛을 비춰서 반사되는 빛과 명암 색상을 만들어 내는 작업니다.

 

클리핑 ------------------------------------------------------------------------------------

 

앞서 후면 추려내기 작업에서 말씀했듯이 랜더링 작업은 많은 컴퓨터의 CPU나 GPU에 가장 많은 연산을 필요로 하는 작업입니다.

뷰스페이스까지 변환되었다면 화면에 보여져야할 폴리곤들이 있을 것입니다.

하지만 반대로 시야 바깥에 존재하는 보이지 않아야할 폴리곤들도 있을 것입니다.

클리핑이란 후면추려내기의 후면처럼 시야 밖에 보이지 않아야할 폴리곤들을 랜더링하지 않게 구분하는 작업을 의미합니다.

 

투영 ------------------------------------------------------------------------------------

클리핑 작업까지는 3D의 모든 작업이 진행되고 바라보는 카메라로 월드에서의 입체전환이 되어있는 상태를 의미합니다.

3D 세계에서 2D화면을 얻어내야 하는 작업이 남았습니다.

왜 어렵게 3D환경을 구성해 놓은걸 2D화면을 얻어내야 할까요?

모니터가 평면이기 때문입니다. 어차피 우리가 보이는 것은 모니터에 표시된 픽셀로 이루어진 2D화면을 보고 있는 것입니다. 즉 3D로 구현된 세상을

모니터로 옮기전에 2D화면으로 만드는 작업을 투영이라고 합니다. (아직 모니터에 옮긴 것은 아닙니다.)

투영작업은 투영좌표계라고 하는 특수한 좌표계로 옮겨지며 다음과 같은 좌표를 가지고 있습니다.

 

 

다음의 함수를 통해서 투영행렬을 꾸밀 수 있습니다.

 

D3DXMATRIX matProj 리턴받을 투영행렬을 받을 행렬의 주소값

FLOAT fFovy -> 시야각의 수직 영역 (라디안)

FLOAT fAspect -> 종횡비 = 너비 / 높이

FLOAT fNear -> 가까운 평면까지의 거리

FLOAT fFar -> 먼 평면까지의 거리

 D3DXMatrixPerspectiveFovLH(&matProj,  fFovy, fAspect, fNear, fFar);

 

뷰포트 변환 ------------------------------------------------------------------------------------

뷰포트란 투영된 각 이미지들을 모니터에 표시할수있는 크기로 다시 변환하는 작업을 의미합니다.

뷰포트를 통해서 화면의 해상도등을 정해줄수 있습니다.

또한 뷰포트 변환은 최종적으로 랜더링이 종료될때 장치에서 자동으로 처리해 줍니다.

 

 D3DVIEWPORT9 vp;

Device->GetViewport(&vp); -> 뷰포트를 가져오고

 

 vp.X = 100; -> 뷰포트를 다시 설정한다.
 vp.Y = 100;
 vp.Width = 300;
 vp.Height = 300;

 

Device->SetViewport(&vp); -> 뷰포트를 세팅한다.

 

래스터라이즈 ------------------------------------------------------------------------------------

스크린 좌표로 버택스들을 변환한다음 2d삼각형들이 그려지게 되는데. 말그대로 모니터의 픽셀하나하나를 계산해서 작업하게 되므로

엄청난 연산이 필요하게 됩니다. 그래서 반드시 전용 그래픽 하드웨어를 통해서 처리 되어야 합니다.

이 래스터 라이즈의 작업까지 끝이나야 모니터 화면에 표시되는 2d이미지가 표시됩니다.

 

Posted by JJOREG

 

'게임개발공부 > LUA공부' 카테고리의 다른 글

테이블  (0) 2014.01.13
문자열 정리  (0) 2014.01.13
연산자.  (0) 2014.01.13
루아의 변수 선언과 규칙.  (0) 2014.01.13
루아스크립트 콘솔에서 실행하기  (0) 2014.01.13
Posted by JJOREG