달의 목소리 -
   달의 목소리  
Front Page
Notice | Tag | Location | Guestbook | Admin | Write Article   
 
MySQL 하위 버전 호환 문제.
MySQL 4.1이상 부터는 계정의 비밀 번호를 해쉬 알고리즘을 기반으로 한 인증
프로토콜을 사용합니다. 즉, MySQL 4.1 미만과 이상의 버전 차이로 Client가 호환
되지 않기 때문에 모두 4.1이상으로 업그레이드를 해야 합니다.

즉 낮은 버전에서 높은 버전의 MySQL에 접속을 시도 하게 되면,

Client does not support authentication protocol requested
by server; consider upgrading MySQL client



이런 메세지를 내뱉으며 업그레이드를 하라고 종용합니다.

하지만, 업그레이드를 하지 못하는 상황이 발생할 수도 있는데,
그럴때는 사용하려는 계정을 하위 버전과 통신이 가능한 비밀 번호 체제로 바꾸면 됩니다.

바꾸는 방법은, old_password를 사용하면 됩니다.

mysql> set password for '계정' = old_password( '패스워드' );


위와 같이 콘솔에서 작업해 주면, 버전이 낮더라도 접속이 가능 해 집니다.


MySQL에서 테이블 단위로 유저 권한 설정하기.

MYSQL에서 기본적으로 유저 단위, DB단위, 테이블 단위, 컬럼 단위로 유저의
권한을 설정할 수가 있습니다.

MySQL을 설치하면, 기본적으로 다음과 같은 테이블이 생성됩니다.

columns_priv
db
func
host
tables_priv
user

이것 외에도 생성이 되는데, 유저의 권한을 설정하기 위해서 필요한 테이블은 위와
같습니다.

이중에서 db단위로 권한을 설정하는 것은 db테이블이며, 테이블의 경우에는
tables_priv, 컬럼 단위로는 columns_priv에서 추가해주면 됩니다.

이때 우선 순위는,

user > db > tables_priv > columns_priv
이며, 만약 user에서 권한이 모두 주어진다면, 자동적으로 밑에 것들도 적용이 되는 식입니다.
즉, 특정 테이블에만 권한을 주고 싶다면, 반드시 user와 db의 권한은 N으로 되어 있어야 합니다.

특정 테이블에 권한을 주기 위한 쿼리는 다음과 같습니다.

insert into tables_priv ( host, db, user, table_name, grantor, table_priv ) values ( '%', 'DB이름', 'USER 아이디', '테이블 이름', 'root@localhost', 'Select,Insert,Update,Delete' )

Tag : MYSQL5


Depth Buffer Shadow

실시간 그림자를 크게 나누어 보자면,

1. Static Shadow
2. Projection Shadow
3. Shadow Volume ( stencil shadow )


세가지로 나누어 볼 수 있습니다.
이중에서도 Projection Shadow가 가장 MMO에서 많이 사용되고 있는 기법중에 하나로,
현재까지도 애용되고 있는 그림자 기법 중에 하나입니다.

Static Shadow는 흔히 원형 그림자라고도 부르며, 실시간으로 그림자의 모양이 바뀌지
않는 그림자를 뜻합니다. 하지만, 실시간처럼 보이도록 하는 기법도 존재 합니다.
원형 그림자를 부위별로 나누어서 마치 투영한 듯하게 하여 그리는 방법으로
캐릭터마다의 고유한 특색을 살리지 못하지만, 마치 실시간 그림자처럼 보이게
하는 기법입니다. 만들기도 쉬울 뿐만 아니라 속도도 매우 빠르기 때문에 상당히
유용한 방법입니다.

Projection Shadow의 경우 Projective Shadow라고도 하며, Light View 시점에서
물체를 렌더링하여 투영하는 기법입니다.  Depth Buffer Shadow는 바로 이런
Projection Shadow의 기법이 발전된 기법으로 Self Shadow가 가능하다는 장점이 있습니다.
일반적인 Projection Shadow에서 가지고 있는 단점들이 보완되었다고 보면 되겠습니다.
( Back Projection 문제도 해결이 됩니다. )

