꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by zinzza
안녕하세요 zinzza 라는 ID를 사용하는 연두父 이경배라고 합니다.
 

연제 일자도 없는 놈이 난입을 해버렸습니다. 
이 점 사과드리며, GameDevForever에 글을 기고할만한 실력이 안되는 놈이라 그냥 관리만 하려다가 가끔은 그냥 가벼운 글로 시간을 때워도 좋지 않나 하는 생각에 글을 써봅니다.

이 글은 게임 기획에 대한 특별한 철학도 없고 대책도 없는 놈이 쓴 글이니 너무 깊게 이해하고 참고하시면 안됩니다.
그저 가벼운 마음으로 저런놈도 있군아... 정도로만 봐주세요^^;


저는 언젠가 부터 게임이 재미가 없습니다.

그 가장 큰 이유는 요즘 게임이 너무 어렵다는 점입니다.
게임이라는게 위키백과에서 찾아보면 "게임(game)은 일정한 규칙에 따라 승부를 겨루거나 즐기는 놀이이다"(http://ko.wikipedia.org/wiki/게임) 라고 나와있습니다. 
어찌되었든 게임은 즐겁게 즐기는 여가의 의미가 있는데 여가를 즐기기 위해 공부를 해야 한다? 
다시 말해 스트레스를 받으면 안된다고 생각하기 때문이지요.
요즘게임은 너무 복잡하더라구요, 저는 캐릭터에게 HP, MP 두가지 이외의 수치가 있으면 머리가 아픕니다-_-;

그리고 그 다음 이유는 게임이 대부분 거기서 거기라는 느낌을 준다는겁니다.
여러가지 이유가 있겠지만 너무나 많은 게임이 나왔고 , 그 덕분에 게임의 기획, 개발 기술이 상향 평준화가 된 게 아닌가 하는 생각도 들긴 하네요.

제가 느끼는건
최근 인기가 많아 보이는 대부분의 FPS는 둠같습니다-_-;
일본 RPG는 FF, 드래곤퀘스트와 비슷하고요, 대전은 스트리트파이터, 전투형 MMO는 죄다 리니지, MMO에서 전투보다 생산등에 초점이 맞으면 울티마, MMO에서 퀘스트 위주라면 와우, RTS는 워크래프트(사실은 듄...)...

어떤 게임이 나와도 "이건 뭐랑뭐랑 합친거네 뭐~" 이딴생각이 듭니다.
그러니 시작도 하기전에 재미가 없지요.

약간 비약하면 이렇습니다.-_-;;;;;

제가 좀 과민반응하는 스타일일 수도 있습니다.
SF영화에서 아무리 특이하게 생긴 총을 봐도 감흥이 안오거든요.
어차피 "뭔가가 발사되서 맞아 죽는것!" 이라는 정의에서 벗어나는게 없으니까요^^;
그나마 하나 희안한거 찾은게 "화성침공"이지요.
 

저거 맞으면 그냥 녹습니다.  그리고 몸을 관통하지요.
그냥 방아쇠 당기고 좌우로 흔들면 다 죽는겁니다-_-;

더 많지만 하나만 더하면,
 
진우님의 트윗. 공감합니다.

기획보다는 기술이 우선시 되는 현상이 아닌가 생각됩니다.
물론 기술도 중요하고, 눈에 확 띄는건 기술력이지만 기술은 기획을 구현하기 위한 도구라 생각입니다.
이런 소리 하면서 기술적으로 말도 안되는 기획을 하면 안되겠지만요^^

그래서 그런가 최근 해외 게임은 스토리가 많이 중요시 되는거 같다는 생각입니다.
게임성의 한계에 도달하니 그냥 한편의 영화를 본 것처럼 게임을 즐기는 방향으로...

아무튼 그런 이유로 저는 게임이 재미 없습니다만, 간혹 획기적인 기획을 한 게임들이 눈에 들어옵니다.

그런 게임을 좀 소개해보면 말이죠...

로코로코

정말 깜딱! 놀란 게임입니다.

저 노란 애들을 대굴대굴 굴려서 합체? 했다가 분해? 됐다가...
솔직히 이거 명확하게 글로 설명하실 수 있는 분이 계신가요-_-?
그냥 동영상 보시죠-_-;
http://www.youtube.com/watch?v=Lej_ZuuHsIA 
기획서 한번 봤으면 좋겠단 생각이 절로 드는 게임입니다.

파타퐁


획기적이다... 라고 생각했지만 기획이 로코로코만큼 획기적이진 않습니다.
리듬액션 게임에 디펜스가 접목됐다고 해야 하나요?
특정한 커맨드를 리듬에 맞춰 넣으면 공격, 이동, 후퇴, 마법등을 사용할 수 있습니다.
시나리오도 아기자기 재미있고, 상당히 독창적인 게임이긴 합니다.

이것도 동영상^^
http://youtu.be/l73pqObHr9w  

용사주제에 건방지다(이것도 디펜스?)

용사주제에 건방지다

이게임은 마왕을 잡으러 오는 용사를 물리치는 디펜스게임 입니다.
예전에 던전키퍼같은 게임에서 나왔던 시나리오지만... 
그러고 보니 이것도 조금 귀여운 시나리오에 디펜스네요. 젠장 동영상 패스-_-;

크래용 피직스

제가 해본 게임중에 가장 획기적이었다고 느낀 게임입니다.
보자마자 헉~! 했던 게임이지요.
물리엔진을 확실히 활용한 게임인데...제가 참 설명을 못하나보네요.
동영상링크신공-o-+ 
http://youtu.be/ii85cL2Uumw  

위룰등(소셜)

제가 처음 해본 소셜게임이지요.
소셜하지 못하게 거의 혼자 했지만-_-;
아무튼 소셜이라는 새로운 시도를 한 게임입니다.
 
몇가지 게임을 너저분하게 적고, 적고보니 무슨 게임 리뷰처럼 되버렸는데요
게임이라는게 화면에 멋지게 뭔가를 보여준다거나 대하드라마처럼 어마어마한 스토리를 가져야 하는건 아닐껍니다.
그저 사람들이 재미있게 즐길 수 있는걸 만들어야 하지 않나 하는 생각입니다.

그리고 제가 참 중요하게 생각하는 뭔가 새로운 게임. 새로운 시도를 하는 게임. 
기획자들은 이런 게임 좀 기획해주세요!

...........써놓고나니... 굽신굽신 글이네요ㅡ,.ㅡ;
반응형
,
Posted by 알 수 없는 사용자

나... 나도 병렬 프로그래밍 할래!! - #2

친절한 티스님이 연재하고 계시는 병맛.... 아니 병렬 프로그래밍에 대해서, 저도 관심이 많아서, 숟가락만 올리려고 합니다. ^^

시대가 병렬 프로그래밍을 원하는 시대이지만, 서버플머가 아닌 입장에서 거의 초보적인 지식만을 가지고 있는 상태입니다. 
따라서, 
이제 막 병렬 프로그래밍을 본격적으로 공부해보려고 하는 입장에서 과연 무엇을 먼저 공부해야 하나? 라는 생각이 들었습니다. 그래서, 차근 차근 밟아나가자를 생각에 만만한(?) "동기화 관련 내용부터 정리해보자!" 라고 결정했습니다. 
 


이미 많은 분들이 사용하시고 계시고, 아시는 내용이지만, 저 같이 잘 모르는 사람들도 있으니, 기초를 다지는 마음으로 정리하도록 해보겠습니다. (꼬꼬마들만 봅시다... ^^)



동기화(Synchronization)이란 무엇인가?

병렬 처리에서 동기화라는 것은 서로 다른 쓰레드 간의 실행 순서를 보장하기 위해서, 구속조건을 강제로 거는 것입니다.
동시에 여러개의 쓰레드가 동일한 자료를 접근하여 조작하고, 그 실행 결과를 접근하는 특정 순서에 의존하는 상황을 "경쟁 상황(race condition)"이라고 부릅니다. 
즉, 동기화란 쓰레드의 실행 순서를 구성하고 공유하는 데이터를 관리하는 것을 말합니다. 

동기화를 처리하는 유형은 일반적으로 상호배제(mutual exclusion)와 상태 동기화(condition synchronization)을 많이 사용하게 됩니다.
- 상호배제(mutual exclusion)
: 쓰레드가 공유 데이터를 사용하는 부분을 동기화 객체(critical section 등)로 블록시키는 방식입니다. 
상태 동기화(condition synchronization) 
: 시스템이 특정 상태로 도달하기 전까지는 쓰레드를 완전히 블록(block)시키는 방식입니다. 
 
동기화객체

- Critical Section
커널 객체를 사용하지 않으며, 그 내부 구조가 단순하기 때문에 동기화 처리를 하는데 있어서 속도가 빠르다는 장점이 있습니다. 하지만, 동일한 프로세스에서만 사용이 가능하다는 제약이 있습니다.
동일한 프로세스에서 다른 쓰레드의 방해를 받지 말아야 하는 작업을 할 때, 이 영역을 크리티컬 섹션으로 둘러싸면, 전역 자원의 독점권을 획득하게 됩니다.
 

커널 객체를 사용하는 동기화의 경우에는 신호 상태(Signaled)와 비신호 상태(Non-Signaled)의 두 가지 상태 중 하나로 존재를 하게 되며, 동기화 객체가 신호 상태(Signaled)가 될 때까지 이 커널 객체를 사용하려는 쓰레드는 대기하게 됩니다.
이런 경우, 대기 함수와 함께 사용하게 되는데, 대기 함수는 일정한 조건에 따라 스레드의 실행을 블록하거나 실행을 허가하 함수를 말합니다. 가장 대표적인 함수가 바로, "WaitForSingleObject" 입니다.

- Mutex
뮤텍스는 오직 한 쓰레드에 의해서만 소유될 수 있으며 일단 어떤 쓰레드에서 소유되면 비신호 상태가 됩니다. 반대로 어떤 쓰레드에서도 소유되어 있지 않는다면, 신호 상태가 됩니다. 기본적으로 크리티컬 섹션과 유사하게 사용이 가능합니다. 이름을 기반으로 프로세스 사이에서의 동기화에서도 사용이 가능하기는 합니다만, 그만큼 속도가 느립니다.
뮤텍스는 쓰레드가 여러 개 있더라도 자신이 소유한 쓰레드를 기억하고 있으며, 같은 쓰레드가 같은 뮤텍스를 중복 호출하더라도 데드락이 발생하지 않게 하고 있습니다. (내부적으로 카운트 체크를 해서, 카운트가 0이 되었을 때 신호 상태로 돌려줌)

- Semaphore
기본적으로 뮤텍스와 비슷하게 처리가 됩니다만, 세마포어는 사용자가 지정한 개수만큼 보호되는 자원에 대해서 접근할 수 있도록 합니다. 유효자원의 카운트가 0이 되면 세마포어는 비신호 상태가 되고, 카운트가 1 이상이면 신호 상태가 됩니다. 다른 쓰레드가 자원을 풀 때까지 대기하게 되고 세마포어가 1 이상이면 유효자원의 개수를 1 감소 시키고 곧바로 리턴합니다. 
 
- Event 

특정 사건의 발생을 다른 쓰레드에 알리는 경우에 사용합니다. 이벤트를 사용한 동기화는 다음과 같은 형태가 됩니다.
"이벤트 E를 비신호 상태로 생성. A 쓰레드가 작업을 진행하고 B 쓰레드는 E가 신호 상태가 될 때까지 대기한다. A 쓰레드가 작업을 완료하면, 이벤트 E를 신호 상태로 변경한다. B 쓰레드가 작업을 시작함"   

deadlock

절대 가용되지 않을 다른 쓰레드의 리소스를 기다리는 때 발생합니다. 

Deadlock이 발생하는 시나리오는 다음과 같습니다.
- self-deadlock
쓰레드가 임의의 타이밍에서 걸었던 lock이 풀리기 전 특정 타이밍에 그것을 사용하려고 할 때 발생. 한 쓰레드에서 일어나게 됨.

recursive deadlock
A 쓰레드를 깨우는 작업을 B에서 할 때

lock-ordering deadlock
2개 이상의 프로세스(쓰레드)들이 자원을 점유한 상태에서 각각 다른 프로세스(쓰레드)들이 점유한 자원을 요청하여, 서로 상대방의 작업이 끝나기를 무한정 기다리는 상태


Lock-Free

Volatile
volatile은 해당 객체(변수)가 OS나 하드웨어 또는 다른 쓰레드에 의해 변경될 수 있다고 알려 주는 키워드 입니다. 즉, 이는 컴파일러에게 해당 객체의 값이 언제든지 변경될 수 있으니, 최적화를 통해서 값을 미리 예상하지 말고, 항상 해당 객체를 참조하는 시점에서 값을 매번 다시 읽도록 하라고 알려주는 것입니다.

일반적으로 컴파일러는 소스 코드를 분석하여, 해당 변수에 대한 값이 변경되지 않을 것으로 예측되면, 이 값을 미리 저장해 두었다가 사용하는 방식으로 최적화를 수행합니다. 

하지만, 멀티 쓰레드 환경에서는 현재 실행되는 쓰레드에 의해서 값이 변경될수가 있습니다. 이를 때 volatile 키워드를 이용할 수 있습니다. volatile 로 선언된 변수는 실제 사용할 시점에서의 값을 참조하기 때문에, 동기화를 처리한 것과 같은 결과를 얻을 수 있습니다.

- Spinlock
"조금만 기다리면 바로 쓸 수 있는데 굳이 컨텍스트 스위칭으로 부하를 줄 필요가 있나?"

[출처] Spin-Lock/Mutex/Semaphore|작성자 즐겁게

 라는 컨셉으로 개발된 것으로 크리티컬 섹션에 진입이 불가능할 때 컨텍스트 스위칭을 하지 않고, 잠시 루프를 돌면서 재시도 하는 것을 말합니다. 

spinlock은 lock을 얻을 수 없다면, 계속 lock을 확인하며 얻을 때 까지 기다립니다. 즉, 무한루프를 돌면서 최대한 다른 스레드에서 CPU를 양보하지 않는 것입니다. lock이 바로 사용이 가능해질 경우에는 컨텍스트 스위칭의 비용을 줄여 CPU의 부담을 줄여줄 수 있지만, 반대로 다른 쓰레드가 lock을 오래 동안 유지하게 된다면, 오히려 CPU 시간을 너무 많이 소모하게 됩니다. 

따라서, Lock/Unlock 시간이 아주 짧아서 락을 하는 경우가 드믄 경우에 유용하게 사용해야 합니다.
 
- Lockfree algorithm
Lockfree 알고리즘을 이해하기 위해서는 먼저 Atomic Operation에 대해서 알아볼 필요가 있습니다.

atomic operation의 정의
1. 일련의 모든 연산이 끝날 때까지 다른 프로세스는 그 연산에 대한 어떠한 변화도 할 수 없다.
2. 전체 연산 중 어느 하나라도 실패할 경우, 모든 연산은 실패하여, 시스템은 전체 연산이 시작하기 전의 상태로 복구된다.

32비트 인텔 CPU (IA-32)에서 지원되는 Atomic Operation에 관해 알아보자. 다음과 같은 인텔의 CPU 연산들 앞에 LOCK을 붙여 해당 연산을 Atomic하게 만들 수 있다. 즉, 스레드간의 동기화를 신경쓰지 않아도 된다. 자세한 것은 인텔사 웹싸이트에서 제공되는
 개발자 매뉴얼을 참고하기 바란다.

ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XA
 
Win32의 Atomic Operation은 Interlockedxxx() 함수로 제공된다. 
간단하게, 차이를 살펴보면 다음과 같다.
  // atomic하다.
  GlobalCounter = 0;   
 
// 이것은 atomic하지 않기 때문에, 멀티 쓰레드에서는 결과를 예측할 수 없다.   ++GlobalCounter;
// atomic incremenet  
 InterlockedIncrement(&GlobalCounter);


Lock-free는 Spinlock과 유사하게 작업을 완료하는 내부적으로 루프를 돌면서 재시도를 하는 방식입니다.
Lock-free 알고리즘에서는 비교 후 교환(Compare-And-Swap)이 가장 중요합니다. 그 이유는 Lock-free 에서는 Lock을 사용하지 않기 때문에, 다른 쓰레드가 이미 그 변수의 값을 변경했을 수가 있습니다. 물론, 발생 확률은 대체로 낮으므로, 변경되지 않았을 것이라고 가정하는 낙관적인(Optimistic) 방법이 Lock-free에 사용됩니다. 따라서, CAS(Compare-And-Swap: InterlockedCompareExchange)을 이용해서, 공유변수가 가정하는 값과 같으면 다음 단계로 넘어가고, 만약 변경되었다면, 재시도하는 방법으로 Lock 없이 동기화가 가능하도록 프로그래밍을 할 수 있습니다.
(즉, InterlockedCompareExchange(&v, b, a) 라는 함수를 보면, v의 값이 a와 같다면, v에 b를 대입하라는 의미입니다. 리턴값은 v가 값이 바뀌기 전의 v의 값을 나타냅니다.)

간단하게, Stack Push에 대한 예제를 보도록 하겠습니다.
void Push(Node* pNewNode)
{
 do {
   Node* t = pTopNode;
   pNewNode->pNext = t;
 } while( !CAS(&pTopNode, t, pNewNode) );
}

마무리
지금까지 병렬프로그래밍에서 기본적으로 알고 있어야 하는 동기화에 관련해서 정리를 해보았습니다. 당연한 이야기 이지만, 동기화에 대해서는 완벽하게 알고 있어야, 병렬 프로그래밍을 잘 할 수 있겠죠?! ㅎ. 특히, 최근에 주목을 받고 있는 Lock-Free와 같은 알고리즘은 그 효능(?)에 대해서 아직도 의견이 엇갈리고 있기도 합니다.
(기초적이고 많이 알려진 내용이라 자세하게 설명하는 것 보다는 간단하게 성격만을 정리하였습니다. (사실 저도 잘 알지 못합니다.. ^^;; 인터넷을 통해서, 관련된 내용의 샘플소스를 같이 보시면 더욱 좋을 것 같습니다.)

하지만, 동기화처리를 통해서 모든 것을 할 수 없기 때문에, 얼마난 논리적으로 전체적으로 설계를 잘 하느냐?가 중요할 것입니다.  단지, 동기화 처리에 대한 개념이 이제 시작일 뿐입니다. 


저의 입방에서는 궁극적으로는 "효율적인 Multithread 기반 게임 엔진을 만들 수 있을까?"혹은 "게임엔진에서 병렬프로그래밍을 최대한 활용할 수 있을까?"에 대한 답을 얻는 것이 목적입니다.

앞으로는 이와 관련해서, 데이터 처리, 멀티코어 엔진 아키텍쳐 등과 같은 내용들에 대해서 아마 지속적으로 볼 생각입니다.
(사실 이 주제는 "친절한 티스"님의 주제고 저는 그냥 숟가락만 올렸을 뿐입니다. ㅎㅎㅎ)

어설픈 내용을 봐주셔서, 감사합니다. ㅎㅎ
 

참고자료
-  Window 구조와 원리 그리고 Codes (정덕영)
-  Threading and Parallel Programming Constructs
-  Lockless Programming Considerations for Xbox360 and Microsoft Windows  
-  Non-blocking algorithm
http://choiwonwoo.egloos.com/1176220 : Atomic Operation
-  Kasa Study : Lock-Free (김성헌)
반응형
,
Posted by 알 수 없는 사용자

Oren-Nayar Reflectance Lighting Model


안녕하세요 11일, 26일 연재를 맡게 된 풍풍풍이라고 합니다 __)
 

저는 여기 계시는 대단한 분들과는 다르며 고렙이 되고 싶은 신입 프로그래머일뿐 제가 공부하며 정리한것을 좋게 보신
포프아저씨의 낚시(?)에 글 을 올리는것 뿐이라고 사전공지를 드리겠습니다 ( 베이스 깔기 스킬을 발동하였습니다 )
나눠서 연재하기 스킬을 시전하려 했으나.. 걍 한번에 올렸습니다.. 
 

그렇게 살지 않기 위해 열심히 하고 있습니다__)


