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

오늘은 프레넬 효과Fresnel Effect에 관해 이야기해보려 합니다.
누구나 한번쯤 들어 보셨겠죠? 반사쪽만 보면 매번 이 단어가 튀어나와서 참 힘들었네요. 그런데 막상 그게 무엇인지 설명을 부탁드려도 될까요? 하면 쉽게 말하기 힘든것도 사실입니다. 오늘 이 부분에 대해서 한번 이야기를 풀어보려 합니다.

물체가 우리에게 보이는 것은 빛이 물체에 반사해서 들어오는 것을 눈(시신경)이 감지하는 것입니다. 컴퓨터쪽으로 말하면 Diffuse, Specular, Reflection이 아마 기본이 될 것입니다.
<물체의 표면에 빛이 반사하는 모델>을 말할 때, Bidirectional reflectance distribution function. 즉 양방향반사분포함수... 약어로 BRDF라는 말을 씁니다.

왜 뜬금없이 BRDF모델이라는 생소한 단어를 꺼내느냐? 라고 물으신다면, 물체 표면에서 빛이 반사되는 성질을 우리는 이 여러가지 수학적 모델로 풀어내고 있으며, 또 위에서 말한 Diffuse, Specular 를 설명하는 것에도 역시 필요하기 때문입니다.
자, 맥스를 켜 보신다면 다음과 같은 매트리얼창을 참 많이 보셨을 겁니다. 이것이 바로 BRDF를 선택하는 풀다운 메뉴입니다. 이외로 가까운 곳에 있지요... ^^
 
일반적으로 Diffuse에 관한 모델은 Lambertian model, Oren–Nayar model 들이 대표적입니다.
그리고 Specular 모델은 리얼타임 게임쪽에서는 Blinn–Phong model 모델을 주로 사용합니다.
그리고 다른 방법으로 Reflection, Refraction이 추가가 될 것입니다. (여러가지 트릭과 기법으로...)

제가 이 글을 쓰게 된 계기는, 과연 매트한 재질에서 디퓨즈와 스페큘러를 알았다면, 이걸로 대부분의 조명을 설명해 낼 수 있는가- 였습니다. 그리고 계속 거리와 풍경, 발밑을 보면서 걸었습니다. 반짝이는걸 다 스페큘러 이런걸로 해석해봐도, 뭔가 좀 부족한게 아닌가... 이런 의문이었습니다. 특히 지하 복도 같은 곳에서는 특히 심했습니다. 그러다 한 곳에서 재미있는 글을 읽게 되었습니다.  
모든물체는 프레넬을 가진다- 라는 글이었습니다. 전 이 글을 읽고 너무나 기뻤습니다.
이참에 번역을 옮겨보도록 하겠습니다.

===============================================================================================
모든 물체는 프레넬을 가진다 - 
게임에서 가장 주요하게 쓰이는 Blinn–Phong model모델의 다이어그램을 보겠습니다.


H = normalize(V+L);
specVal = pow(saturate(dot(H,N)),power);


V는 View Vector, 즉 카메라의 위치를 가리킵니다. L은 Light 광원의 방향입니다.
N은 노멀 벡터, H는 둘의  하프벡터입니다.
공식과 그림에 따르면, 구체의 어느 지점이 가장 밝을(스페큘러 밸류가 높을)까요?  광원에서 들어온 빛이 바로 카메라를 직격하는 곳일겁니다. 기술적으로 말하면 하프벡터가 노멀과 일치하는 곳이 가장 밝을 것입니다. 광원이. 반사되서, 카메라를 때린다. 네 명확합니다.  

자 그럼 이번 다이어그램을 보겠습니다. 자 여기서도  노멀과 하프벡터가 일치한다고 한다고 가정하면, 위와 거의 일치하는 밝기가 나타나야겠죠? 이론상으로는 맞습니다만 실제 현실에서도 그럴까요?

답은 NO 입니다.



많이 보셨지요? 이런 현상. 단순히 사각 벽돌 매트리얼에서도 블린-퐁 스페큘러 모델이 다르게 나타납니다. 좌측은 편광필터로 디퓨즈만 뽑은것이고 우측은 스페큘러를 뽑은 사진입니다.
Glancing Angle( 아주 얕은 각도) 에서 같은 광원이라 할지라도 스페큘러가 훨씬 밝게 보이는 현상,
이걸 바로 프레넬 현상,  Fresnel Effect라고 합니다.
리얼타임 게임에서 메탈등에 사용되는 BRDF Schlick approximaion model을 사용하면 (GPU Gems 3 chapter on skin:) (Schlick 의 Approx 공식은 다음과 같습니다. )

float base = 1 – dot(V,H);
float exponential = pow( base, 5.0);
float fresnel = exponential + F0 * (1.0 – exponential);
specVal *= fresnel;

이러한 형식으로 프레넬의 밝기를 구할 수 있습니다.

사람들은 흔히 프레넬 현상을 알고 있다고 하더라도, 반짝이는 물체에서만 이 현상이 일어난다고 믿는 경향이 있습니다. 물이나, 유리, 금속 같은 경우지요. 그렇지만 사실은, 거의 모든 물체에서 강력하게 나타납니다. 오히려 반짝이지 않는 재질에서 더 극적으로 발생한다고 생각합니다.  

PVC 파이프 정면

 PVC 파이프 얕은 각도.  

골판지 상자

골판지 얕은 각도
 

짱돌

짱돌 얕은 각도
 

수건

수건 얕은 각도

하나더 짚고 가자면 X-Rite 카메라 칼라체커를 사용할때 주의사항으로, 언제나 카메라에 직각으로 대고 사용할것을 권장합니다. 왜 그런지 이제 논의 할 필요는 없겠지요. (당연히 더 밝아지니까요 색상이 다 변해버립니다. )

자 위 글까지는 그냥 프레넬에 관한 글과 공식을 옮긴것이고, 과연 그럼 프레넬이란 어떤현상인지 좀더 알아보겠습니다.

Augustin-Jean Fresnel  (/freɪˈnɛl/ fray-nel; French: [ɔɡystɛ̃ ʒɑ̃ fʁɛnɛl]; 1788–1827),


오귀스텡 쟝 프레넬. ( 프레스넬이 아닙니다 s가 묵음이라... 후... 저도 최근까지 프레스넬이라고 했는데 부끄럽네요 ///ㅅ/// ) 그냥 프레넬이라고 하겠습니다. 이분은 16세기말, 17세기에 파동광학 Wave Optics 의 확립에 크게 기여한 프랑스 엔지니어입니다.
 
1655년 뉴턴이 프리즘으로 스펙트럼을 분리한 이후, 반사와 굴절에 관한 연구는 그야말로 급진전했습니다. 16세기가 대항해시대가 한창이었을때니...

물체의 망원경이나 렌즈와 같은 옵티컬 디바이스에 대한 연구의 요구와 맞물려 수많은 발명이 이루어 졌습니다.  하지만 당시 빛이 프리즘에 분리되는걸 관찰하고 뉴턴은 빛은 입자다 하는 입자설을 주장했습니다, 지금에 와서는 입자와 파동이 혼재한다는걸 정설로 하고 있지만, 이 프레넬이란 분이 19세기에 파동설이 입자설을 대치하게 한 장본인중 한명이라니 대단하죠...
  물체의 반사와 굴절을 다루다보면 만나게되는 스넬의 법칙 Snell’s Law,  

왜 갑자기 또 뜬금없이 스넬의 법칙이냐고요? 왜냐하면, 스넬의 법칙은 바로 이 프레넬 방정식의 일부이기 때문입니다.
스넬의 법칙에 따르면, 두 매질medium이 마주하는 경계면에서 임계각 (Critical Angle)을 넘으면 전반사가 일어난다. - 즉, 입사된 모든 빛이 반사된다는 것입니다. 수학공식을 나열하면 머리가 엄청나게 아프실지도 모를 아티스트분들을 위해 사진을 준비해 보았습니다. 출처 : <동영상으로 보실분들은 요기

자 어떠셨나요?  이제 낮은 입사각에서 두 물체에서 반사가 일어나는 걸 아셨지요?

유리쪽에서 보면 40’ 언저리입니다. 유리에서->공기간의 Critical Angle은 41.8' 입니다.
 

이제 이 그림을 보시겠습니다.

좌측 그림에서 수영장의 수면이 거의 직각일때는 내부가 훤히 비쳐 보이지만, (거의 Tranmitted 되버림) 우측처럼 비스듬이 수면을 바라보면 하늘만이 거의다 반사되어 보이고 풀 내부는 거의 보이지 않습니다. (Reflection) 이는 위 Snell’s Law를 적용 받아 IOR(굴절율) 계수가 높아질수록, 점점 더 정면에서 벗어날수록, 각이 더 얕아질수록 반사가 더 강하게 일어난다는것입니다. (구체라면 중심부는 어둡고 주변부가 더 밝아 지겠지요)
 입사각 > 임계각 = 반사Reflection
 
입사각 < 임계각 = 굴절Transmission 

출처 : SketchupUcation

Glossiness는 실제 반짝거리는 강도를 결정한다면,

IOR(굴절율)이 강하면 강할수록 주변광 반사의 영향이 증가한다는것을 알 수 있습니다.

실제로도, Fresnel Reflection을 이용하는 매트리얼은 IOR 핸들러가 거의 반드시 있습니다. (ex) CryEngine, Unreal Engine 등)
비록 이것은 단지 빛을 투과하는 두 매질에서만 일어나는 것이 아니라는 것입니다. 실제 여러분의 현실에서 직각으로 볼때, 거의 제 색으로 보이다가, 비스듬히 보았을때 밝아지는 거의 모든 사물들이 있습니다. 이것은 단지 게임에서 쓰는 스페큘러 BRDF 모델인 Blinn-Phong 으로 설명할 수 없기에, 우리는 이것을 프레넬 효과(Fresnel Effect) 라고 부릅니다. 요즘엔 림라이트셰이더 rim-light shader로 많이들 fake하는데 바로 이 프레넬효과를 immitate하는 것입니다.  