Depth Buffer Shadow는 광원 방향에서 본 깊이 값을 렌더링하여 그림자를 그린 후에
깊이 값을 토대로 그림자를 그릴 영역을 정하는 기법입니다.
총 2회의 Pass로 렌더링이 되는데 저의 경우에는 그림자맵을 한번 부드럽게 가우시안
블러를 먹이기 때문에 총 2번의 객체 렌더링과 3회의 Image Pass로 렌더링했습니다.

먼저, 첫번째 패스에서는 깊이 맵을 생성합니다.( 깊이버퍼가 아닙니다. )
깊이 맵은 일반 적인 Projection Shadow와 마찬가지로 Light View에서 물체를
렌더링 합니다. 이때, 텍스처에 깊이 값을 써야 하는데 World - View - Projection 행렬과
정점이 곱해진 값중에서 z와 w값을 토대로 텍스처에 기입합니다.


VS_OUTPUT_SHADOW1 RenderShadow1_VS( float4 vP : POSITION )
{
VS_OUTPUT_SHADOW1 o = (VS_OUTPUT_SHADOW1)0;

o.vP = mul( vP, g_mWorldViewProjection );
o.vDepth = o.vP;
return o;
}

float4 RenderShadow1_PS( VS_OUTPUT_SHADOW1 i ) : COLOR
{
float fDepth = i.vDepth.z / i.vDepth.w; // 깊이 값을 씁니다.
float fBios = fDepth * 0.005f; // 미리 바이어스를 구합니다.
return fDepth + fBios;
}
 


HLSL코드로 보면 위와 같습니다.
Pixel Shader에서 z값을 w로 나누는 이유는 '타카시 이마기레'의 다이렉트 X 쉐이더
프로그래밍 책에 잘 나와 있습니다.

사용자 삽입 이미지
저의 경우 깊이 맵을 저장할 텍스처를 부동 소수점 텍스처로 생성하였습니다.
희미하지만 가운데에서 왼쪽에 캐릭터가 존재합니다.

두번째 패스에서는 실질적으로 렌더링을 하게 되는데, 깊이 맵을 Diffuse맵과 다른
Stage에 설정하고 이것을 토대로 깊이 값을 비교하여 그림자를 추출하게 됩니다.

이때, 실제 보일 카메라로 렌더링을 하되 비교할 깊이 값을 구할 World-View-Projection행렬은 광원에서 보는 행렬을 사용해야 합니다.


o.vP = mul( vP, g_mWorldViewProjection );
o.vDepth = mul( vP, LVP );
o.vShadow = mul( vP, mul( LVP, g_mSBIAS ) );
 


SBIAS는, Scale BIAS 행렬입니다. 흔히 Projection Shadow에서 사용하는 것을
사용하시면 됩니다.

LVP의 경우에는 맨처음 깊이 값을 구했을때 사용한 g_mWorldViewProjection을
다시 설정하여 사용하면 됩니다. 개인적으로 이부분이 처리하기가 까다로웠는데
기본적으로 카메라를 설정한 상태에서 엔진단에서 카메라에 맞는
g_mWorldViewProjection를 설정해주기 때문에, Light상에서의 View와 Projection을
미리 곱한 것을 쉐이더 상수로 넘기고 Object의 World행렬을 Shader단에서 곱하게
하여 처리하였습니다. 덕분에 속도의 감소를 감안해야 했습니다.

픽셀 쉐이더에서는 깊이 값을 비교하여 그림자를 그리도록 합니다.


float shadow = tex2Dproj( tex3, vShadow ).r;
float Color = ( fDepth < shadow ) ? 1 : 0;
 


사실, 여기까지는 그렇게 어려운 부분은 아닙니다. 단지, 쉐이더가 많이 사용되게 되고
기존의 렌더링 방법과 다르기 때문에 Skinning을 CPU가 아닌 Shader로 처리할 경우에는
이것에 대해서 따로 코드를 작성해 줘야 하는 부담감이 존재합니다.

이렇게 까지 해서 렌더링을 하면 다음과 같이 그림자가 져야할 부분이 칠해지게 됩니다.

사용자 삽입 이미지