여기에 계신 대략 쩌시는 프로그래머 분들 및 아티스트 기획자 분들이 더 난이도 있는 것을 설명해 주실것이고 -.-
난이도가 너무 데스윙만 나오면 재미 없으니 가끔씩 트롤도 나와줘야 한다는 결론하에 글을 올리게 되었습니다

물론!! 틀릴 수도 있습니다 __) 그때는 과감히!!!! 피드백을 주시진 마시고...

.....살며시 해주세요
 


오늘 할 것은 Oren-Nayer 조명 모델입니다
개념부터 차근차근 살펴 보겠습니다

 개념.

 

Oren-Nayer 조명 모델이란

Michael Oren 과  Shree K. Nayar가  Generalization of Lambert’s reflectance model”, Michael Oren and Shree K. Nayar  논문에서 발표한 모델로 거친 표면으로부터의 diffuse 를 표현하기 위한 reflectance model 이라고 합니다.

(거친 표면으로부터의 Specular를 고려한 모델은 Cook-Torrance Model입니다)

 

Lambert 의 diffuse model 은 모든 방향에서 같은 복사량을 취하게 된다는 가정을 깔고 있으며, 

플라스틱이나 모래와 같이 반들반들한 표면을 가진 재질 적합하다고 할 수 있습니다. 