대표적으로 카페인트. 단순히 블린-퐁 스페큘러만으로는 이런느낌은 힘들죠 시야각이 얕아질수록 점점더 밝아집니다.

다음으로 수면. 굴절이 되면서 수면아래 바닥이 보이네요.

그러나 Critical Angle을 넘어가면서 거울처럼 변해버립니다.


제가 출근길에 마구 찍은 사진입니다. 사실 프레넬 이펙트 안들어간거 찍기가 더 힘들어요. 가능하다면 게임에서도 향후 모든 매트리얼에 들어가지 않을까 합니다. (바램)

 

얼굴에 있는 유막때문에 사람피부 표현에도 프레넬을 적용한 예. (실제로는 훨씬 수많은 기법이 쓰였습니다만, 프레넬만 보겠습니다) 

Comparing Fresnel Formulations

When Computing Kelemen/Szirmay-Kalos Specular Reflectance

http://http.developer.nvidia.com/GPUGems3/gpugems3_ch14.html


자 이제 프레넬 효과에 대해 한번 알아보았습니다. 요약하면, BRDF Blinn-Phong만으로 설명할수 없는 빛의 스페큘러 반사가 존재한다.
그 반사는 정면에서 바라보면 크게 눈에 뜨이지 않지만, 얕은 각도로 보면 물체의 표면이 밝아진다. 이를 프레넬 효과라 한다. 이는 물체 경계면에서 굴절율IOR 에 따라 다르게 나타난다. 그리고...
모든 물체는 프레넬 이펙트를 가진다.
우... 기나긴글 수고하셨습니다. 이걸 다 읽으신 분이 있다면 진심으로 존경스럽습니다. 그리고 서투른 글 보아주셔서 감사드립니다. 솔직히 연구해가면서 쓴 글이라 틀린게 많을것 같아 걱정입니다. ㅠㅅㅠ아마 이걸 보신 다음 나가서 주변의 사물을 보시면 조금은 더 다르게 느껴지실까요? 해질녂 노을에 비친 풍경이나 바다를 보면서 한번쯤 생각해 주세요. 오귀스땡 쟝 프레넬이라는 멋진 사람을...그리고 이런것들을 하나하나 표현하려고 애쓰는 현대 그래픽 프로그래머들과 아티스트들도 말입니다.

그럼, 오늘도 부디 좋은 하루 되세요! ^^
 
반응형
,
Posted by 알 수 없는 사용자
안녕하세요? 요양과 잉여짓으로 바쁜 Rhea Strike 입니다.
정작 당분간 하지 않을려했던
잉여 프로그램들은 짜고 있지만 가장 맘먹은 동인지 원고와 짤 제작, 여친 만들기 등이 늦어져 무척 가슴 아픈 나날입니다.

다들 쟁쟁한 주제들과 현업에서 연구하신 소재들로 막강한 화력을 자랑하시지만
실력이 딸려 제가 준비한 것은 네트워크 게임을 만드는 튜터리얼 연재입니다.
이 연재를 시작하기전 무슨 주제로 어떻게 이어갈까 고민을 잠시 했는데요,
잠시 이 링크 http://rhea.pe.kr/493 를 읽어주시고 연재글을 봐주시면 감사하겠습니다.

연재의 초기는 기본적인 채팅으로 시작될 것입니다.
이는 네트워크 프로그래밍에 익숙하지 않는 분들과 있을지도 모를 학생들을 위한 것이기도 합니다만,
매 연재 속에 점점 단순 채팅 프로그램이 게임과 아키텍쳐로 점차 커져가며
퇴근길 지하철에서 전체 게임을 위해서 한번 곱씹어 생각해볼만한 꺼리를 던져드리는게 제 목적입니다.

자, 그럼 지루한 서론을 마치고 우선은 모두들 알고 계신 내용이라도 복습의 의미로
기초부터 시작해보지요. ...역시 지루하군요.

 

짤에 별다른 의미는 없습니다만 이런 여친을 찾고 있습니다. 도움을 바랍니다.

짤에 별다른 의미는 없습니다만 이런 여친을 찾고 있습니다. 도움을 바랍니다.


1. 윈속의 역사
이미 게임을 하나 운영하고 있는 곳이라면 많은 것이 갖춰져 있습니다.
개발자 이외에도 DB, 서버, 패치 시스템, 그리고 그것을 돌려주는 여러 DBA, SE분들이 있습니다.
그러나 아무 것도 없는 곳에서 새로 온라인/네트워크 게임을 만든다면 맨땅에 헤딩을 해야 합니다.

우선 기획서가 나오고 DB라던가 여러 가지 인프라들이 먼저 있어야할 것이지만,
가장 단순하게 윈도우 서버와 클라이언트부터 만든다고 가정합시다.
자, 그럼 서버는 어떻게 만들어야 할까요?
서버를 만들기 전에 서버와 클라이언트에는 어떤 "Winsock Model"을 사용할런지부터 결정해야 합니다.