기본 텍스처로 하는 경우에는 aliasing이 상당히 심합니다.
그래서 저는 이것에 가우시안 블러를 먹여서 조금은 부드럽게 하였습니다.
스크린샷에서 볼 수 있듯이 허벅지에 칼의 그림자가 드리워진 것을 볼 수
있습니다. 또한, 가슴 밑부분이나 팔에도 자신의 그림자가 드리워졌습니다.

부드럽게 먹이는 과정까지는 Soft Edge Shadow 자료를 참고하였습니다.

사용자 삽입 이미지


마지막으로 최종 결과 물입니다.
Post Image Processing처럼 2패스만에 바로 오브젝트에 그리는 것이 아니라,
텍스처에 최종 결과 물을 렌더링하고 이것을 부드럽게 이미지 처리 후에
일명 "뽀샤시 효과"처럼 전체 Scene을 그린 후에 덧 씌웠습니다.

기본적으로 적용하는 것 까지는 오래 걸리지 않았지만 원하는 퀄리티를 내기까지가
상당히 힘들었습니다. Self Shadow를 위해서라면 차라리 Stencil Shadow를 쓰는것이
더 나은 퀄리티가 될 것 같습니다.

현재 작업 중인 캐쥬얼 축구 게임인 '풋아이'에도 적용 시켜 보려고 했으나
역시 큰 퀄리티 차이를 느끼지 못하여 결국 도입은 하지 않을 것 같습니다.

P.S.) 이왕이면 링크 주소만이 아니라 트랙백까지 걸어 주시면 감사하겠습니다. :)


RIM LIGHT

요즘 라이팅 기법중에 "대세"라고 까지는 모르겠습니다만, 구현도 쉽고
느낌도 괜찮기 때문에 많이 쓰이는 것 같습니다. ( 요즘 캐쥬얼 게임을 만들다 보니
카툰쪽 라이팅 말고는 구현해본게 오랜만인거 같네요. )

그래서 저도 한번 구현해 보았습니다. 

RIM LIGHT란, ( LIM LIGHT라고 하는 곳도 있음 ) 후광효과를 내기 위한
LIGHT ( 혹은 조명 )이라고 보시면 될 것 같습니다.
사실 이런건 직접 눈으로 보는 것이 이해가 빠른데, 간단하게 말로 표현하자면,
예수나 석가모니 뒤에 흔히 하는 후광처럼 과장된 것이 아니라 은은하게 물체를
더욱 두드러져 보이게 하는 빛입니다.

이렇게 후광을 구현하기 위해선, 단 두가지만 알아 두면 됩니다.

1. 피사체 ( 물체 )의 외곽-실루엣. ( fresnel항 )
2. 빛을 등지고 있는지의 여부 ( backlight )

후광의 경우에는 물체의 외곽이 되는 라인에 빛이 먹기 때문에 구해야 하며,
역광을 표현하기 위해서는, 빛을 정면으로 받고 있을땐 RIM LIGHT이 먹지 않게 해야 합니다.

1. 피사체 ( 물체 )의 외곽-실루엣. ( fresnel항 )

사용자 삽입 이미지

위의 스크린샷을 보다시피, 외곽라인에 많은 빛이 먹는 것을 볼 수가 있습니다.
현재 모델은 normal map을 이용한 per pixel lighting으로 처리되는 normal값을
사용하였습니다.

fresnel을 구하기 위해서는, 정점(vertex)의 방향벡터(normal)값과 카메라와
정점(vertex)의 방향값의 내적을 구하면 됩니다.

간단하게 HLSL코드로 설명하자면,


// 카메라에서 정점을 바라보는 방향
vEye = normalize( vPS.xyz - g_vCameraPos.xyz );

// 프레넬항을 구합니다. ( 카메라에서
fresnel = 1.0 - abs( dot( vNormal, vEye ) );
 


이렇게 됩니다. 위의 스크린샷은 이것을 그대로 색으로 치환한 경우입니다.

2. 빛을 등지고 있는지의 여부 ( backlight )

역광이기 때문에, 빛을 정면으로 받고 있을 때가 아닌 빛을 등지고 있을때만 먹도록 해야
합니다.