하지만 현실 세계에서는 거친 표면을 가진 재질들도 많이 있지요
 





( 각 Specular Reflectance에 따른 Diffuse Model의 비교)

하이라이트에는 변화가 없으나 Diffuse가 Oren-Nayer 쪽이 약간 더 어두운 형태를 띄고 있습니다
 

 나이키 광고 인데..  하늘에 있는 달이  Oren-Nayer 방식을 사용해서 렌더링 되었습니다 @_@

 

 

Oren-Nayer Model의 공식을 유도할 때 사용되는 거친 표면(roughness Surface)이라는 것은

서로 다른 각도를 가진 Torrance 와 Sparrow에 의해 제안된 미세면(microfacet)들의 집합으로서 설명될 수 있습니다.

 

여기에서 미세면의 표면은 길고 대칭적인 V 자형태의 굴곡(cavity)으로 구성된다고 가정합니다.

또한, 각 cavity 는 두 개의 평면의 면으로 구성됩니다

 

면의 거친 정도는 경사진 면의 분산을 위한 확률 함수로 결정되는데, 보통  Gaussian distribution  이 사용됩니다

 


 

Oren-Nayar reflectance model 에서 각 면은 Lambetr reflectance model 을 따른다고 가정합니다.

 


 

여기에서  
 는 입사광선의 radiance이며, 
 은 반사 광선의 radiance입니다.

 

해당 표현은 구면 좌표계에서의 좌표 표현법으로 원래는 
와 같이 세 개의 정보를 사용해야 하지만

우리가 사용하는 것은 단위 vector 이기 때문에 원점으로부터의 거리를 의미하는 
 에 대한 정보는 필요로 하지 않습니다.

 

따라서 구하는 것은 
 이며 
 

는 양의 Z 축과 이루는 각도를 의미하고, 

 는 Z 축을 축으로 했을 때 양의 방향 X 축과 이루는 각을 의미합니다

 

또한 
 와 
의 vector 를 알고 있다면 vector의 성분을 이용해 아래와 같이 구할 수 있습니다

 




 

 

 공식.

  

Oren-Nayar model 에서는 미세면의 방향에 의한 빛에 반사량, 자기차폐, 자기반사의 효과가 포함되어 있으며
공식은 다음과 같이 표현됩니다

 

 

과정은 이렇게 되겠군요@_@
 

여기에서 A = B 는

 


,  

 

이고 

 

을 통해 아래의
 


 

 
를 구합니다

 

이때의 
 는 surface 의 조도계수(albedo) 이며, 
 는 [ 0.0, 1.0 ] 범위를 가지는 surface 의 거친 정도를 의미합니다.

만약 
가 0 이라면(즉, 매끄러운 표면이라면
  이 되어 다음과 같이 되며

 

 


 

N과 L의 두벡터를 normalize해서 계산하므로 길이가 전부 1이고



이 말은 
   일때의 값은  
와 같다는 말이므로

가 0일때는 Lambert 방식과 대충 비슷한 양상을 띠게 됩니다


 

그래도 계속 봅시다.... 0-.-0
 

 Lamber model 과의 비교.

 

 

아래 그림은 영문위키에 있는 Oren-Nayar 와 Lambertian 의 Brightness 를 비교한 것입니다

여기서 Measurements 는 실제 model 에서의 측정값을 나타내고 있습니다

 




 

이 graph 를 보면 Oren-Nayar 의 경우에는 면과 광원의 각이 거의 수직이 되어 갈 때 밝기가 급격하게 변하지만,
Lambertian 의 경우에는 완만하게 변하고 있다라는 것을 알 수 있지요 @_@

3개의 모델을 비교해 보겠습니다 



 

 

Lambertian model 에 익숙해져 있기 때문에 가운데 그림이 제일 자연스러워 보일 수도 있으나, 

실제 이미지는 더 많은 정보를 가지고 있기에 ( 거칠은 정도까지 나타내는 부분을 포함해서  )

실제 Real Image에 가까운 것은 Oren-Nayar Model 이라고 할 수 있습니다 ( 실제 결과도 그러함 )

 

아래 그림은  
 값에 따른 변화를 보여 줍니다.

 


 

Specular 는?

 

Oren-Nayar model 은 diffuse model 이기 때문에 specular 성분은 고려하고 있지 않습니다.

그러므로 다른 Specluar 모델과 혼합하여 표현해야 합니다

 

 specular model에는 다음과 같은 것들이 있는데요

 

  • Phong
  • Blinn - Phong
  • Gaussian Distribution
  • Beckmann Distribution
  • Ward anisotropic Distribution
  • Cook - torrance Model

 
저 부분에 대해서는 다른 훌륭하신 분들이 해주실거라고... 믿습니다...

 ........

 


구현

  

구현에 있어 특이한 사항은 원래 world 의 좌표계를 원점으로 하는 구면 좌표계를 사용하지 않고,

normal 을 up 으로 하는 구면 좌표계를 사용한다는 것인데요.


왜냐하면 어차피 
 
 가 관계를 표현하기 위한 요소여서 꼭 특정 좌표계에 종속될 필요가 없기 때문입니다.

 

그러므로 아래와 같은 표현이 가능합니다@_@ 

 

 




 

  

위의 공식들을 HLSL에서 표현해보면


float4 psOrenNayarSimple
        (
            in VS_OUTPUT f,
            uniform bool UseLookUpTexture
        ) : SV_TARGET
{
    // Make sure the interpolated inputs and
    // constant parameters are normalized
    float3 n = normalize( f.normal );
    float3 l = normalize( -vLightDirection );
    float3 v = normalize( pCameraPosition - f.world );

    // Compute the other aliases
    float gamma   = dot
                    (
                        v - n * dot( v, n ),
                        l - n * dot( l, n )
                    );

    float rough_sq = fRoughness * fRoughness;

    float A = 1.0f - 0.5f * (rough_sq / (rough_sq + 0.33f));

    float B = 0.45f * (rough_sq / (rough_sq + 0.09));

    float C;
    if( UseLookUpTexture )
    {
        // The two dot-products will be in the range of
        // 0.0 to 1.0 which is perfect for a texture lookup:
        float tc = float2
                    (
                        (VdotN + 1.0f) / 2.0f,
                        (LdotN + 1.0f) / 2.0f
                    );
    C = texSinTanLookup.Sample( DefaultSampler, tc ).r;
    }
    else
    {
        float alpha = max( acos( dot( v, n ) ), acos( dot( l, n ) ) );
        float beta  = min( acos( dot( v, n ) ), acos( dot( l, n ) ) );

        C = sin(alpha) * tan(beta);
    }

    float3 final = (A + B * max( 0.0f, gamma ) * C);

    return float4( cDiffuse * max( 0.0f, dot( n, l ) ) * final, 1.0f );
}

 

 

이때 cDiffuse = ( Albedo / PI ) 입니다.

 

잘보면 UseLookUpTexture 부분이 있는데 sin()과 tan() 함수에 여전히 종속적인 부분을

Texture Fetch를 통하여 대체 하는 방식을 사용하는 부분을 말합니다.

 

위와같은 sin()과 tan()함수를 계산한 텍스처를 참조해서 사용합니다

 

아래는 HLSL에서 참조되는 룩업텍스처를 만드는 방법을 나타내고 있습니다.

 

HRESULT CreateSinTanLookupTexture( ID3D10Device* pDevice )
{
    HRESULT hr = S_OK;

    // The incoming dot-product will be between 0.0 and 1.0
    // covering 180 degrees thus a look-up of 512 pixels
    // gives roughly 1/3rd of a degree accuracy which
    // should be enough...
    const UINT LOOKUP_DIMENSION = 512;

    // Describe the texture
    D3D10_TEXTURE2D_DESC texDesc;
    texDesc.ArraySize            = 1;
    texDesc.BindFlags            = D3D10_BIND_SHADER_RESOURCE;
    texDesc.CPUAccessFlags       = 0;
    texDesc.Format               = DXGI_FORMAT_R32_FLOAT;
    texDesc.Height               = LOOKUP_DIMENSION;
    texDesc.Width                = LOOKUP_DIMENSION;
    texDesc.MipLevels            = 1;
    texDesc.MiscFlags            = 0;
    texDesc.SampleDesc.Count     = 1;
    texDesc.SampleDesc.Quality   = 0;
    texDesc.Usage                = D3D10_USAGE_IMMUTABLE;

    // Generate the initial data
    float* fLookup = new float[ LOOKUP_DIMENSION * LOOKUP_DIMENSION ];

    for( UINT x = 0; x < LOOKUP_DIMENSION; ++x )
    {
        for( UINT y = 0; y < LOOKUP_DIMENSION; ++y )
        {
            // This following fragment is a direct conversion of
            // the code that appears in the HLSL shader
            float VdotN = static_cast< float >( x )
                        / static_cast< float >( LOOKUP_DIMENSION );
            float LdotN = static_cast< float >( y )
                        / static_cast< float >( LOOKUP_DIMENSION );

            // Convert the 0.0..1.0 ranges to be -1.0..+1.0
            VdotN *= 2.0f;
            VdotN -= 1.0f;

            LdotN *= 2.0f;
            LdotN -= 1.0f;

            float alpha = max( acosf( VdotN ), acosf( LdotN ) );
            float beta  = min( acosf( VdotN ), acosf( LdotN ) );

            fLookup[ x + y * LOOKUP_DIMENSION ]
                    = sinf( alpha ) * tanf( beta );
        }
    }

    D3D10_SUBRESOURCE_DATA initialData;
    initialData.pSysMem             = fLookup;
    initialData.SysMemPitch         = sizeof(float) * LOOKUP_DIMENSION;
    initialData.SysMemSlicePitch    = 0;

    // Create the actual texture
    hr = pDevice->CreateTexture2D
                        (
                            &texDesc,
                            &initialData,
                            &g_pSinTanTexture
                        );
    if( FAILED( hr ) )
    {
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    // Create a view onto the texture
    ID3D10ShaderResourceView* pLookupRV = NULL;

    hr = pDevice->CreateShaderResourceView
                                    (
                                        g_pSinTanTexture,
                                        NULL,
                                        &pLookupRV
                                    );
    if( FAILED( hr ) )
    {
        SAFE_RELEASE( pLookupRV );
        SAFE_RELEASE( g_pSinTanTexture );
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    // Bind it to the effect variable
    ID3D10EffectShaderResourceVariable *pFXVar
    = g_pEffect->GetVariableByName("texSinTanLookup")->AsShaderResource( );
    if( !pFXVar->IsValid() )
    {
        SAFE_RELEASE( pLookupRV );
        SAFE_RELEASE( g_pSinTanTexture );
        SAFE_DELETE_ARRAY( fLookup );

        return hr;
    }

    pFXVar->SetResource( pLookupRV );

    // Clear up any intermediary resources
    SAFE_RELEASE( pLookupRV );
    SAFE_DELETE_ARRAY( fLookup );

    return hr;
}

 

 

일부 작업들 중에는 룩업텍스처 사용이 오히려 보틀넥의 주된 원인이 될수 있으므로

이 부분은 
상황에 맞게 합리적인 생각을 하여 처리를 하는것이 좋습니다

 여기까지 오느라 수고하셨습니다..__)

 

 


구현은 위의 파일을 다운받아 렌더몽키로 돌리면 되시겠구요 ㅇ_ㅇ
 
각 값들을 이리저리 조절해보면서 이런거구나 하시면 되겠습니다 __)

 

구현한 것을 보시면  
 를 0 으로 해도 실제 Lambert 방식과 같은 밝기는 나오지 않는데 이유는 공식을 보면 알 수있습니다.


다시 위에 있던 공식을 보시죠...

 



위의 공식에서  
이면 괄호안이 소거가 되므로 아래와 같은 식이 되며

Lambert Model과 일치하다고 말씀드렸습니다

 




구현에서는 
와 
를 둘다 1 로 잡았으니, 최종결과는 
 에 의해 결정됩니다.

따라서 
를 조절하여 비교하면 대충 비슷한 밝기가 나옵니다. (저는 1.0 정도가 비슷한밝기 였다능...)

( 주의 : 비교를 위한 조절일뿐, 정확하게는
 를 곱하는 것이 맞으며, 필요에 따라 조절하는것은 재량입니다)

  

 

 

Lambert


Oren-Nayer  
= 0

 

Oren-Nayer  
 = 0.5
 


 Oren-Nayer 
= 1.0


블라인드 렌더러님에 블로그에는 디퍼드 렌더링에서도 사용가능한 Oren-Nayer 코드나

텍스처 룩업을 사용하지 않고 근사치를 사용해 최적화 한 Oren-Nayer 코드가 있습니다 __)

 (포프 아저씨에게 떠넘기기 & 적절한 블로그 홍보)
 