원래 소켓은 Windows 운영체제가 아니라 BSD UNIX에서 먼저 만들어졌습니다.
그래서 초창기 윈도우 3.1 같은 경우에는 소켓(http://www.joinc.co.kr/modules/moniwiki/wiki.php/man/12/BSD%20socket)
이 아예 존재하지 않았습니다.

이는 단순 소켓 지원 여부를 떠나 당시 마이크로소프트(앞으로 MS)의 중요한 의사 표방이기도 합니다.
소켓은 TCP/IP, UDP를 위한 라이브러리이고 TCP/IP, UDP같은 프로토콜은 바로 인터넷의 기반이 되는 프로토콜들이죠. 모든 것을 집어삼킨 HTTP 역시 TCP/IP 80번 포트를 이용한 텍스트기반 프로토콜 아니겠습니까?

다시 말해, MS는 현재의 인터넷이 이리 사용될지 몰랐습니다, 무려 90년대 중반까지도요!
MS는 소켓과 TCP/IP가 아닌 자신들의 프로토콜로 MS-Network라는 현재의 인터넷과 비슷무리하지만 다른 개념을 만들고 싶었습니다.

그 망상은 Windows 2000 이 등장하면서 공식적으로 사라졌는데 \\로 접근하던 다른 PC접근과 네트워크 공유를 NetBIOS(NetBIEU) 설치 없이 TCP만 설치하면  되도록한 것이 그 증거(?)라고 볼수 있습니다. 정확하게 말하면 NetBIOS over TCP/IP 이란 개념인데 아뭏든 90년대에도 MS는 인터넷도, TCP도, BSD 소켓도 반대! 그냥 MS껄로 다 정ㅋ벅ㅋ할꺼야!!였었습니다.
그래서 Win 3.1에서 Netscape로 웹서핑을 하던 우리들은 Win 3.1에는 없는 TCP/IP 프로토콜을 쓰기 위해
트럼펫 윈속(http://www.trumpet.com.au/index.php/downloads.html) 을 따로 설치해 썼었었습니다.
진짜 일반 사용자가 쓸수 있는 MS표 소켓이 나오는 것은 Win95 때부터입니다.

추억 돋는 화면 하나 더! http://www.loblolly.net/~rddecker/helppages/win31trmwns.htm


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




...........





.....


..


그러나 어쩌겠습니까?
모두가 알다시피 인터넷(다시 말해 TCP/IP과 그것을 쓰기 위한 BSD Sockets)이 대세가 되었는데요.


이 당시 빌횽의 마음...

이 당시 빌횽의 마음...









할수 없이, MS는 BSD 소켓과 똑같은 윈도우용 소켓 라이브러리를 제작합니다.
BSD 소켓 4.3에 기반해 1993년 WinNT 3.51과 함께 Winsock 1.1을 공개합니다.
즉, Winsock은 WINdows + SOCKet의 약자지요.

정말로 이때 빌횽은 전세계 개발자들에게 이말을 들었습니다.

정말로 이때 빌횽은 전세계 개발자들에게 이말을 들었습니다.




이 윈속 1.1은 놀랄만큼 BSD 소켓과 동일합니다.
고인이신 스티븐슨의 UNIX Network Programming에 있는 소스 대부분이 헤더 파일만 약간 바꾸면 그대로 돌아갈 정도입니다.

네트워크 게임계의 프로메테우스, 古 리차드 스티븐슨

네트워크 게임계의 프로메테우스, 古 리차드 스티븐슨

이는 다이렉트X 초창기의 모습과 유사합니다. DOS 게임 개발자들에게 복잡한 Windows Procedure와 콜백을 잘 몰라도 DOS 게임을 그대로 포팅할수 있도록 한 배려와 비슷한 모습입니다.

하지만 이때까지도 MS는 TCP/IP의 가능성을 잘 몰랐습니다.
...그래서 Winsock 1.1을 사용하는 Windows 서버는 ....상당히 구렸습니다.
윈도우는 서버로는 절대 안된다는 말이 이때부터 흘러나왔죠.

그러나 1995년 WinNT 4.0의 등장과 함께 선보인 Winsock 2.0부터 상황은 달라지기 시작합니다.
아키텍쳐부터 싹 달라졌습니다.
1.0과의 호환성은 100% 유지하면서 Windows에 최적화된 진짜 Winsock이 등장한 것입니다.
(달라진 아키텍쳐로 "개인방화벽" 프로그램이 만들어질 수 있었습니다. 자세한건 "SPI"로 검색을 ^^)
오오미 윈속이랑께~!!

오오미 윈속이랑께~!!

현재 우리가 주력으로 사용하는 Winsock은 16비트 모드가 제거되어 1996년에 발표된 2,2를 사용하고 있으며
Win8이 등장할때 Winsock에 Registered IO extensions 이 추가된다고 하는데 간만에 Winsock에 새로운 기능이 추가되는 것이라 기대가 큽니다.

2. 윈속 모델

모델의 좋은 사례, Winsock에게도 공식 모델이 필요합니다.

모델의 좋은 사례, Winsock에게도 공식 모델이 필요합니다.


드디어 오늘 기초의 핵심인 윈속 모델 이야기를 하겠습니다.
안타깝게도 윈속이 버전업이 되었다고 해서 윈속의 성능이 공짜로 올라가는 것은 아닙니다.


네트워크 프로그래밍에서 첫쨰 난관은 언제 "1) 누가 서버에게 Connect를 해 올줄 모른다"는 점입니다. 그리고 이때 클라이언트가 Connect를 해오는 순간, 아주 빠르게 accpet()를 해줘야 합니다. 접속이 수천개가 되면 이 작업 역시 딜레이가 걸리고 컨텍스트 스위칭이 걸립니다.
그런 다음, 일단 양쪽이 연결이 되면 "2) 서버든 클라든 언제 상대방의 메시지를 듣기 위해 recv()를 해야할지 모른다"는 점입니다(아시겠지만 연결을 끊는 close() 역시 사실은 상대방에게는 recv()입니다.)

언제 이벤트가 오는지 모르는 싯점을 체크해봤습니다.

언제 이벤트가 오는지 모르는 싯점을 체크해봤습니다.

이미지 출처 : http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Frzab6%2Frzab6connectionor.htm

사실 게임 클라이언트처럼 소켓 5개 정도에서는 큰 차이가 없습니다만,
서버는 한 프로세스가 수백~수천개의 접속을 관리해야 합니다.
이를 위해 단일 thread를 돌리는 방법으로는 고작 수십명이면 금방 한계에 찹니다.
아무리 서버장비가 좋아도 똑같습니다.
왜 단일 thread는 안될까요?
thread라는게 마법은 아닙니다. 단순히 CPU 한계치까지 일을 시킬 뿐, 계속 루프를 돌며 찌질하게 검사하는 역활 밖에 할 수 없습니다.

좋은 서버란 가장 적은 thread와 CPU를 사용해 가장 많은 접속을 빠르게 처리해줄수 있어야 하며 이를 위해 두가지 개념을 먼저 알아야 합니다.

우선 비동기(asynchronous )라는 개념이 들어갑니다.
쉽게 말해 계속 루프를 돌며 검사하는게 아니라 클라이언트에게 접속요청이 왔거나 메시지가 왔을때에만
해당 소켓에게 알맞은 작업을 해주는 것입니다.

그래서 비동기 Winsock을 위해 4가지 모델이 고려되었습니다.







이중 알맞은 Winsock Model 네명을 고르시오.

이중 알맞은 Winsock Model 네명을 고르시오.






...는 아니고...





 모델명 특징 기타
 Window Message Select
      (WSAAsyncSelect)
소켓 이벤트를 윈도우 메시지를 이용해 알려준다.  윈도우 메시지를 이용하므로 반드시 윈도우가 생성되어야 하며 MFC의 CSocket 클래스가 바로 이 모델.
 Kernel Event Select
     (WSAEventSelect)
소켓 이벤트를 커널 이벤트로 알려준다.  한 객체당 최대 64개의 이벤트 밖에 못쓰므로 서버로 쓰기 위해서는 코드가 쓸때없이 복잡해짐.
 Overlapped I/O
    (Overlapped)
소켓 이벤트를 비동기 읽기/쓰기를 위한 오버랩 구조체를 이용해 알려준다. 위의 둘과 달리 Overlapped와 IOCP는 Win95, 98에서는 안됩니다.

이 기법은 파일에 비동기로 읽기쓰기를 같이 하기 위함이었으나 소켓이든 파일이든 둘다 같은 HANDLE 이잖아요? ^^

역시 MS의 꽁수의 결정체라 불릴만 합니다.
 Overlapped I/O + IOCP
    (IOCP)
위의 방식에 더해 커널 큐잉을 통해 소켓 이벤트를 통보받는다. 사실상 윈됴에서 서버는 이넘으로 정해져있다. C#에서 비동기 선언해도 IOCP가 만들어진다.

결론은 서버는 IOCP가 정답입니다!
...이런 모델들입니다.


두번째는 넌블로킹(non blocking)개념입니다.
단일 thread가 아닌 비동기 서버를 짰다고 합시다.
이때 어떤 이벤트가 일어나면 비동기로 OS가 서버에게 알려줍니다.
그러나 send(), recv() 같은 함수는 블로킹(blocking)함수 입니다.
send(), recv() 작업이 끝날 때까지 서버 프로그램은 다음 일을 할수 없습니다.
이때 WSASend(), WSARecv같은 넌블로킹 함수로 대체한다면, 작업이 끝나기 전에 서버는 다음 작업으로 진행될 수 있습니다.

아마 넌블러킹 함수는 네트워크 프로그래밍에서 처음 보신 분들도 있을 것이지만,
수많은 I/O관련 함수들의 고급은 전부 넌블러킹으로 흘러가게 됩니다.
넌블러킹 함수는 어쩔수 없이 Callback 함수가 반드시 뒤따라갑니다.
그래서 처음 접하면 조금 어렵습니다............................................가 아니라 실은 많이 어렵습니다,
거기에 Overlapped나 IOCP같은 비동기 통보까지 더해지면 아주 복잡해집니다.

다행히 클라이언트에서 주로 쓰이는 WSAAsyncSelect 모델과 WSAEventSelect 모델에서는 넌블로킹이 그리 중요한 개념은 아닙니다. 유선 인터넷이 하도 빨라서요... 
클라이언트에서 넌블로킹을 잘못 쓰면 소켓 응답 오기 전까지 게임 프로그램 자체가 위험해질수도 있고
그 사이 화면 UI처리같은 다른 할일들도 신경을 써야 합니다(<-이거 은근 고급 토픽입니다.).

그렇다고 클라이언트=블로킹이 절대 아닙니다.
최근 스마트 디바이스에서 SNS클라이언트와 SNG와 같은 게임에서는 불안한 3G 네트워크 때문에
요즘은 클라이언트도 비동기 + 넌블로킹이 대세입니다.

3G가 워낙 느리고 자주 끊기니, 일단 클라이언트에서는 send()한 것으로 치고
유저에게 다음 작업을 할수 있도록 해주는 것입니다.
혹시 안된다면 나중에 에러 메시지가 뜨며 다시 하라고 알려줍니다.

SNG를 안하시는 분들도 트위터같은 앱에서 자주 보셨을 것입니다.
재미있게도 이런 처리 방식은 트위터 웹페이지에서도 확인할 수 있습니다.
물론 저도(사실은 밑에 팀원 갈궈서 하게 했습니다... >_<;;; ) SNS 만들때 이렇게 했고요.

만약 단순 블로킹이라면 이런 상황 자체를 볼수 없을 것입니다.

만약 단순 블로킹이라면 이런 상황 자체를 볼수 없을 것입니다.


이는 We Rule같은 게임이 네트워크가 불안정할때 쉽게 확인될수 있습니다. 단순해 보이는 SNG도 3G라는 현실로 제법 복잡한 네트워크 구현 기술을 알아야 합니다.

...이슬이...드립 치고 싶습니다.


3. 오늘의 소스
오늘의 소스는 간단한 클라이언트/서버 소스입니다.
클라이언트는 MFC로 만들었고 Winsock Model 역시 CSocket, 즉 윈도우 메시지 방식입니다.
서버는 boost::asio로 짰습니다. 기본 TCP 서버를 그대로 썼습니다.
단, 한 파일로 설명된 소스를 보기 좋게 클래스별로 나눠놨습니다.
이 정도면 네트워크 프로그래밍에 익숙하지 못한 초보분이라도 약간의 노력이면 다음 시간부터 따라오실수 있을 것입니다.

이 간단한 프로그램들로 네트워크 게임을 위한 첫 삽질이 시작될 것입니다.




4. 더 생각해볼꺼리
1) 심심하고 정말정말 할일이 없는 날, 단일 thread 방식의 채팅 서버와 윈도우 메시지 방식의 채팅 서버를 만들어 최대 몇개의 클라이언트에게 원할하게 메시지를 돌려주는지 직접 재어보세요.
2) 클라이언트가 블로킹으로 작동되어야할 경우와 넌블로킹으로 작동되어야할 경우를 생각해보세요.
3) 클라이언트가 블로킹과 넌블로킹, 모두가 동시에 필요하다면 소켓 라이브러리를 어떻게 만들어야할까요?
   (힌트 : MFC에서 CSocket 와 부모 클래스인 CAsyncsocket를 살펴보세요.)