사용자 삽입 이미지


이처럼, 모델과 라이트의 위치는 그대로 유지한채, 카메라만 캐릭터의 뒤로 향하게 하면,
후광이 먹는 모습을 볼 수가 있습니다. 빛의 정면에선 후광이 먹지 않는 것이지요.

이렇게 빛을 정면으로 먹는 정도는, "빛-정점 방향"과 "카메라-정점 방향"의 내적을 통해서
구할 수가 있습니다.

이때, 내적의 경우 두 방향 벡터의 각도가 90도가 되면 0이 되며, 일직선이 되면,
1이 되거나 -1이 됩니다.



// 카메라 시점에서 정점을 바라본 방향
vEye = normalize( vPS.xyz - g_vCameraPos.xyz );

// 빛의 시점에서 정점을 바라본 방향
vLight = normalize( vPS.xyz - g_vLightPos.xyz );

// 백라이트 값
float backlight = dot( vLight, vEye ) );
 


HLSL코드로 보았을때 위에처럼 backlight를 구하게 되면, backlight값은
-1~1의 사이로 결과가 나오며, 카메라가 빛을 정면으로 보고 있는 시점
( 즉, 물체 입장에선 후광이 비쳐지는 시점 )에서 -1이 나오므로 이것을
역으로 해줄 필요가 있습니다.


// 백라이트 값
float backlight = -dot( vLight, vEye ) );
 


또한, 우리가 표현하고자 하는 것은, 후광인 경우이며 backlight값이 0이 될때,
vLight와 vEye의 사이각이 90도 정도가 됩니다. 즉, 0 이하의 값은 버리면 간단히
구현이 완료 됩니다.


// 백라이트 값
float backlight = max( 0, -dot( vLight, vEye ) );
 



이렇게 간단한 공식만으로도 구현이 가능합니다.

사용자 삽입 이미지


위의 마지막 스크린샷에서 첫번째 캐릭터는 빛을 정면으로 받고 있는 모습이며,
두번째와 세번째 사진은 각각 빛을 등지고 있는 상황입니다.
두번째는 RIM LIGHT가 적용되지 않았으며, 세번째는 RIM LIGHT가 적용된 상태입니다.


*넥슨 컨퍼런스( NDC )의 동영상 자료를 참고하였습니다.


애니메이션 보간과 블렌딩
캐릭터 애니메이션의 경우, "skinning"과, 애니메이션 계산으로 크게 나눌 수가
있는데, 이때 "skinning"은 실제적인 "연산"이라고 한다면 애니메이션 계산은
좀더 자연스러운 애니메이션을 보여주도록 처리하는 것이라고 볼 수 있겠습니다.

특히, 여러 동작들이 나오는 캐릭터의 애니메이션의 경우 동작간의 부드러운
연결이 필수적으로 엔진 단에서 지원을 해주어야 하는데 보통 동작간의 부드러운
연결에는, "보간(interpolate)"과 "블렌딩(blending or mixing)"으로 많이 사용합니다.

두 가지 모두 다 장단점이 있습니다. 또한, 여유가 된다면, 보간과 블렌딩을 동시에
사용하기도 합니다. 보통 이런 경우에는 동작간의 연결에는, "블렌딩"을 사용하며
키 프레임간의 부드러운 연결에는 "보간"을 사용합니다.

보간과 블렌딩의 가장 큰 차이는, 보간은 말그대로 중간 값을 부드럽게 생성하는
방법이며, 블렌딩은 두개 이상의 동작을 섞어 쓰는 것을 말합니다.
예를들어, 정지 동작에서 뛰는 동작으로 바뀔때, 정지동작에서의 현재 프레임과
뛰는 동작의 첫번째 프레임을 부드럽게 특정 시간 동안 연결해 주는 것을 "보간"이라
하고 정지 동작이 플레이 되는 과정에서 천천히 뛰는 동작의 가중치를 특정 시간동안
높이게 하여 부드럽게 연결하는 것을 "블렌딩"이라고 합니다.


위는 동작과 동작 사이를 "보간"만으로 연결 하는 경우입니다.