디퍼드 렌더링 엔진에서 Oren-Nayer 사용하기

텍스처 룩업을 사용하지 않고 최적화 한 Oren-Nayer
 
----------------------------------------------------------------------------------------------------------------------------------


간단히(?) 알아보았는데요 @_@  틀린게 많을 것 같아 걱정도 많이 됩니다 ㅠ.ㅠ

수식 같은게 기억 나지 않으셔도..


 
              거친 표면으로부터의 diffuse 를 표현하기 위한 reflectance model이다!!! 
 
 
 
라고 정도만 기억하셔도 이 글을 올린 보람은 충분히 있다고 생각합니다 

나머지는 필요할때마다 참조 하셔도 되고 그 외에 많은 자료들이 있을 것입니다
다 같이 열심히 하셔서 고렙들이 되는 그날 까지 화이팅입니다^^
 

반응형
,
Posted by 알 수 없는 사용자

01. what
02. how
03. storytelling
04. prototyping 上
05. prototyping 下

시작하기에 앞서, 존 라도프가 지은 'Gamification & 소셜게임'이라는 책의 165페이지에서 발췌한 다음의 내용을 살펴보자.


게 임은 겉으로는 단순한 오락거리로 보일지도 모르지만, 우리의 마음을 사로잡는 이유는 인간의 심리학적, 생리학적 동기요인에 작용하기 때문이다. 실제로 이것이 무슨 말인지 살펴보기 위해, 팜빌(FarmVille)을 오직 기능 측면으로만 묘사해보면 어떻게 보일지 생각해보자.


   - 농부로서 연속적인 레벨업하기

   - 농장처럼 보이는 2D 그래픽 인터페이스와 상호작용하기

   - 농작물을 심고, 비료 주고, 수확하기 위해 스크린을 클릭하기

   - 농작물을 시장에 팔기

   - 농장을 꾸밀 장식물을 구입하기 위해 게임머니 축적하기

   - 고급 과제 완수를 위해 친구를 농장 '이웃'으로 전환시키기


이 설명은 그다지 와닿지 않는다(게임 업계의 많은 사람이 농장 운영에 관한 게임이 이토록 인기를 끌 줄은 몰랐다). 농장을 운영한다는 이해하기 쉬운 게임의 내러티브는 많은 사람을 끌어들이는 데 도움이 됐다. 기능과 규칙이 성공적인 게임의 뼈대를 구성할지는 모르겠지만, 팜빌을 성공으로 이끈 점은 사람들에게 느낌을 주는 방식이었다.


FarmVille ⓒ Zynga / screenshot from Ricky's farm


'(농부로써) 농장을 운영한다'라는 what이 직접적으로 전달될 유저의 감성적 경험에 대한 내러티브를 찾아가기 위한 것이라면, '농작물을 심고, 비료 주고, 수확하기 위해 클릭한다'라는 how는 그를 뒷받침 하기 위한 구조적이며 기능적인 규칙들이 된다.

이처럼 앞의 what 연재에서 다뤘던 내용은 누구라도 이해하기 쉬운 디렉션 비전 혹은 목표를 만들기 위해 어떻게 발상 할 수 있을까에 대한 것이였다. 위에 대입해 보자면, 그 의도는 기능과 규칙의 뼈대 위에서 개발자던 유저던 누구라도 '이해하기 쉬운 게임의 내러티브'를 구성하려고 함일 것이다.


그렇다면 이번에는 그 기능과 규칙의 뼈대를 통해 what을 좀더 점검하고 더 강력하게 만드는 how로 접근해보자.



how는 말 그대로 어떻게 만드느냐에 대한 고민이지만, 컨셉 혹은 비전을 기획하는 단계에서 다루어야 하는 '어떻게'는 무슨 툴을 쓴다, 어떤 메커니즘이나 로직을 쓴다는 기술적인 개념과는 조금 다르다. 앞의 글에서 다뤘던 what이 대략적으로 정해졌다면, 그 what이 가진 무엇 혹은 내러티브를 실제로 구현해내기 위해 기획적으로 필요하거나 고려해야 할 세부적인 사항에 어떤 것이 있고 어떻게 구성해야 하느냐를 미리 꺼내보는 것이다.

이 과정은 어떻게 보면 실제 기획서를 작성하는 단계에서나 고민할 부분으로 볼 수도 있겠지만, 기획서에서 필요로 하는 상세한 로직이나 메커니즘, 수치에 중점을 두지 않고 최초 단계부터 유연하게 커뮤니케이션 하기 위한 용도로써 본다면, 애초에 구현할 수 없는 시행착오를 최소화하면서 일반적인 팀의 협업 파이프라인의 피드백을 통해 가능성과 특장점을 좀더 구체화하고 끌어올릴 수 있는 과정이 될 수도 있다.


좀더 쉬운 설명(과연 잘 될지;;;;)을 위해, 여기서부터는 가상의 프로젝트에 대한 what을 통해 그 how를 구성하는 기능과 규칙, 그리고 발생할 수 있는 피드백의 사례들을 살펴보자.


차를 타고 도시를 달리며 총 쏘고 부딪혀가며 상대를 파괴하고 방해해 먼저 완주하는 격투 레이싱

엄청 독특하고 재미있을 것 같다는 느낌은 상당히 적은 듯 하고, 세스고딘이 말했던 '리마커블'한 그 어떤 것도 그다지 없어보이며, 본 연재에서 다루고자 하는 감성 경험에 대한 내러티브는 왠지 없다시피 한데다가;; PC 온라인(멀티) 기반이라 가정한다 해도 카트라이더나 스틸독을 비롯해 멀게는 번아웃이나 데스트랙 등 이미 시중에 나와있는 유사한 게임들도 주루룩 떠오르며 스쳐지날 뿐이니... 아, 왜 이런 예시를 생각해낸걸까 난..

SteelDog ⓒ NCsoft / Burnout:Revenge ⓒ EA

하지만 일단 설명을 위한 예시니까 양해해주실 것이라 믿고;;;; 어쨌든 위와 같이 프로젝트의 what을 정의했다고 하면, 이를 위해 먼저 정리되어야 할 how는 간단하게 다음과 같은 기능과 규칙의 나열들로 시작할 것이다.