4) 실제 게임회사의 면접에서 IOCP의 동작 메커니즘을 구두로 답변하기는 무척 어렵습니다.
    그래서 저는 이렇게 묻습니다,
    "왜 IOCP 소스에는 recv() 함수가 두번 적혀있나요?"
    여러분은 머라고 대답하시겠습니까?


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

// 원래는 1월 12일에 포스팅해야 하는데 퐆프님께 허락받고 스케쥴보다 일찍 올립니다. 기획 카테고리엔 글이 너무 적어요 ;ㅅ;

전편에서는 스타크래프트 유닛의 이동을 예로 들면서 재정의(Override)를 이용한 다형성(Polymorphism)에 대해서 간단하게 설명해보았습니다. 하지만 아마도 C++, JAVA 등과 같은 객체지향적 프로그래밍 언어를 전혀 공부해본적이 없는 사람이라면 아직 뭐가 뭔지 제대로 이해하기는 어려울 겁니다. 하지만 너무 낙심할 필요는 없습니다. 현업 프로그래머라도 경험과 시행착오가 쌓이지 않으면 제대로 활용하지 못하는 것이 바로 이 개념이니까요.

그런데, 두 번째 포스팅을 이어가기 전에 한 가지 짚고 넘어갈 점이 있습니다. 기획자가 왜 프로그래머의 영역인 객체지향이나 추상화를 알아야 하는가하는 '논란'입니다.

흔히 기획자가 프로그래밍을 공부하는 경우의 단점으로 '창의성 저하'를 많이 언급합니다. 프로그래밍을 공부한 기획자는 어떤 아이디어를 떠올릴 때 구현여부를 먼저 고민하기 때문이라는 이유죠. 물론 그런 기획자도 있을 수 있겠지만, 어설프게 프로그래밍을 공부했다고 해서 어떤 아이디어가 기술적으로 구현할 수 있는지를 정확하게 판단하는 것 자체가 불가능한 일입니다. 그리고 게임 개발에 있어서 '이 아이디어는 구현할 수 없다'는 판정을 받는 경우는, 그것이 기술적으로 불가능하다기보다는 개발의 효율성이나 최적화의 관점에서 포기하는 경우가 대부분입니다. (또는 기획서가 무슨 말을 하는 건지 이해하기 어렵거나, 만들기 귀찮아서일 수도 있음)

즉 구현이 불가능한 것은 아니지만, 프로젝트의 목적에 부합하지 않거나, 혹은 개발기간이나 비용, 엔진성능 또는 유저 시스템 요구사양의 한계 때문에 포기하는 것이죠. 어떤 아이디어의 기술적인 구현여부나 방법을 최종적으로 결정하거나 판단하는 것은 기획자가 아니라 테크니컬 디렉터(TD) 정도의 실력과 짬밥이 쌓여야 가능한 일입니다.

이러라고 프로그래밍 공부하라는 소리는 아닙니다

그렇다면 게임 기획자, 그 중에서도 시스템 기획자는 왜 프로그래밍을 공부하고 객체지향이나 추상화의 개념을 알아야 할까요? 그것은 기획자가 어떤 아이디어의 구현 여부를 1차적으로 판단하기 위한 것이 아니라, 자신의 기획 아이디어를 프로그래머들에게 정확하게 전달하고 소통하기 위함입니다. 기획자 혼자서 게임을 완성시킬 수 있는 툴을 쓰지 않는 한, 여러분의 아이디어는 대부분 프로그래머를 통해 실체화됩니다. 그렇기 때문에 프로그래머가 이해하기 좋은 표현을 통해 아이디어를 전달하면 기획의도대로 더욱 정확하게 구현할 수 있고 더 효율적인 개발을 할 수 있게 됩니다.

그러면 이번에는 좀 더 실무적인 추상화의 예를 들어보겠습니다.

제가 게임기획 강의를 할 때, 수강생들에게 WoW 마법사의 스킬을 분류해보라고 하면 보통은 분류방법이 두 가지로 나뉩니다.

(1) 화염, 냉기, 비전

(2) 캐스팅(주문의 시전 시간이 필요한 스킬), 즉시시전, 광역마법...


1번 같은 분류는 일반적인 유저의 입장, 즉 그냥 게임에서 보여주는대로 분류하는 개념이죠. 콘텐츠 기획서라면 모를까 시스템 기획서에서는 적합하지 않은 분류입니다. 반면에 2번과 같은 분류는 1번보다는 조금 낫지만, 아직도 좀 부족하죠. 하지만 여전히 많은 시스템 기획자들이 2번 정도의 수준으로 기획을 합니다.

그런데 문제는 2번 같은식으로 스킬의 명세를 기획서로 써서 프로그래머에게 전달하면, 구현하는 것 자체는 가능하지만 확장성이나 유연성이 떨어지는 결과물이 나온다는 점이 문제입니다. (프로그래머의 입장에서는 코드의 중복, 유지보수성의 문제도 발생함)

구체적인 예를 들어보죠. WoW를 접은지 꽤 오래되서 정확한 스킬 명칭은 기억나지 않지만, 흑마법사의 마법 중에는 처음엔 시전시간이 2.5초지만, 해당 스킬의 시전 시간을 줄여주는 특성을 찍으면 0.5초씩 시전 시간이 감소해서 5포인트의 특성을 투자하면, 즉시시전 스킬로 바뀌는 경우가 있습니다. 만약 2번과 같은식의 분류에 기초한 스킬 시스템 기획서를 그대로 구현했다면, 이와 같은 예외적인 스킬은 코드를 갈아 엎거나, 아니면 이런 예외적인 스킬을 구현하기 위한 별도의 코드를 새로 작성해야만 합니다.

게다가 시전시간이 필요하면서도 광역마법인 경우라든지, '화염구'와 같이 특정 대상을 향해 발사체를 시전하는 캐스팅 스킬이지만, 발사체가 대상에 명중한 후에 광역효과가 발생하는 스킬 같은 경우 등 기존의 설계와 다른 예외 상황이 조금만 발생해도 코드를 갈아엎거나 아예 별도로 작성해야 하는 엄청난 비효율이 발생할 것입니다. 게다가 이런 비효율만 발생하고 끝나는 것이 아니라, 계속된 예외상황의 추가에 따른 고질적인 버그도 함께 따라다닐 겁니다.

따라서 시스템 기획 단계에서부터 객체지향적 설계방식을 숙지하고 있는 기획자가 기획 아이디어를 1차적으로 추상화하거나 혹은 추상화의 단서를 제공하여 프로그래머와 함께 조율을 하는 것이 서로에게도 좋고 개발 프로세스 전체에 있어서도 바람직하다는 것이 필자가 이 포스팅을 통해 전하고 싶은 핵심입니다.

그러면 추상화는 대체 어떻게 해야 할까요? WoW의 스킬을 예로 들자면, 화염구, 얼음 회오리, 돌진, 방패막기, 각종 버프 등과 같은 각각의 스킬은 구체화된 객체(object)라고 할 수 있습니다. 객체지향 프로그래밍에서는 이렇게 구체화된 최종 결과물을 인스턴스(instance)라고 부르죠. 여러분들이 만약 스킬 시스템을 기획한다면, 초기 기획단계에서 구상한 스킬 목록을 바탕으로 공통점을 추려내서 비슷한 것끼리 분류하는 작업이 바로 추상화입니다.