위는 동작과 동작 사이를 "블렌딩"으로 연결하는 경우입니다.
"보간"과는 다르게 "블렌딩"의 경우 블렌딩 되는 시간을 조금 넉넉하게 해주어야
위와 같이 자연스럽게 연결됩니다. 짧을 수록 "보간"과 거의 비슷한 결과물을 보여
주게 됩니다.

위의 동영상은 각각 보간에서는 "0.4초"간 연산을 하며, 블렌딩의 경우에는, "1.4초"동안
블렌딩을 합니다.

보시다시피, 자연스럽게 애니메이션을 연결하는 방법은 "블렌딩"이 더 자연스럽습니다.

보간의 경우 키프레임을 중간 보간하면 되기 때문에 상대적으로 구현하기가 쉽습니다.
블렌딩은 보간과 비슷하긴 하지만, 두개의 동작을 동시에 플레이 해야 하기 때문에
보간보다는 조금 복잡합니다.


// 첫번째 프레임과 보간합니다.
const Vector3* vPos = pKey->GetPos( 0 );
const Quaternion* qRot = pKey->GetRot( 0 );

// 키 값을 구합니다.
m_apBone[i].vPos.Lerp( m_apPrevBone[i].vPos, *vPos, fOffset );
m_apBone[i].qRot.Slerp( m_apPrevBone[i].qRot, *qRot, fOffset );
 


보간은 위와 같이 새 동작의 첫프레임과 동작을 바꾸는 시점에서의
그전 동작의 프레임 사이를 보간하게 됩니다.


const IAniKey* pPrevKey = m_pPrevAni->GetBoneKey( i );
const IAniKey* pNextKey = m_pAni->GetBoneKey( i );

if( NULL == pPrevKey ) continue;
if( NULL == pNextKey ) continue;

// 첫번째 프레임과 보간합니다.
const Vector3* vPrevPos = pPrevKey->GetPos( m_nPrevFrame );
const Vector3* vNextPos = pNextKey->GetPos( m_nFrame );

const Quaternion* qPrevRot = pPrevKey->GetRot( m_nPrevFrame );
const Quaternion* qNextRot = pNextKey->GetRot( m_nFrame );

// 키 값을 구합니다.
m_apBone[i].vPos.Lerp( *vPrevPos, *vNextPos, fOffset );
m_apBone[i].qRot.Slerp( *qPrevRot, *qNextRot, fOffset );
 


블렌딩의 경우에는 위와 같이 두개의 동작에 대한 프레임이 동시에 진행되며,
가중치가 서서히 새로운 동작으로 시간이 흐를수록 1에 가까워 지기 때문에
자연스럽게 그전 동작에서 새로운 동작으로 이어지게 됩니다.





BLOG main image
MSN: kjmgo@narew.net Mail: kjmgo@narew.net
 Notice
미약하나마 하나의 촛불을 켜..
Profile
 Link Site
SK8SNOW
꿈 속으로...
달의 목소리( 예전 블로그 )
달의 목소리( 잡담용 블러..
성 범죄 스크랩
 Category
전체 (45)
Diary (6)
3D Programming (25)
Physics (0)
Math (0)
Network (4)
Wine (7)
Narew3D (1)
 TAGS
MYSQL5 축구게임 축구 축구 게임 풋아이 FOOTAI
 Calendar
«   2008/08   »
          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            
 Recent Entries
MySQL 하위 버전 호환 문제. (2)
MySQL에서 테이블 단위로.. (5)
Depth Buffer Shadow (2)
RIM LIGHT (3)
애니메이션 보간과 블렌딩
 Recent Comments
개발중이던 게임의 DBMS의..
kjmgo - 10:06
Mysql의 버전업을 했다가..
딱쮜 - 06:56
조언 감사드립니다. ^^
kjmgo - 08/21
더 궁금하신 점이 있으면 c..
창 - 08/21
1. 실수를 줄일 수 있습니..
창 - 08/21
 Recent Trackbacks
풋아이.
달의 목소리
 Archive
2008/08
2008/07
2008/06
2008/05
2008/04
 Visitor Statistics
Total : 27675
Today : 47
Yesterday : 89
태터툴즈 배너
Eolin
rss