플레이어는 차량을 타고 운전한다.
벗어날 수 없도록 정해진 트랙이 아닌, 도시 형태의 공간 위를 달린다.
차량은 다른 차량 혹은 배경 환경과 충돌하는 등 상호작용 할 수 있다.
총과 같은 무기를 사용해 공격하고 공격당한다.
차량은 파괴될 수 있다.

명확한 출발점과 골인지점이 있기에 완주가 가능하다.
완주해 보상을 얻고, 보상을 통해 성장해 다음 단계에 도전할 수 있다.
기타 등등..

이렇게 일반적인 게임의 기능과 규칙을 정리해 내게 되면, 이 정도만으로도 개발 파이프라인의 그래픽/애니메이션/프로그래밍/사운드 담당자들에게 어떤 고민을 해야할지 인지시키게 수 있게 된다. 한번 러프하게나마 화두가 던져진다면 능동적인 협업팀에서는 그에 대한 추가적인 아이디어나 우려사항, 궁금함을 참지 못하고 아래와 같이 피드백을 던져올 것이다.

너무 평범하잖아!! 이건 아닌 것 같아~ 완전히 새로운 뭐 없어? 창의적(!)으로다가..
플레이어가 캐릭터 없이 차량을 소유하나? 아니면 차량을 운전하기 위한 캐릭터로부터 차량을 소유하나?
차량이나 캐릭터를 커스터마이징 할 수 있는가? 파츠 커스터마이징의 수준은? BM과 연계는?
그래픽이던 재미던 엇비슷한 수준이라면 차라리 카트라이더를 하지 않을까...
차량의 운전은 물리와 운동 법칙에 따라 사실적으로 움직이는거? 단순화 해 아케이드 게임처럼 직관적으로 휙휙?
PvE가 메인 플레이 비중인가? PvP가 메인 플레이 비중인가? 혼합되었다면 유저경험의 흐름 순서는?
레이싱은 방을 만들어 들어가는 캐주얼한 방식? 아님 오픈월드를 달리다 발생하는 이벤트 혹은 자동 매치로 경쟁하는?
난 번아웃 파라다이스처럼 달리다 이벤트가 발생하는 방식이 좋던데? GTA는 아니지만 오픈월드 레이싱 느낌도 좀 나고.
시간 걸리는 본 게임 말고, 바로 쉽게 같이 할 수 있는 게임 모드들은 없나? co-op이나 서바이벌 레이스 같은 모드는 어때?
도시의 도로 위만 달리는거야? 그럼 도시는 병풍인데.. 주변 사물이나 건물 등을 이용한 입체적 경로는 안될까?
배경에 놓여있는 사물을 파괴하거나 밀어내고 폭발시키는 등의 활용이 가능해? 이를 통해 상대를 방해할 수 있는 수준도?
스플릿세컨드나 모터스톰3을 보면 배경이 알아서 파괴되고 무너져서 압박해오던데, 그런건 어떨 것 같아? 만들기 어렵겠지만..
전투를 하려면 조준 조작을 해야하는데, 달리면서 상하좌우 시야를 확보해 본다는게 어렵지 않을까?
헤일로나 모던워페어,
스플릿세컨드 플레이 할 때 차량 탄거랑 비슷한 느낌의 조작인거야?
움직임 조작과 전투 조작이 동시에 일어나야 할텐데, 어떤 화면 UI 구성과 조작 방식을 사용할껴?

총만 쏠 수 있어? 스파이더 지뢰나 낚아채는 윈치(견인용 장치) 같은 교란형 도구도 있으면? 그러고보니 방어 공방은 없는겨?
차량의 파괴는 HP가 소모되야만 되는거? 그냥 바퀴만 터진다던지하는 파츠별 파괴와 그에 연계된 효과를 활용할 수는 없나?
그렇게 된다면 조낸 타이어만 펑크 낸다던지, 연료통만 노리는 지뢰라던지 해서 달성과제(트로피)까지 연계하면 재밌겄다!
그래서 이 게임의 목적은 뭔데? 업그레이드로 무한 경쟁 반복인지, 스토리 상 어떤 목표를 향한 것인지? 모든 단계의 끝은 어디?
기타 등등..

얌전하게 써있기는 하지만.. 사실 실제 개발 단계라면 저 정도에서 그치지는 않을 것이다. 물론 좋은 아이디어들을 덧붙여 브레인스토밍에 힘을 실어줄 수도 있겠지만 예상 외의 다양한 질문이 쏟아질 수 있고, 우려사항을 비롯해 더 많은 질책과 힐난(!)의 냉험한 눈초리가 암암리에 느껴질 수도 있을 것이다. 하지만 이러한 피드백에 처음부터 일관된 답을 줄창 할 수 있다는 사기꾼능력자라면 좋겠지만, 아이디어를 떠올린 처음부터 완벽하게 무슨 게임을 만들 것인지 차곡차곡 다 쌓아놓을 수는 없을 것이다.
때문에, 아직 그 방향성을 완전히 정의하지 못한 것과 크게 다르지 않다고 볼 수도 있다. 이것은 비전을 찾아가는 브레인스토밍 과정의 한 부분일뿐, 그 하나하나의 사항들에 대해 되돌아 봤을 때 what을 바꿔야 하나까지 우려할 필요는 없다. 물론 어디선가 높은 곳에서 직격된 완전 뜬금없는 아이디어가 아닌, 엄청 리마커블한 대격변의 아이디어가 나왔다면 다시 what을 고민해봐야 할 수도 있겠지만, 그런 경우는 프로젝트나 팀의 상황에 맞춰 달라지는 경우이니 일단 이 얘기에서는 제외하고;;

어쨌든, 사전부터 충분한 자료 조사와 담당자한테 물어보기 등을 먼저 수행해 자체적인 FAQ를 먼저 만들어 최대한 그 인식의 갭을 줄여나간 상태로 함께 피드백을 주고 받고 그 아이디어를 쌓아간다면, 중간에 의견이 무시당했다느니 갑자기 엎은 것 같다느니하는 불필요한 오해를 일으키는 일은 최소화 할 수 있을 것이다.
그 결과로 이렇게 피드백에서 다뤄질 수 있는 더 파고든 기획/구현적 사항이 글로써건 마인드맵의 형태로건 정리돼 있다면, 그것이 이 게임의 구체적이고 일관된 방향성의 특징(feature)을 규정하게 되므로 '디렉션 비전'을 뒷받침 해주는 강력한 근거로 계속 남아 이후의 기획에 있어 큰 도움이 될 것이다.
이제 어떤 요소에서 특징적인 재미가 발생할 것인지를 좀더 구체적으로 파악해보자. 어쩌면 그 요소들 중 적절한 것들을 찾아 이 게임 고유의 쿨-피처 혹은 리마커블-피처로 좀더 강화할 방법을 찾아볼 수도 있을지도 모른다.

일단 이 예시에서는 피드백을 검토한 결과 기존 작품들의 차별화를 위해, '리얼한 물리 운동계를 사용하지만 키보드를 사용한 이동 조작과 마우스를 통한 시점과 전투 조작을 중심으로 삼아, 빠르게 달린다보다는 좀더 오픈월드(혹은 샌드박스) 타입의 전술과 전략으로 벌이는 난장판 전투 레이스'를 만든다는 형태로 변경해 보았다. 최초의 무엇을 하는 어떤 게임이다까지를 누가 제시했던, 이제는 모두가 함께 생각하고 공감할 수 있을 단계까지 정리해 볼 수 있게 된 것이다. 어째 오픈월드 속 막가는 난장판 레이스..라는 점이 마음에 든다.
이것을 굳이 레퍼런스 하이컨셉화 한다면, '스플릿세컨드의 조작계와 연출 + 헤일로의 차량 전술 + 번아웃파라다이스의 월드구성과 퀘스트트리거 + α'처럼 정리해 볼 수도 있을 것이다. 하지만 장르 특성 상, 이런 레퍼런스가 전체 팀원에게 공감대를 얻을만큼 다 잘 나간 게임들도 아니고 다들 그렇게 해본 것 같지도 않아 보편적이지 못하다면, 직접 글이나 스케치로 만들어보거나 참고할만한 유사 스크린샷이나 플레이 영상으로써 대체하는 것이 더 나을 것이다. 왜냐하면 실제로 플레이를 경험해보거나 깊게 살펴보지 못한 샘플에 대한 설명은 자칫 비경험자에게 잘못된 이해를 불러일으켜 나비효과(!)를 일으킬수도 있기 때문이다.

Burnout Paradise ⓒ EA / SplitSecond ⓒ DisneyInteractiveStudios

이렇게 구성요소들이 모두에게 같은 모습으로 공유될 수 있을 정도로 구체화 되면, 각 분야별 담당자들이 그 우선순위에 따라 해당 기능을 구현하기 위한 실제 업무(task)를 아래처럼 시작해 각 실무 파트에 맞는 러프한 기능 명세를 뽑아낼 수 있을 것이다.

플레이어는 캐릭터와 차량 정보를 갖는다. 또한 각각 커스터마이징이 가능하다.
룩(Look)에 대한 요소는 부분유료화 BM으로, 성능에 대한 부분은 인-게임 성장과 보상으로 가져간다.
차량은 무게와 중력, 가속과 충돌 등에 있어서만 물리 운동 법칙을 따르지만
본격 레이싱은 아니니 자동변속기로만 조작된다.
도로 뿐만 아니라 복층 고가도로, 터널 벽 타기, 빌딩 옥상 등도 이용할 수 있도록 3차원 좌표계를 사용한다.
충돌로 인해 건물이나 사물을 파괴하고 밀어낼 수도 있도록 배경 구성물도 Entity화 하고 동적 객체로 분리한다.
동적 객체가 접촉, 충돌, 파괴 시 발생시키는 상태이상 효과가 PC나 NPC, 터레인에 부가될 수 있다.
상대를 방해(파괴, 밀기, 끌기, 붙잡기 등등)할 수 있는 방법으로써 모든 종류의 무기의 등장을 고려해야 한다.
차량은 상태이상이나 방해행동에 따라 모두 다르게 인터랙션해야 하기 때문에, 각 파츠별 파괴와 디버프 기능을 연계한다.
완주 시 파츠에 대한 보상이나 랭킹에 대한 보상이 주어지고 그를 타인에게 부각시킬 수 있도록 UI를 만든다.
이 게임은 스토리 기반이 아닌 PvP를 메인 게임 플레이로 가져가지만, 로비와 방의 구조를 월드 트리거에서 동적으로 제어한다.
기타 등등..