그런데 문제는, 객체지향에서 말하는 추상화를 제대로 이해하지 못한 상태에서 공통점을 추려내면, 바로 위에서 말한 1번이나 2번과 같은 분류 정도 밖에는 할 수 없다는 사실입니다. 그렇다면 객체지향을 공부하고 C++ 같은 언어를 좀 만져봐야만 하느냐? 물론 공부를 하는 것이 좋습니다. 적어도 여러분이 시스템 기획자로 성장하기를 원한다면, 최소한 C++ 책 한 권 쯤은 독파하시기를 권합니다. 저는 오래 전에 객체지향적인 프로그래밍 언어를 공부하지 않은 상태에서 이론적으로만 객체지향을 공부한 적이 있었는데, 확실히 직접 코드를 만지면서 공부하는 것보다 훨씬 이해하기 힘들었습니다. 하지만 이런저런 이유로 아직 프로그래밍을 공부할 상황이 안 되거나 공부해도 잘 이해하기 어렵다면, 일단은 워크래프트3 에디터 같은 좋은 에디터 툴을 다뤄보면 아주 큰 도움이 됩니다.

워3 에디터의 경우, 툴의 오브젝트 에디터에 워3에서 기본적으로 제공하는 유닛과 스킬, 기타 오브젝트들에 대한 데이터를 볼 수 있습니다. 그런데 이것들을 잘 살펴보면, 그 다른 건 전혀 건드리지 않고 단지 데이터만 수정해서 새로운 유닛이 되거나 새로운 스킬이 만들어질 수 있습니다.


예를 들어 화염구와 같은 스킬은 이름을 냉기화살로, 데미지 속성값을 냉기타입으로, 발사체 모델링을 얼음덩어리로 수정하면 화염구가 아닌 냉기화살이 돼죠. 워터엘레멘탈 소환 스킬은 소환물 데이터를 드래곤으로 바꾸면 드래곤 소환스킬로 변합니다. 이런 일들이 가능한 이유는 처음 시스템을 설계할 때부터 확장성과 유연성을 가질 수 있도록 철저한 추상화를 거쳤기 때문입니다. 그리고 이와 같이 데이터(값)만 바꿔서 구현할 수 있는 것은 하나의 공통성을 가지고 있다고 할 수 있으며, '개발(구현)의 관점'에서 본다면 하나의 객체로 간주할 수 있습니다.

그럼 다음 포스팅에서는 추상화에 대해서 보다 깊이 있게 들어가 봅시다. 다음 포스팅이 올라올 때까지 여러분이 즐기는 게임에서 특정 시스템을 놓고 데이터만 다르고 나머지는 같은 공통성을 가진 객체들을 분류하는 연습을 한 번 해보시길 바랍니다.

to be continued...

반응형
,
Posted by 친절한티스
안녕하세요. 친절한티스 라는 필명(?)을 쓰고 있는 박기헌입니다. 다른 네임드 필자분들이 워낙 고급 주제로 글을 올려주시는 바람에 저의 쩌렙티가 팍팍나는 글을 읽고 많은 분들이 실망하실까봐 걱정이 이만 저만 아닙니다. 그래도 작게나마 이제 막 프로그래밍에 입문하시는 분들에게라도 도움이 되겠지 생각으로 글을 써볼까 합니다.

RTTI란?
Run-Time Type Infomation 또는 Run-Time Type Identification 이라고도 합니다. 한글로 풀이하자면 "실시간 형식 정보" 라고 할수 있겠죠. 그럼 이게 무엇에 쓰는 물건인가? 라고 하면, 일단 다형성에 대해 알아야 할 것 입니다. C++에서는 대표적인 특징 중 하나로 다형성이라는 것이 있습니다. 부모 객체의 포인터로 자식 객체를 가리킬수 있는 것이죠. 간단하게 코드로 봐보자면 밑에와 같습니다.
class Parent
{
public:
	virtual void Print()
	{
		printf( "i'm your father!!\n" );
	}
};

class Child : public Parent
{
public:
	Child() : m_iMagicNum(777), m_pMagicPointer(NULL) {}

	virtual void Print()
	{
		printf( "i'm succeeding you father!!\n" );
	}

	void PrintMemVar()
	{
		printf( "Magic~ Magic~ : %d, %d\n" );
	}
private:
	int m_iMagicNum;
	int* m_pMagicPointer;
};

void main()
{
	Parent *pParent = new Child;
	pParent->Print();
}
위의 main 함수를 보시면 부모 클래스인 Parent 포인터 변수를 통해 Child 객체를 생성하여 Print 함수를 호출하고 있습니다. 여기서는 "i'm succeeding you father!!" 가 출력되겠죠. 이와 같은 코드는 실무에서 빈번하게 일어납니다. 실제 객체가 어떤 모양이든 간에 상관없이 인터페이스 클래스를 통해 객체들을 통합적으로 관리할수 있는 이점을 제공하기 때문이죠.

그런데 여기서 Child의 PrintMemVar 함수를 호출할 일이 생긴다면 어떨까요? pParent는 실제 Child 객체이니까  PrintMemVar 함수를 호출할 수 있어야 합니다. 그런데 pParent는 현재 Parent 형이니  PrintMemVar 함수에 대한 정보가 없습니다. Child 형으로 형 변환을 해주어야   PrintMemVar 함수를 호출 할 수 있죠.
void main()
{
	Parent *pParent = new Child;
	pParent->Print();

	// Child* 형으로 형변환
	Child *pChild = (Child*)pParent;
	pChild->PrintMemVar();
}
잘 작동합니다. pParent가 애초에 Child 객체이니 당연한 결과죠. 하지만 이 코드는 굉장히 위험한 코드입니다. 위의 코드야 앞서 pParent가 Child 객체인 것을 알고 있으니 이런 식으로 코드를 작성할 수 있는 것이지 실무에서는 현재 가리키고 있는 객체가 Child 형인지 Parent인지 또는 전혀 다른 상속 객체인지 알 수가 없습니다.

pParent가 Child가 아닌 Parent로 객체를 생성한 후, Child*형으로 형변환 하여 PrintfMemvar를 호출 해보면 전혀 예상치 못한 값들이 출력됩니다. 실제 프로젝트에서 이런 상황이 발생하게 되면 최악의 경우 크래시가 발생할 확률이 커집니다. 이런 문제를 방지 하기 위해 형변환을 하기 전 객체의 형식 정보를 확인할 필요가 있습니다.