기능 명세가 나오게 되면 실질적으로 각 작업을 시작할 수 있는 상태가 될 수 있지만, 아직 기획에 있어 미시적-거시적 게임 모델이 수립되지 않은 상태인지라, 처음부터 끝까지 끌고가야 할 게임의 전체 유저경험 중 극히 대표적인 플레이(대부분의 경우, 이동과 전투, 멀티)를 위한 '프로토타이핑'용 수준에 그칠 수 밖에 없다. 모든 기능과 메커니즘은 유기적으로 얽혀져야 지속적으로 동기를 부여하고 몰입시키며 순환할 수 있기 때문에, 충분한 시뮬레이션 과정이 필요하기 때문이다.
따라서 여기서 주의할 점은, 지금 이 what & how의 유저경험 설계의 과정이 실제 기능 개발을 위한 단계가 아니라는 것이다. 본 연재에서 다루고자 함은 프로토타입을 제작하기 위한 기획적 발상과 스토리텔링의 접근을 이야기하는 것이라, 위의 기능 명세가 최후까지 유지되고 변경되지 않을 기능이자 로직이며 데이터 구조일 수는 없다.

하지만 이러한 과정을 통한다면 프리-프로덕션에 있어서 대단한 기획서가 없더라도 팀 전체가 좀더 빠르고 유연한 반복 개발(iteration)을 통해 구체적 모습을 예측해 볼 수 있거나 일부를 구현해 점검해 볼 수 있는 프로토타입을 뽑는데 좀더 힘을 실어갈 수 있게 해, 추후 실제 구현의 우선순위를 수립하거나 미시적-거시적 기획의 절차를 구성할 때에도 유효하게 쓰일 것이다.

정리하자면, 기능과 규칙을 미리 예상해보는 how의 과정은 기획적으로 what에서 규정한 내러티브의 선언을 좀더 구체적으로 실제 체험 가능한 재미 요소에 가깝게 정의하고, 예측되는 오류나 발전방향을 미리 함께 점검해 보는데 그 목적이 있다. 기획이 모든 분야와 구성에 있어 how를 다 찾아낸다는 것은 불가능하기 때문에, 그 '어떻게'에 대해 함께 논의할 수 있는 기준점을 마련하고 그를 취합해 공감대를 기반으로 한 what의 방향성을 덧붙이고 강화해 프로덕션의 시행착오를 최소화할 수 있는 구체적인 방향성을 만들어 프로젝트의 비전으로써 끌고갈 수 있는 가치를 갖게 만들고자 하는 것이다.

번외로, 업체나 팀마다 용어는 조금씩 다르겠지만 유사한 접근 사례로써 유저경험(user story)를 기능(feature)로 쪼개고, 이를 업무(task)로 만들어 실무 작업을 진행하는 매니지먼트의 방법으로써 FF14의 리빌딩을 맡고 있는 스퀘어에닉스의 하시모토요시히사 CTO가 컨퍼런스에서 발표한 게임 개발 프로젝트 매니지먼트 강좌도 읽어보셨으면 한다.

Final Fantasy 14 ⓒ SquareEnix

다음에는 what과 how에 대한 이해를 바탕으로 좀더 본격적으로 필자가 프리-프로덕션 기획에 대해 하고자 했던 내용인 '시스템기획曰, 우리도 경공으로 벽 좀 타죠'라던가, '컨텐츠기획曰, 초반부 튜토리얼 던전 2개랑 집단포위형 몹 AI 필요해요' 등등 같이 앞뒤 다 짤린 기획 스펙 말고, 유저경험을 고려해 개발팀 내 공감까지 이끌어내 볼 수 있는 방법으로써 '왜?'를 통해 생각해보는 storytelling 기획의 이야기 해보도록 하자.



반응형

'기획' 카테고리의 다른 글

게임 UX 기획 - 03. storytelling  (2) 2012.02.17
모든 기획에는 의도가 필요하다.  (30) 2012.02.14
게임 UX 기획 - 01. what  (16) 2012.02.02
게임 UX 기획 - 예고  (3) 2012.01.31
온라인게임 퀘스트의 구조  (5) 2012.01.30
,
Posted by 알 수 없는 사용자

으음, 본의 아니게 5일이나 늦었습니다.
백수 주제에 아파치 웹서버 설정 문제로 골치를 앓아 시간을 소비했습니다.
대신 오늘 포스트는 가장 실용적인 포스트와 예제가 될 것입니다.

여친 만들기는 여전히 노력조차 못하고 있는데, 도대체 왜 이렇게 사나... 생각중입니다.
최근 고등학교때 동기, 후배분, 선생님을 뵙고 오니 더 복잡한 생각이 듭니다.

이번엔 다리 휘었다는 댓글은 안달리길 바라며.

이번엔 다리 휘었다는 댓글은 안달리길 바라며.

오늘의 주제는 데이터베이스(Database)와 그것을 이용한 프로젝트입니다.
그런데 이상하게도 어느 게임 관련 서적에도 DBMS에 대한 이야기는 거의 나오질 않습니다.
먹고 사는데 중요한 정보는 쉽게 노출되지 않아서 일까요?
하지만 게임 업계에 근무하시는 분들은 이 DBMS야말로 핵심 요소라는 것을 잘 알고 계실 겁니다.

최근 KGC에서 N문사의 강산아님이 DBMS와 SQL 최적화에 대한 이야기를 해주시고 있는 것은,
그런 의미에서 아주 좋은 발전이라 생각합니다(서평을 바라신다면 한권 보내주시길 바랍니다. ^^).

열심히 읽어드리겠음.

열심히 읽어드리겠음.


자, 그럼 DBMS는 게임에서 어디에 쓰일까요?

1) 멤버쉽 전체 - 가입, 탈퇴, 로긴, 친구, 길드, 아바타...
2) 게임 컨텐츠 - 경험치, 레벨, 아이템, 공격력, 전적...
3) 몹정보 - 몹의 경험치, 레벨, 아이템, 공격력, 전적...
4) 사용자 패턴 분석
5) 그외 "정보"라고 할수 있는 모든 것을 담을 때

네, 다시 말해 게임과 서비스의 모든 것을 담고 있는 곳이 DBMS입니다.

또한 DBMS는 다양한 서버들에 있어 가장 쉬운 서버간 다리(brige)와 동기화 객체 역활을 해줍니다.

보통 회원 가입은 웹페이지(웹서버)에서 받습니다만, 게임 정보는 게임 서버를 이용합니다.
별것 아닌 단순한 형태이지만, 이미 한 DB에게 웹서버와 게임서버라는 역활이 다른 두개의 서버가 연결이 되었고
아무 쉬운 방법으로 두 서버가 한 사용자에 대한 정보를 공유한 것입니다.

그리고 이 웹서버와 게임 서버를 각각의 쓰레드 객체라고 생각하고 DB를 한 유저에 대한 유저 객체라고 놓고 본다면,
유저 객체에서 트랜잭션(transaction)을 이용해 쓰레드 동기화를 구현하고 있는 셈입니다.
이런 관점이 아키텍트로 나가는 첫 시점이라 생각합니다.

1. DBMS 기초
DB에 대해 익숙하신 분도 있고, 익숙하지 않은 분들도 많을 것입니다.
특히 학생 때에 DB에 대해 관심을 가지는 분들이 적습니다. 아마 비쥬얼한 것이 없고 DB를 사용하는 프로젝트도 없기 때문일 것입니다.
그래서 설명을 하긴 해야하는데...




.....




..................





......네, 코드도 재활용하는데 글이라고 재활용 못하겠습니까?
억지로 찾은 아래의 링크로 DB와 DBMS를 이용하는 법에 대한 내용은 날로 먹겠습니다.

날로 먹으니 더 맛있네요~

날로 먹으니 더 맛있네요~

1편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039130809
2편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039131033
3편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039131490


날로 먹은 위의 글은, 당시 테마가 MFC라서 MFC로 되어져있지만, MFC가 아니더래도 똑같습니다.

만약 여기에 더 추가한다면 ADO.NET과 요즘 유행(?)하는 NoSQL에 대한 이야기를 적어볼까 합니다.
그러나 ADO.NET은 DataSet을 비롯한 강력한 기능에도 불구하고 닷넷 프레임워크를 필요로 한다는 점이 결정의 걸림돌이 됩니다.
즉 ADO.NET을 이용한다면 서버도 C#을 써야 합니다. Managed C++ 따윈 잊으십시요.

또한 NoSQL은 트랜잭션 처리와 보안과 안정성에 있어서 연구(그리고 직접 뜯어고치는 재빌드 필수)가 더 필요하다고 봅니다.
BLOB을 대체하기 위한 방법으로는 좋으나 마냥 NoSQL이 진리다~!! 라고 생각하는 것은 아니라고 봅니다.
주위에 NoSQL이 뜬다고 우리도 걍 NoSQL로 개발해~ NoSQL로 뒤짚어~~라는 분 있으면 이 문장을 보여주세요.
그래도 말 안들으면 딴 회사 가세요~~~
참고로 저는 mongoDB을 DB계의 UDP라고 생각합니다. 현존 관계형 DBMS가 TCP라면 mongoDB은 딱 UDP 같은 느낌입니다.

본격 Native C++ 강좌인 이 연재에서는 ADO를 이용하여 DB를 연결하겠습니다.

2. Winsock과 ADO를 이용한 아바타 채팅 시스템
간만에 실전 코드로 돌아가봅시다.
우리가 만들 것은 소켓과 DB를 이용한 로그인 + 아바타 채팅 시스템입니다.
로긴에 성공하면 DB에 저장된 자신의 아바타가 화면에 나오며 상대와 채팅을 할때면 대화를 보낸 사람의 아바타가 표시됩니다.
프로젝트 명세서

1) DB에는 ID와 PWD, 그리고 사용자 이름과 아바타가 있다.
2) 클라이언트에는 서버에게 ID와 PWD를 보낸다.
3) 서버는 클라이언트가 보낸 ID와 PWD를 DBMS에 질의 한다. 이때 DB 연결 방식은 ADO를 이용한다.
4) 로긴에 실패하면 실패 패킷을, 성공하면 사용자의 추가 데이터(이름, 아바타)를 보낸다.
5) 클라이언트가 보내는 채팅 메시지에는 아바타 정보가 포함된다.


명세서가 나왔으니 가장 먼저 DB를 만들어 봅시다.
MS-SQL Server 같은 것이 있으면 좋겠지만 대부분 무리일 것이니 위의 강좌처럼 MS-Access를 이용하겠습니다.
Access는 Excel에게 밀려 Office를 설치하며 잊으시는 분도 많을 것입니다.
그러나 Access는 MS-SQL Server를 대신하여 공부하기에 정말 좋은 개인용 DBMS입니다.
MS-SQL Server 로 마이그레이션도 아주 훌륭합니다.

이런 이야기를 왜 드리냐면, 수많은 게임 회사들은 전담 DBA가 없는 경우가 많기 때문입니다.
초기 개발단계나 규모가 작은 개발사라면 서버 개발자가 DBMS를 만질 수 밖에 없는 안타까움이 있습니다(...가끔 SE질도 합니다.).
신입 서버 개발자라면 이력서에 자신있는 DBMS 하나를 꼭 적어보시길 바랍니다.

2,1 Database 설계
넹, 딸랑 한 테이블입니다.
JOIN이고 머고 필요없습니다. 이 강좌가 SQL 강좌는 아니니까요.

꼴랑 필드 4개

꼴랑 필드 4개


ID와 PWD를 소켓으로 받아 NAME과 AVATRA를 내려보냅니다.

ID와 PWD를 소켓으로 받아 NAME과 AVATRA를 내려보냅니다.


별다른 설명이 없어도 어떻게 사용될지 보입니다.
서버에서 요청할 SQL문은 SELECT NAME, AVATAR FROM USER WHERE ID='test1' AND PWD ='aaaa' 뿐입니다.
BOF랑 EOF가 같다면 당연히 로긴 실패가 되는 것이고요.
회원 가입과 아바타 설정은 웹프로그래머분이 웹서버에서 받아주세요~

아, 그리고 가급적 DBMS에 질의는 생SQL문이 아니라 반드시 Stored Procedure를 써서 접근하는 습관이 중요합니다.
그런데 저는 이미 습관화 되어 있으므로 이번 예제에는 걍 생SQL문을 날렸습니다.
이는 단순히 SP가 빠르다는 이유가 아니라,
정기적으로 DBMS의 성능을 테스트(Query Profile)하는데 생SQL문을 쓰면 제대로 분석하기 힘들기 때문입니다.

2.2 ADO를 이용한 DB접근
이번 회 예제에는 가장 기본적인 형태의 ADO 클래스가 들어있습니다.
이미 다이렉트X를 날고 기는 독자분들에게 COM 컴포넌트 인터페이스 얻어오는 법은 장난일꺼라 생각되어 회원정보를 질의하는 부분만 보겠습니다.

if (S_OK == rad.RAConnect(_T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=..\\DB\\MyDB.mdb;")))
{
oRs = rad.RAGetDisconnectedRs(strQueryLogon.c_str());
CComVariant val;
if (oRs)
{
while (!oRs->EndOfFile)
{
// Get Name
val = oRs->Fields->Item[_variant_t(_T("NAME"))]->Value;
if (val.vt != (VT_NULL))
{
printf("NAME : %s\n",(char*)_bstr_t(val));
m_strName = (char*)_bstr_t(val);
}

// Get Avatar
val = oRs->Fields->Item[_variant_t(_T("AVATAR"))]->Value;
if (val.vt != (VT_NULL))
{
printf("AVATAR : %s\n",(char*)_bstr_t(val));
val.ChangeType( VT_I4 );
m_iAvatar = val.lVal;
}
bIsFind = TRUE;

oRs->MoveNext();
}
oRs->Release();
oRs->Close();
}
}

참 쉽죠?

ADO 연결 문자셋으로 MDB 파일에 접근한 후, Recordset 객체를 다시 만들어 SELECT의 결과값을 획득합니다.
이때 귀찮은 것이 COM 변수를 일반 변수로 변환하는 것일 겁니다.
NAME은 문자형이므로 _bstr_t를 그대로 변환하였는데 AVATAR는 INT형이라 CComVariant 에서 제공하느 ChangeType()으로 변환했습니다.
COM 변수를 변환할때는 atoi()같은 고전 함수보다 COM에서 제공하는 방법을 사용하는 것이 안전합니다.

...사실 요즘 COM 공부하시는 분들 별로 없으리라 생각됩니다.
예전에 게임 아카데미에서 COM 강의를 해보니 학생 분들 모두 전멸을 하여 아주 행복했습니다.
하지만 윈도우용 함수는 크게 WIn32 API와 COM으로 구분됩니다. SH로 시작하는 쉘함수들 역시 COM의 한 부분입니다.
닷넷 프레임워크 역시 COM과 XML의 조합입니다.
이중 디스패치 구현해가며 COM 컴포넌트를 직접 만드는건 시대에 뒤떨어지는 일이라 하더라도
기존 COM 컴포넌트를 이용하는 것이 어려우면 막강한 윈도우 OS를 반 밖에 사용하지 못하는 것이라 생각됩니다.

2.3 패킷 설계
DB와 ADO를 극복하고 가장 재밌는 부분으로 돌아왔습니다.
먼저 이 프로젝트에 사용될 패킷을 적어봅시다.

//
// MyPacket.h
//
#define MSG_LOGIN 1000
#define MSG_LOGINFAIL 1001
#define MSG_LOGINSUCC 1002
#define MSG_MESSAGE 1003

// 헤드 검출용 C, S
class CGENERIC
{
public:
DWORD head;
};

// 로긴 패킷 C->S
class CLOGIN : public CGENERIC
{
public:
char id[20];
char pwd[20];
};

// 로긴 실패 S->C
class CLOGFAIL : public CGENERIC
{
public:
};

// 로긴 성공 S->C
class CLOGSUCC : public CGENERIC
{
public:
char id[20];
DWORD avatar;
char name[20];
};

// 메시지 C->S->C
class CMESSAGE : public CGENERIC
{
public:
char id[20];
DWORD avatar;
char message[500];
};

전부 고정 패킷입니다. 아직 string이나 vector를 그래도 송수신하게 해주는 패킷 생성기를 만들지 않았으니까요.
누군가 패킷 생성기 대신 포스팅 해주실 분 없나요?
아직 패킷수도 많지 않으니까 패킷 클래스 이름에 송신용, 수신용, 알림용 네이밍 규격을 붙이지 않았습니다.

점점 포스트가 읽기 어려워지고 있습니다.

점점 포스트가 읽기 어려워지고 있습니다.

모든 패킷은 CGENERIC을 상속받고 있습니다.
일차적으로 모든 패킷은 CHAR에서 CGENERIC으로 캐스팅 되어 어떤 패킷인지를 분석하게 됩니다.
그런데 CGENERIC에 패킷 판독용 DWORD만 있고 실제 데이터 크기를 담은 정보는 없군요?
1회 포스팅 때 나갔던 소스를 보시면 CDataMessage라는 버퍼용 클래스가 있고 그것에서 앞부분 4Byte를 패킷 사이즈용으로 사용하고 있습니다. 즉, 모든 패킷은 CDataMessage로 다시 감싸는 구조로 되어있습니다.

명확한 설명을 위해 그림으로 다시 그려보죠.

CLOGIN 패킷으로 접속과 함께 로긴 정보인 ID, PWD를 보냅니다.

CLOGIN 패킷으로 접속과 함께 로긴 정보인 ID, PWD를 보냅니다.


DB 질의 결과에 따라 성공, 실패를 보냅니다. 성공시 NAME과 AVATAR를 보내며 실패면 클라이언트는 접속을 끊습니다.

DB 질의 결과에 따라 성공, 실패를 보냅니다. 성공시 NAME과 AVATAR를 보내며 실패면 클라이언트는 접속을 끊습니다.

이제 다른 클라이언트B가 접속을 합니다.

이제 다른 클라이언트B가 접속을 합니다.


누군가 접속을 성공하면 서버는 모두에게 CLOGINSUCC을 보냅니다. 이미 연결되어 있던 클라이언트는 이 정보를 누군가 입장했다고 판단합니다.

누군가 접속을 성공하면 서버는 모두에게 CLOGINSUCC을 보냅니다. 이미 연결되어 있던 클라이언트는 이 정보를 누군가 입장했다고 판단합니다.


서버는 단순히 CMESSAGE 패킷을 중계만 합니다.

서버는 단순히 CMESSAGE 패킷을 중계만 합니다.


2.4 클라이언트 구현
아래는 클라이언트의 핵심 구현부 입니다.
중요한 것은 로긴 성공과 메시지에 있어 같은 패킷을 이용하여 클라이언트에서 판정하고 있다는 것입니다.
패킷 처리 부분은 이미 서버와 클라이언트가 비슷하므로 서버는 별다른 설명이 필요 없을 것입니다.

// 일단 받은 패킷의 종류를 알아본다.
CGENERIC* pGenericPack = (CGENERIC*)szRecvData;


// 해당 패킷 처리로 따로따로 보낸다.
switch( pGenericPack->head )
{
case MSG_LOGINFAIL :
{
pMainWnd->OnCloseToServer(TRUE);
pMainWnd->AddToLog(_T("ID나 비밀번호를 체크하세요."));
}
break;

case MSG_LOGINSUCC :
{
CLOGSUCC* pLoginSucc = (CLOGSUCC *)szRecvData;
pLoginSucc->avatar;
pLoginSucc->name;
pLoginSucc->id;
CString strUserName, strID;

if(pMainWnd->m_strMyID.IsEmpty())
{
strUserName = pLoginSucc->name;
strUserName = strUserName + " 님 어서오세요.(By Server).";
pMainWnd->AddToLog(strUserName);

pMainWnd->m_iMyAvatar = pLoginSucc->avatar;
TRACE("%d\n", pMainWnd->m_iMyAvatar);

pMainWnd->m_strMyID = pLoginSucc->id;
pMainWnd->DrawMyAvatar();
}
else
{
strUserName = pLoginSucc->name;
strUserName = strUserName + " 님이 입장하셨습니다.(By Server).";
pMainWnd->AddToLog(strUserName);
}
}
break;

case MSG_MESSAGE:
{
CMESSAGE* pMessagePack= (CMESSAGE*)szRecvData;
CString strRecvMessage, strID;
strRecvMessage.Format("%s : %s", pMessagePack->id, pMessagePack->message);
strID = pMessagePack->id;

pMainWnd->AddToLog(strRecvMessage);

if ( pMainWnd->m_strID != strID)
{
pMainWnd->m_iOtherAvatar = (int)pMessagePack->avatar;
pMainWnd->DrawOtherAvatar(); // 상대 캐릭터
}

}break;



3. 인증샷, 하지만...
자, 이렇게 완성되었습니다.

덕내쩌는 클라이언트라 덕후가 아니면 받지 못함 ㅋㅋㅋㅋㅋㅋㅋ

덕내쩌는 클라이언트라 덕후가 아니면 받지 못함 ㅋㅋㅋㅋㅋㅋㅋ


이 포스팅을 읽으며 한가지 새로운 사실을 알수가 있는데,
서버에서는 별다른 연산이 없었지만 나름 아바타가 포함된 그럴싸한 채팅 시스템이 만들어졌습니다.
이는 DB라는 자료구조가 존재하기 때문이죠.
사실 게임 서버에서 타게팅, 넌타게팅, 3D 위치값 같은 게임 냄새 물씬 나는 요소는 결코 절대적인 비중을 차지 하지 않습니다.
개발자의 시선에서 게임 서버는 DBMS의 정보를 클라이언트에게 보내주고, 또한 클라이언트가 보낸 정보를 DBMS에 쌓기 위해 존재합니다.

3회만에 이 프로젝트를 포스팅 할 수 있어 무척 기쁩니다.
소스를 잘 만들었다는 이야기가 아니라, DB가 투하된 실전용 클라이언트/서버의 핵심을 선보였다는 것이며
그와 동시에 클라이언트/서버의 모든 문제점을 고스란히 알수 있는 프로젝트이기 때문입니다.

문제점이야 밤새도록 적을 수 있겠지만 우선 몇가지만 적어보죠.

4. 더 생각해볼 꺼리
오늘은 더 생각해볼 꺼리가 참 많네요.

1) 서로 채팅을 할때 NAME 대신 ID가 보여지고 있습니다. 기껏 만든 NAME을 자기만 보고 있는 셈이죠.
이를 ID와 NAME이 동시에 보여지도록 해봅시다.
또한 그렇게 할때 어떻게 하면 가장 작은 사이즈의 패킷을 보낼 수 있을까요?

2) 이 프로젝트에는 현재 치명적인 버그(?)가 있습니다!
한 클라이언트가 로긴을 실패하면 CLOGINFAIL 패킷을 받아 모든 클라이언트가 접속이 끊어지고 맙니다.
이 문제를 어떻게 해결할수 있을까요?
클라이언트에서 고치는 방법(패킷 수정 포함)과 서버에서 해결하는 방법, 각각 두가지 방법을 생각해봅시다.

3) DB 연결
현재 서버는 CLOGIN 패킷을 받을때 ADO로 DBMS에 연결과 질의, 그리고 닫기를 수행하고 있습니다.
만약 MMORPG처럼 다수의 사용자가 접속을 한다면,
아니 지금 프로젝트에서도 모든 사용자의 채팅을 서버에 저장한다면(법적인 문제를 떠나) DBMS는 뻗게 됩니다.
어떻게 하면 DBMS에게 무리를 주지 않으며 편안한 서비스를 할 수 있을까요?
단순히 DBMS 폴링도 좋은 방법이지만 그 규모조차 넘어서면 어떻게 할까요?
또한 게임 서버뿐만 아니라 웹서버에서도 사용자 정보를 갱신한다면(닉네임 변경) 어떤 문제와 해결점이 있을까요?