RTTI는 바로 이 확인 작업을 위한 장치로 현재 객체의 형식 정보를 검사할 수 있는 명령어를 제공하고 있습니다. 대표적으로 dynamic_cast가 그것입니다. ( 이 외에, typeid, type_info 등이 있습니다. 자세한 것은  http://msdn.microsoft.com/en-us/library/b2ay8610.aspx 참고 )
void main()
{
	Parent *pParent = new Child;
	pParent->Print();

	// pParent가 Child* 형으로 변환 가능한지 검사
	// pParent가 Child* 형이 아니면 NULL 이 반환된다.
	Child *pChild = dynamic_cast<Child*>(pParent);
	if( pChild )
		pChild->PrintMemVar();
}
위의 메인 함수에 추가된 코드 입니다. dynamic_cast를 통해 형 변환을 수행하면 pParent가 가리키고 있는 객체가 Child 이라면 포인터를 반환하고 그렇지 않으면 NULL 포인터를 반환하게 됩니다. 이를 통해 안전하게 Child 형으로 변환하여 Child에서 정의한 함수를 호출할 수 있습니다.

dynamic_cast의 문제 
dynamic_cast 로 안전하게 다운캐스트( down cast, 부모형에서 자식형으로 형변환 )를 할수 있다면 모든 문제가 다 해결된거 같습니다. 그런데 문제가 있습니다. dynamic_cast가 느리다는 겁니다. dynamic_cast 를 사용하면 내부적으로 RTTI 정보를 체크하고, 변환할 객체에 대한 형식 정보를 비교하는데 이 수행 비용이 큰게 문제입니다. 

게임에서 퍼포먼스는 생명과도 같습니다. 아무리 멋진 게임이라도 프레임이 뚝뚝 끊기거나 느리면 하기 싫어지죠. 그래서 많은 프로그래머들이 1프레임이라도 더 빠르게 하기 위해 많은 노력을 합니다. 느린 dynamic_cast 대신에 빠르면서도 안전하게 다운캐스트 할수 있는 방법은 없을까 생각해볼 수 있죠.

만들어 봅시다
dynamic_cast 보다 빠르면서 같은 기능을 수행할 수 있는 방법은 의외로 간단하게 구현 할 수 있습니다. 
class CRTTI
{
public:
	CRTTI( const wstring strClassName, const CRTTI* pBaseRTTI );
	inline const wstring& GetClassName() const;
	inline const CRTTI* GetBaseRTTI() const;

private:
	const wstring		m_strClassName;
	const CRTTI*		m_pBaseRTTI;
};
위 클래스가 새로운 RTTI 검사를 위한 클래스입니다. 정말 간단하죠? 단순히 클래스 이름과 부모 클래스를 위한 멤버변수 하나가 전부입니다. 그러면 이것을 어떻게 이용해서 객체의 형식을 검사할지 보겠습니다.
// 최상위 클래스에 선언
#define DeclRootRTTI(classname) \
	public: \
		static const CRTTI ms_RTTI; \
		virtual const CRTTI* GetRTTI() const { return &ms_RTTI; } \
		static bool IsKindOf( const CRTTI* pRTTI, const classname *pObject ) \
		{ \
			if( NULL == pObject ) \
			{ \
				return false; \
			} \
			return pObject->IsKindOf( pRTTI ); \
		} \
		bool IsKindOf( const CRTTI* pRTTI ) const \
		{ \
			const CRTTI* pTmp = GetRTTI(); \
			while( NULL != pTmp ) \
			{ \
				if (pTmp == pRTTI) \
				{ \
					return true; \
				} \
				pTmp = pTmp->GetBaseRTTI(); \
			} \
			return false; \
		} 

// 자식 클래스들에 선언
#define DeclRTTI \
	public: \
	static const CRTTI ms_RTTI; \
	virtual const CRTTI* GetRTTI() const {return &ms_RTTI;}

#define ImplRootRTTI(classname) \
	const CRTTI classname::ms_RTTI(L#classname, NULL)

#define ImplRTTI( classname, baseclassname) \
	const CRTTI classname::ms_RTTI(L#classname, &baseclassname::ms_RTTI)


// Parent 클래스는 최상위 클래스다
class Parent
{
public:
	DeclRootRTTI(Parent); // RootRTTI 선언

public:
	virtual void Print()
	{
		printf( "i'm your father!!\n" );
	}
};
ImplRootRTTI(Parent);

// Child 클래스는 Parent로부터 상속 되었다
class Child : public Parent
{
public:
	DeclRTTI;

public:
	Child() : m_iMagicNum(777), m_pMagicPointer(NULL) {}

	virtual void Print()
	{
		printf( "i'm succeeding you father!!\n" );
	}

	void PrintMemVar()
	{
		printf( "Magic~ Magic~ : %d, %d\n" );
	}
private:
	int m_iMagicNum;
	int* m_pMagicPointer;
};
ImplRTTI(Child, Parent); // Parent에서 상속됨을 알린다
매크로 때문에 코드가 복잡해 보일 수 있지만 잘 보시면 별거 없습니다. CRTTI를 각 클래스에 정적 변수로 선언을 해주고, 상위 클래스가 있는 경우 CRTTI의 m_pBaseRTTI 멤버 변수에 상위 클래스의 CRTTI 정적 멤버 변수를 저장하는 것이 전부 지요. 그리고 이 각 클래스의 CRTTI 정적 멤버 변수를 비교함으로서 클래스의 형식 정보를 알아 낼 수 있습니다. 단순 포인터 비교이기 때문에 속도도 빠르지요.

그럼 실제 사용 예제를 보겠습니다.
#define IsKindOf(classname, pObject) \
	classname::IsKindOf(&classname::ms_RTTI, pObject)

void main()
{
	Parent *pParent = new Child;
	pParent->Print();

	// pParent가 Child* 형으로 변환 가능한지 검사
	if( IsKindOf( Child, pParent ) )
	{
		Child *pChild = (Child*)pParent;
		pChild->PrintMemVar();
	}
}


// 좀더 dynamic_cast 와 비슷한 방식으로 사용 하기 위한 방법
// DeclRootRTTI 매크로에 밑의 코드를 추가
static classname* DynamicCast( const CRTTI* pRTTI, \
	const classname* pObject ) \
	{ \
		if( !pObject ) \
		{ \
			return NULL; \
		} \
		return pObject->DynamicCast( pRTTI ); \
	} \
	classname* DynamicCast( const CRTTI* pRTTI ) const \
	{ \
		return ( IsKindOf( pRTTI ) ? ( classname* ) this : NULL ); \
	}

#define DynamicCast(classname, pObject) \
	((classname*) classname::DynamicCast(&classname::ms_RTTI, pObject))

void main()
{
	Parent *pParent = new Child;
	pParent->Print();

	// pParent가 Child* 형으로 변환 가능한지 검사
	Child *pChild = DynamicCast(Child, pParent);
	if( pChild  )
		pChild->PrintMemVar();
}
밑의 DynamicCast 부분을 보시면 dynamic_cast와 같은 방식으로 사용하면서도 수행 속도에서는 훨씬 빠른 형식 정보 검사를 할수 있습니다. 

해결해야 할 점
기존 dynamic_cast에 비해서 빨라졌다 하나 아직은 개선의 여지가 있습니다. 상위 클래스와 비교시 상속 깊이만큼 루프를 돈다는 점이나 메모리 비친화적이라는 점, 매크로를 많이 쓴다는 등이 있겠네요. 이런 점등은 좀더 고민해봐서 개선을 해봐야 할 것입니다. 

스샷 하나 없는 것이 심심해서 울겜 일러스트 한장 투척

 
반응형
,
Posted by 대마왕J

1. 시작합니다.

저번 시간에는 프롤로그에 가까웠지요. 이번부터가 진짜 강의입니다. shaderFX는 다 설치 하셨는지요?
이번 시간에는 shaderFX의 기본 사용법과 기초 이론을 알아보도록 하지요.
또한 shaderFX 를 이용해서 할 수 있는 일도 간단하게 소개해 드릴께요.

2. ShaderFX 기본 사용법

2-1 실행해 봅시다.

이제 ShaderFX가 설치되었으면 우선 실행을 해야 합니다. Render/ShaderFX를 실행해 봅시다.

shaderFX 실행



이런 창이 열릴 겁니다. 왠 도마뱀이 꼬리를 치고 있네요.
등록되었다지 않았다는 말이 나오고 있긴 하지만, 어차피 무료입니다. 사용하는데는 아무런 문제가 없습니다.

이게 shader FX 라고 하는 프로그램이고, 그냥 '또 하나의 메터리얼 에디터' 라고 생각하셔도 아무런 문제가 없습니다. [각주:1]



2-2 쉐이더를 만들고, 오브젝트에 적용해 봅시다.

자 그럼 뭔가 일단 하나 만들어 보고 시작할까요.
가장 만만한 녀석인, 주전자 하나 만들어 봅시다. 그래픽 디자이너라면 이 정도는 쉽게 하시겠지요?
네? 원화 디자이너라서 못하신다고요? 허허허허허허허허허허허허허허허허허허허허허허허허허허허허허허허허허허
이건 기술 축에도 들지 못하는 간단한 기능입니다. 그냥 클릭 두 번만 하면 되는건데요. [각주:2]



주전자가 만들어졌으니 이제 여기에 메터리얼(shader)을 입혀야 겠지요

shaderFX의 창에서 StandardMat_ 어쩌고라고 씌여진 창을 클릭합니다.
이게 지금부터 만들 shader의 루트(Root) 창 입니다. 가장 기본이 되는 베이스예요.

그리고 주전자도 클릭합니다. 이제 주전자에 이 shader를 적용할 예정이니,
반드시 두 개가 같이 선택이 되어 있어야만 하는 거지요.


자, 이렇게 두 개가 모두 선택이 되었다면, 이번엔 shaderFX의 창에서 Tool/Assign Material to Selection 을 누릅니다.


음? 왠 경고가 뜹니다.


경고 내용을 차분하게 읽어보면, '당신은 아직 FX 파일이 어디에 생성될 것인지 결정하지 않았으니 임시로 내가 만들어 놓겠소' 라는 뜻입니다. 뭐, 일단은 큰 상관 없습니다. [확인] 을 눌러서 다음으로 넘어가 보지요.

짜잔.


축하드립니다! 첫 번째 쉐이더를 완성하셨습니다!!! 자 이제 끝!








... 물론 이건 끝이 아닙니다. 이제 시작일 뿐이죠.







2-3 빠알간 주전자를 만들어 봅시다.

프로그래머가 쉐이더를 배울 때, 제일 처음 하는 것이 바로 이 '빨간 공' 만들기 입니다 .
우리는 명색이 그래픽 디자이너이니, 빨간 공으로 만족할 수 없습니다. 빨간 주전자를 만들어 봅시다!!! 간디작살
만들기는 매우 간단합니다.

shaderFX의 화면의 빈 곳에서 오른쪽 클릭을 하면 나오는 메뉴에서, Maps/Color 를 선택하는 것입니다.
Map는 말그대로 맵을 만든다는 것이고, 그 중 "Color Map" 하나를 만든다는 것이지요.

필자의 이름이 그림에 고스란히 노출되었지만, 못 본 척 하고 지나가도록 합시다.


이렇게 하면, 왠 회색 사각형 카드 같은게 하나 나오게 됩니다. 이게 Color Map 오브젝트 입니다.
이름도 Color 네요.
이 Color 오브젝트를 선택하면 오른쪽에 해당되는 인터페이스가 나오므로, 여기서 빨간색으로 바꿔 줘 봅시다.
오른쪽 인터페이스에서 이름도 바꿀 수 있으니까, 원하시면 바꾸시면 되지요.


빨간색 오브젝트가 되었습니다. 공은 아직도 하얀색이지요. 별로 바뀐게 없습니다.

자 이번엔 빨간색 오브젝트 아래에 있는 RGB 라고 씌인 녹색 동그라미를 보십시오.
이걸 마우스 왼쪽 클릭 - 드래그 하면 왠 선이 나오는데 , 이것을 Ambient Color 로 이어줍니다.


선 끝에 float3 라고 씌여 있습니다. 조만간 이걸 설명할테니 관심을 가져 주세요.


그냥 드래그해서 Ambient Color에 놓고 오면 됩니다.
"빨간 맵을 엠비언트라고 하는 요소에 적용" 했습니다.
공이 빨갛게 변했습니다!!! 이런 종북좌파빨갱이!


선을 끊는 방법은, 선을 선택하고 Delete 키를 누르기만 하면 됩니다.
다시 이어주는 것은 언제건 상관없이 할 수 있습니다.


색을 바꾸는 것은 다시 color 오브젝트를 선택하고 , 다른 색으로 바꿔주면 됩니다.
그런데 되지 않습니다. 응?



놀랄 필요 없습니다. 이렇게 칼라를 바꾸는 경우에는 F5나 Color옆의 'Set'버튼을 눌러주기만 하면 적용됩니다.



주의사항 : shader FX로 만들고 적용한 재질은 렌더링되지 않습니다!!!! 보통 그래픽 디자이너들이 말하는 렌더링은 , Render 버튼을 눌러서 파일로 렌더링 하는 오프라인 렌더링 (Offline Rendering)을 의미합니다만, 그것은 불가능합니다.

여기서 만들고 있는 Shader는 뷰포트에서만 보이는 리얼타임 렌더링(Realtime Rendering) 전용이기 때문에 뷰포트에서 보이는 것이 곧 결과물이므로, 이 결과물을 그래픽 파일로 만들고 싶으시면 화면을 캡쳐(PrintScreen) 하는 수 밖에 없습니다.




2-3 저장하기
일단 첫 쉐이더를 완성하셨음을 축하드립니다. 프로그래머들도 이런 식으로 첫 번째 쉐이더를 만들거든요. [각주:3]
어쨌거나 이제 이 쉐이더를 저장하는 법을 알려 드리겠습니다.

shaderFX 에서 저장하는 법은 두 가지가 있습니다. 하나는 shaderFX에서 만든 이 구조 (선을 연결해서 이어 만드는 구조 - 노드구조- 라고도 하지요) 를 .SFX 파일로 저장하는 것과, 여기서 제작한 것을 표준 쉐이더 파일 포맷인 .FX 파일로 저장하는 것입니다.

일반적으로 이 작업을 하실 때에는 .SFX 파일을 꼭 저장하셔야 나중에 불러와 수정하거나 할 수 있기 때문에 이것은 꼭 저장을 하셔야 합니다. 이것이 일반적으로 그래픽 디자이너가 알고 있는 '저장' 의 개념이지요.[각주:4]
.SFX 파일은 shaderFX 전용 파일 포맷으로, 다른데서 쓸 수 없습니다. 오직 shaderFX에서만 쓸 수 있는 파일입니다.

.FX 로 저장한다는 것은, 마치 그래픽 프로그램에서 다른 프로그램으로 파일을 넘기기 위해 익스포트(Export) 하는 개념과 비슷합니다. .FX는 쉐이더 프로그램 언어인 HLSL(High Level Shader Language) 의 표준 파일 포맷으로, 이것으로 저장하게 되면 3D max를 비롯하여 일부의 게임 엔진에서도 이 쉐이더 파일을 직접 사용할 수 있으며, 다른 게임 엔진이라고 하더라도 약간의 수정을 통해 게임에서 실제로 사용할 수 있게 만들 수 있습니다.

.SFX 파일로 저장하기는 어렵지 않습니다.

그냥 우리가 알고 있는 일반적인 저장이기 때문입니다. Ctrl+S 만 눌러줘도 저장할 수 있습니다. .SFX 파일로요.


그런데, 이 쉐이더 파일을 게임엔진이나 맥스에서 사용할 수 있는 전용 쉐이더 프로그램으로 export 하려면, 다른 메뉴를 사용해야 합니다. 이것은 실제로 이 강의 동안에 사용할 일은 없을 것입니다만, 만약 여러분들이 만든 쉐이더를 실제로 게임에 사용해야 할 필요가 생겼다던가 할 때에는 FX 파일로 익스포트를 해 주어야 합니다.

이렇게 저장하기 위해서는 아래 그림과 같이, 3D max에서 사용할 수 있는 FX 파일과
기타 다른 프로그램들에서 사용할 수 있는 FX 파일로 익스포트 할 수 있습니다.


익스포트 옵션은 여러 개가 있습니다. DirectX 를 위한 옵션도 있고, 다른 여러 엔진에 따라 저장할 수 있는 옵션들이 있습니다. 미리 익혀 둘 필요는 없고, 만약 사용할 일이 생기면 그 때 엔진 프로그래머와 상의하면 되겠습니다.


이렇게 FX로 출력하면, "표준 hlsl 언어"로 출력되어, 대부분의 게임엔진에서 어느 정도만 수정하면 사용할 수 있게 됩니다. 그렇지만 연습용이 아닌 이상 실제로 사용하기는 쉽지 않은데요,

그것은 바로 아래와 같이 '코드가 너무 쓸데없이 복잡하게 나오기 때문' 입니다.
단순히 붉은 주전자 하나만 출력하는 쉐이더 코드임에도 불구하고, 최적화를 전혀 해주지 않기 때문에 복잡한 라이트 연산이 모두 들어 있는 채로 출력됩니다. [각주:5]

즉 제작하기 간편한 대신 최적화가 전혀 되어 있지 않은 채로 익스포트 되기 때문에, 사실상 실무에서 사용하기 위해서는 이 fx 파일을 바로 사용하기 보다는 이 파일을 참고해서 재 작성하는 쪽이 훨씬 빠르고 편합니다.



저도 hlsl언어를 직접 다루게 된 이후부터는 이렇게 shaderFX에서 출력한 쉐이더 코드를 직접 사용하지는 않게 되었지만, 가끔씩 기능 구현의 힌트를 얻고 싶을 때에는 이렇게 쉽게 짜서 출력한 코드를 보고 참고하면 꽤 도움이 될 때도 있었습니다.


자, 이렇게 해서 가장 기본적인 쉐이더를 shaderFX를 이용하여 작성해 보았습니다.
어찌 보면 이번 시간도 기초 설명이었네요. 언제 본격적인 내용이 연재될까...

다음 시간에는 이런 것이 연재됩니다.

- 텍스쳐를 넣어봅니다!!! 뭐, 사실 위의 것을 할 수 있으시다면 벌써 할 수 있으십니다!!! 매우 쉽지요.
- 그런데, 여기서 드디어 이론이 들어갑니다! float3의 비밀이 풀리는 시간입니다. 프로그래머는 알고 있지만 그래픽 디자이너들은 잘 모르던 방식의 색이론에 대해서 배우게 됩니다!!!
  1. 실제로 최신 버전 3D max의 메터리얼 에디터는 shaderFX와 흡사한 모양의 에디터 창을 가지고 있습니다. [본문으로]
  2. 미리 말씀드렸다시피, 3Dmax의 기초 정도는 아셔야 가능한 강의입니다. 기초 책을 봐도 2-3일 정도만 공부하시면 알 수 있는 레벨이지요. 별로 안어렵습니다. 공부하세요. [본문으로]
  3. 물론 프로그래머들은 같은 일을 각종 코드들을 쳐 넣어서 하지만요 [본문으로]
  4. 프로그래머들은 '워크 스페이스를 저장한다' 라고 말하면 알아듣습니다. [본문으로]
  5. 실제 저 빨간 주전자 구현을 코드로 짜면, 몇 줄밖에 되지 않습니다. [본문으로]
반응형
,
Posted by 알 수 없는 사용자
오늘 포스팅할 주제는 '툴' 입니다!
이전에 가벼운 해킹에 대한 주제를 골랐었는데..
웬지 이번에도 해킹에 대해 쓰게 되면 계속 그 주제만 만지게 될 것 같아서..
이번엔 툴! 툴에 대해서 이야기를 해보려고 합니다.

게임을 만들때에는 툴도 항상 만들게 됩니다.
특히 요즘에는 툴의 중요성이 더욱 늘어난 것 같습니다.
물론 좋은 엔진을 구입하면 좋은 툴이 포함되어 나오기 때문에 만들지 않는 경우도 있습니다..
하지만!! 저희팀은 좋은 엔진을 구입하지 않았기 때문에.. 툴을 만듭니다!


그런데 툴을 만들때에 툴만 전문으로 만드는 프로그래머가 있는 팀은 거의 없을겁니다.
그렇다보니 툴에 대해 전문적이지 않은 게임프로그래머들이 툴을 만들게 되고 그러다보면 가끔씩은 툴툴대며 만들기도 합니다.. 

또 툴을 만드는것이 익숙하지 않다보니 실수아닌 실수가 많이 생기게 됩니다.그 중에 가장 큰 실수는 아무래도 툴을 위한 코드가 생각보다 커지는것이 아닐까 생각됩니다. 이것을 줄일 수 있다면 이것 자체만으로도 오버엔지니어링을 최소화하는 방법이라고 볼 수 있을것입니다.

툴을 만들때 중요한것은 레이아웃이라고 생각합니다. 레이아웃을 쉽고 빠르게 만들 수 있어야 좋은 툴을 만들 수 있습니다. 그렇지 않고 정말 기능만을 가진 벽돌같은 툴을 만든다면 작업자들이 사용하는데 굉장히 불편해할 것입니다. 속도가 빠른것보다는 편의성이 좋아야 작업자가 작업을 더 빠르게 할 수 있을테니까요.. 

그런데 가끔 팀 내부에서 쓰는 툴이라고 해서 레이아웃에 신경쓰지 않고 만드는 경우를 종종 보게 되었습니다. 그렇게 되면 툴을 사용하는 작업자들이 불편하게 계속 쓰게 되고 그런것들이 쌓여서 전체적인 개발 시간에도 영향을 주게 되기도 합니다. (그래서 좋은 엔진을 사는게 좋은것 같습니다. 좋은 엔진에는 좋은 툴이 있으니까요.)

또는 레이아웃에 신경을 쓰지만 그것을 위해 툴을 위한 코드가 과하게 많아지는 경우도 존재합니다. 이것은 툴의 개발 및 유지를 힘들게 하고 또한 마찬가지로 개발 시간에 영향을 주게 될 것입니다.

그래서 저는 C# WPF로 툴을 만드는것을 추천하려고 합니다.

C#으로 툴을 만드는것에 거부감을 가지시는 분들이 생각보다 많은것 같습니다. 거부감을 가지고 계신 분들은 대부분 C#으로 툴을 만들게 되면 엔진은 보통 C++이기 때문에 C++과 C#의 통신을 위해 CLR을 작업했던 분들입니다. C++에 있는 기능을 C#에서 쓰기 위해 CLR로 C++ 클래스들을 모두 랩핑을 하고.. 또 C++이지만 C++이 아닌 이상한 환경에서의 비정상적인것처럼 보이는 동작을 CLR 작업을 하면서 겪으셨기 때문일것입니다. 그런데 MFC로 작업을 하게 되면 어차피 C++이니 이러한 작업이 필요없어지고 또 MFC도 많은 발전을 해와서 레이아웃을 작업하는데 굉장히 편해졌기 때문에 MFC를 더 선호하시는 분들이 많습니다.

일단 이부분에 대해서 말씀드리자면 저도 역시 CLR로 C++과 C# 통신을 만드는것은 추천하지도 않고 저희팀에서는 CLR 작업은 전혀 하지 않습니다. CLR이 쉽게 익숙해지지 않아서 작업하는데 많은 불편함이 있고 클래스들을 랩핑하는것 또한 위에서 말한 오버엔지니어링에 속한다고 생각하기 때문입니다.

그래서 저희팀에서 사용하는 방법은 Dll Interop을 사용하여 C++과 C#의 통신을 합니다.
 (출처 - 신휘재님의 '엔진과 툴의 그렇고 그런 사이')

이에 대한 자세한 내용은 신휘재님의  '엔진과 툴의 그렇고 그런 사이'을 참고해주세요.. 어쨋든 그래서 저희팀은 C#을 사용합니다.

그리고 WPF를 사용합니다.

WPF를 사용하게 된 이유는 상당히 단순하지만... WPF의 장점은 WPF를 사용하는것을 후회하지 않게 만들어줄 것입니다. WPF는 툴의 레이아웃을 굉장히 쉽고 빠르게 그리고 강력하게 만들 수 있습니다. 

툴을 만들때는 먼저 레이아웃을 잡아보고 기능을 사용하는데 문제가 없는지 불편하지는 않은지의 확인이 빠르게 되어야 합니다.

애니메이션 프레임에 이벤트를 설정하는 기능을 툴에 넣어야 한다고 예를 들어봅시다. 필요한 기능은 먼저 애니메이션을 볼 수 있어야 하고, 재생할 수 있어야 하고, 특정 프레임에 이벤트를 설정하고 어느 위치에 설정되었는지를 볼 수 있고 이벤트의 특성에 따라 작업자가 다르기 때문에 구분하기 쉽게 만들고.. 등등

이러한 용도로 제가 만든 레이아웃은 아래와 같습니다.

필요한 기능들은 모두 들어있고 사용하기 편한건 모르겠지만 뭐 나쁘지는 않은것 같습니다. 이 레이아웃을 만들고 테스트를 해보는데 걸린 시간은 얼마나 들었을까요?


WPF에서는 위의 코드가 전부입니다. xaml을 이용하여 레이아웃을 만들기 때문에 작업하기도 상당히 쉽습니다. 해당 레이아웃을 테스트해보기까지 걸린 시간은 웹서핑을 하면서 딴생각을 하면서 작업을 해도 몇십분 정도면 충분히 테스트가 가능할 정도입니다.

물론 테스트를 어느정도 해본 이후에 이 코드를 기준으로 커스텀 컨트롤을 만들거나 정리를 하는 시간이 필요하긴 합니다만, 그렇다 하더라도 레이아웃 자체에 들어가는 시간은 많지 않습니다. 커스텀 컨트롤을 만들어야 할 때에도 컨트롤 안에 컨트롤을 넣는것이 자유롭기 때문에(어찌보면 당연한것이지만 MFC나 윈폼에서는 쉽지 않죠..) 컨트롤을 만드는것에 부담이 거의 없습니다.

여기에 조금만 더 신경을 쓰면 프로그래머 센스이어서 이쁘지는 않지만 보기에 나쁘지는 않게 만드는것도 어렵지 않습니다.

레이아웃을 만드는데에 부담이 거의 없기 때문에 툴을 만들때에는 실제 기능과의 연결에만 신경써주면 좋은 툴을 보다 쉽게 만들 수 있을 것입니다.

게다가 WPF는 접근하기도 상당히 쉬운 언어입니다. 사실 WPF는 XAML로 레이아웃을 만든다는것 외에도 굉장히 많은 기능들을 제공합니다. 간단하게 나열해보면..
  • Dependency Property 
  • Attached Property 
  • Routed Event 
  • Attached Event 
  • Trigger 
  • Style 
  • Animation 
  • Rendering
  • 등등등
많은 기능들을 제공하고 있고 실제로 이런것들을 모두 알면 더 쉽고 더 이쁘게 WPF로 작업을 할 수 있습니다. 심지어 WPF에서는 컨트롤들 렌더링이 DX기반으로 돌아가기 때문에 픽셀쉐이더도 사용할 수 있으니까요..

하지만!! 이런것들을 모두 몰라도! XAML과 C#의 기본적인 문법만 알고 있어도 툴을 만들기에는 충분합니다! C#을 공부해야 하는것은 어쩔 수 없지만 XAML은 XML을 써본적이 있다면 따로 공부할 필요도 없습니다! XAML이 XML을 그대로 가져다 사용한 것이니까요. 다른 기능들은 차근차근 천천히 알아가면 되는 것입니다..

또 툴을 만들때에 C#으로 만들다보면 C++에 이미 구현되어있는 기능들도 C#에서 다시 한번 구현하는 경우도 있습니다.. 물론 이런 경우는 많이 없겠지만 어쨌든 이것 역시 하지 않아도 될 일을 하는 것이기 때문에 오버 엔지니어링이 된다고 생각합니다. 예를 들면 툴에서 카메라를 사용하기 위해서 카메라 제어를 C#에서 하기 위해 카메라 관련된 모든 코드를 C#에 따로 만든다던지..

또는 조금 극단적인 얘기지만 C#에서 게임내 시스템을 제어하기 위해 과하게 많은 코드를 만드는 경우가 생길수도 있습니다.

이런 코드는 유지해나가는데에도 상당히 힘듭니다. 있는건 있는걸 그대로 쓰고 C#에서는 정말 레이아웃과 기능의 연결 부분 위주로만 만드는것이 여러가지로 좋을 것입니다.


툴을 만드는 프로그래머나 툴을 사용하는 작업자분들이나 모두 스트레스 받지 않고 일할 수 있도록 합시다! 화이팅!



ps. 모두 읽기 귀찮으신 분들은 굵은 글씨와 이미지파일만 보세요...
 
반응형
,