아, 그리고 지금 현재에도 이 프로젝트에서 DB질의 시간이 가장 많은 시간을 소모하고 있을 것입니다.

4) 서버와 월드
이 프로젝트가 인기가 좋아 동접 20만명(...)의 프로젝트가 되었다면 합시다.
그래서 서버를 늘립니다.
서버를 늘렸을때 각각 다른 서버에 있는 사용자들끼리 채팅을 하게 해야할까요?
아니면 깔끔하게 포기할까요?

전자라면 서버"들"을 어떻게 구성해야 할까요?
후자라면 그게 바로 한 월드가 되며 다른 월드의 친구와는 게임을 당연히 할수 없을 것입니다.
그럼에도 불구하고 다른 친구에게 쪽지라도 보내고 싶다는 고갱의 건의가 왔고 기획팀에서 해달라고 하면 어떻게 할까요?

5) 지난 시간 소개해드린 WireShark로 보면 ID와 PWD는 그대로 드러납니다.
오픈하자마자 중국계 IP들의 놀이터가 되겠군요.
웹에서 로긴이 아닌 게임에서 로긴이라면 어떤 방식으로 숨겨야 할까요?

1), 2)는 네트워크 공부를 처음하는 분이라도 해결할수 있는 부분이고
나머지는 현업을 준비하는 분들에게 알맞은 토픽입니다.

이것으로 이번 포스팅을 마치도록 하겠습니다.
늦어져서 다시 한번 죄송하고요, 이번 포스팅의 서버에서는 ADO 질의 이외에는 일체의 로직이 없었습니다.
다음 시간에는 서버에 로직과 자료구조를 투척해보겠습니다. 바로 게임으로 들어가는 관문인 로비(Lobby)를 만들어볼까 합니다.

언젠간 이런 날이... .

언젠간 이런 날이... .

내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/ )를 명시해주세요.

프로젝트 다운로드 : http://rhea.pe.kr/attachment/cfile22.uf@1272AD3A4F32B534061400.zip

반응형
,
Posted by 알 수 없는 사용자
문자열은 게임 개발에서 아직까지 많이 사용되고 있는 데이터 형식입니다. 여기서 데이터 형식이라고 한것은 문자열이 근본적으로 정수형이나 실수형처럼 내재된 데이터 타입이 아니기 때문입니다. 따라서 문자열 데이터 형식을 실제로 구현 및 처리할 때에는 많은 문제들이 발생합니다.

문자열에 대한 근본적인 문제로는 저장과 관리의 문제입니다. C++에서 문자열들은 근본적인 데이터형이 아닙니다. 따라서 문자열은 문자의 배열로 구현되고, 이에 대한 변수 길이는 하드코딩으로 제한되거나, 동적으로 할당합니다. 따라서 이를 처리하기에 편리한 여러 문자열 클래스들이 존재합니다. STL의 string 클래스가 좋은 예제입니다. 

두번째 문제로는 다중언어의 지원입니다. 이를 지원하기 위한 유니코드의 사용과 멀티바이트의 사용은 기본적인 규칙을 초기에 잡아주지 않으면 많은 문제가 발생하며, 특히 한자권 지역의 언어 지원은 많은 문제를 발생시킬 수 있습니다.

최종적으로 이 글에서 처리하고자 하는 속도의 문제입니다. 문자열들을 아직까지 내부적인 리소스 파일 이름, 오브젝트 아이디와 같은 곳에서 사용하는 프로젝트가 많이 존재합니다. 예를 들면 레벨 오브젝트에 "Player-Camera", "Mountain-Rock-01" 등등이 있습니다. 좋은 명명 규칙을 가진 문자열은 프로그래머나, 디자이너들이 오브젝트나 리소스들을 식별하기에 매우 편리합니다. 하지만 이렇게 문자열들만을 사용하여 처리하는 것은 게임 속도의 저하를 유발할 가능성이 매우 높습니다. 정수형들이나 실수형들을 비교하거나 복사하는 것은 단순히 머신 언어 명령들을 통해 이루어지기 때문에 매우 빠릅니다. 하지만 strcmp()와 같은 함수를 사용한 문자열들의 비교는  문자 배열들을 검색하는 O(n)의 비용이 필요합니다. 문자열의 복사는 메모리 동적 할당 비용을 포함한 O(n) 메모리 복사가 필요합니다. 문자열만을 사용하는 프로젝트들에서 strcmp()와 strcpy()가 가장 시간을 소비하는 함수로 측정될 가능성이 매우 높습니다.

이에 따라 문자열을 해쉬화 하여 정수형으로 대응하여 사용한다면 매우 많은 속도 이득을 볼 수 있습니다. 해쉬 함수는 부분적인-단일 정수에 문자열을 대응합니다. 문자열 해쉬 코드의 비교는 정수비교와 같은 것이어서 매우 빠르게 이루어질 수 있습니다. 해쉬 테이블 형식으로 문자열들을 저장해 놓는다면 해쉬 코드로부터 문자열을 얻어 올 수 있어 디버깅을 할 때나 디자이너에게 표시해 줄 때도 기존의 문자열로 표시해 줄 수 있습니다.

하지만 많은 사람들이 해시 시스템에서 충돌이 발생할 가능성이 있어 해쉬화된 문자열을 기피한다고 이야기들을 합니다. (즉, 두가지 다른 문자열들이 동일한 해쉬 코드를 생성하는 것입니다). 하지만 알맞은 해쉬 함수를 사용한다면,. 32-비트 해쉬 코드는 40억 이상의 값들을 사용할 수 있습니다. 그래서, 만일 해쉬 함수가 매우 넓은 범위로 문자열들을 잘 배치한다면, 충돌이 발생하지 않습니다. 해외의 스튜디오에서는 단순히 CRC-32 알고리즘을 사용하였을 때 2년동안 충돌이 한번도 발생하지 않았다는 참고 문서가 존재합니다.

다음 글에서는 해쉬화된 문자열의 구현에 대한 단계들을 차례로 설명해 보도록 하겠습니다.

- by 김영민

- 참고 문서: Game Engine Architecture: 5. Engine Support System
반응형
,