3D Game Programming 11 - ASE Animation afewhee@gmail.com
0. 목차 ASE 구조 Geometry Parsing Geometry Texture Parsing Material Texture Rigid Body Animation Scene Animation X-File Animation
1. ASE 구조 ASE Parsing Parsing: 의미분석 ASCII 로구성된텍스트내용에렌더링에관련된의미를부여 ASE Exporting 3D Max 에서작업한 Object 를 ASE 형식으로저장 내용 Material Geometry Animation Program ASE 파일에대한뷰어 (Viewer) 작업 이후캐릭터툴 (Character Tool) 로발전
1. ASE 구조 Scene File Name이름, Start, End Frame 등 Frame에관련된내용 Frame Speed: Frame/Second Tick: Max에서사용하는시간 millisecond 보다정밀 Frame Speed= 30, Tick/Frame = 160 이면 1초 = 4800 Tick Material List Total 재질 수 텍스처매핑에대한정보 Geometry는이를인덱스로참고 ("MATERIAL_REF") 텍스처파일이름이한글이면파일이름이제대로출력이안됨 Geometry See Geometry Shape: Line 으로구성된객체 Camera: 카메라움직임에대한연출을기록 Light Object 조명에대한연출 : Omni (Point Light), Directional, Target (Spot Light) 게임에서는주로 Scene, Material List, Geometry 를사용 이외의내용은따로작업
1. ASE 구조 Geometry Node 로구성 Node Name Parent Node World 행렬 Node 의크기 (S), 회전 (R), 이동 (T) Object를구성하는 Mesh vertex List Face (Index) List Normal vector List Texture Coordinate List Texture Face List Animation Material Index Node Max의 Object는계층적자료구조인 Tree 자료구조들이결합된 Forest 형식으로구성 부모노드이름 자신의노드이름 Node Root Node Node Node Node Node Node Forest Node Root Node Node Node 링크의순서대로파일에노드가기록되어있음 자료구조를 Tree 로만들필요가없음 Node Node Node Node Node Node
2. ASE Parsing 해석방법 자료구조 ASE의구조와동일하게구성 List 구조의자료블록 '*' + ' 자료구조이름 ' + '{' 자료의끝 : '}' 필요에따라 while 문을사용해서자료를해석개별자료 '*' + ' 자료구조이름 ' + '{' Ex) ASE 자료구조설정 ASE File *GEOMOBJECT { *NODE_NAME "Pyramid01" *MESH { *MESH_NUMVERTEX 5 *MESH_NUMFACES 7 *MESH_VERTEX_LIST { *MESH_VERTEX 0 19.6581-24.7863 66.1019 *MESH_VERTEX 1-28.2051-68.6610 0.0050 *MESH_VERTEX 2 67.5214-68.6610 0.0050 *MESH_VERTEX 3 67.5214 19.0883 0.0050 *MESH_VERTEX 4-28.2051 19.0883 0.0050 } *MESH_FACE_LIST { *MESH_FACE 0: A: 0 B: 1 C: 2 AB: *MESH_FACE 1: A: 0 B: 2 C: 3 AB: *MESH_FACE 2: A: 0 B: 3 C: 4 AB: *MESH_FACE 3: A: 0 B: 4 C: 1 AB: *MESH_FACE 4: A: 1 B: 5 C: 2 AB: *MESH_FACE 5: A: 2 B: 5 C: 3 AB: } } } //ASE Structure struct AseVtx { FLOAT x, y, z; AseVtx() : x(0), y(0), z(0){} }; struct AseFce { WORD a, b, c; AseFce() : a(0), b(0), c(0){} }; struct AseGeo { char snodename[64]; int inumvtx; int inumfce; AseVtx* plstvtx; AseFce* plstfce; };
2. ASE Parsing 뷰어 (Viewer) 기본요소 인풋, 카메라클래스오브젝트의크기를눈으로판별할수있는 Grid 필요 위치에대한자료해석 3D Max 는오른손좌표계사용 Direct3D 왼손좌표계로전환 (y, z 교환 ) ẑ 3D MAX ŷ xˆ //ASE File *MESH_VERTEX 0 19.6581-24.7863 66.1019 ŷ Direct3D ẑ //Program Float3 (19.6581, 66.1019, -24.7863) xˆ INT nidx=0; FLOAT x=0.f, y=0.f, z=0.f; sscanf(sline, "%*s %d %f %f %f", &nidx, &x, &y, &z); m_pgeo[ngeoidx].plstvtx[nidx].x = x; m_pgeo[ngeoidx].plstvtx[nidx].y = z; m_pgeo[ngeoidx].plstvtx[nidx].z = y;
2. ASE Parsing Face (Index) 에대한자료해석 3D Max 는오른손좌표계사용 Direct3D 왼손좌표계로전환 (c, b 교환 ) //ASE File *MESH_FACE 6: A: 3 B: 5 C: 4 //Program WORD3 (3, 4, 5) INT INT sscanf( nidx=0; a=0, b=0, c=0; sline, "%*s %d: %s %d %s %d %s %d", &nidx, stmp1, &a, stmp1, &b, stmp1, &c); m_pgeo[ngeoidx].plstfce[nidx].a = a; m_pgeo[ngeoidx].plstfce[nidx].b = c; m_pgeo[ngeoidx].plstfce[nidx].c = b;
2. ASE Parsing ASE Class 에대한추상화 Model Interface struct ILcMdl { virtual ~ILcMdl(){}; virtual INT Create(void* pdev, void* sfile)=0; virtual void Destroy()=0; virtual INT FrameMove()=0; virtual void Render()=0; }; ASE 객체생성 INT LcAse_Create(char* scmd, ILcMdl** pdata // Output data, void* pdev // Device, void* sname = NULL // Model File Name, void* poriginal= NULL // Original ILcMdl Pointer for Clone, void* p4=null // Not Use, void* p5=null // Not Use ); ASE 클래스 class CLcAse : public ILcMdl
2. ASE Parsing - Geometry While Loop 로개수파악 Geometry 숫자는기록이안되어있음 while(!feof(fp)) { if(0 == _strnicmp(sline, "*GEOMOBJECT {", strlen("*geomobject {") )) ++m_ngeo; } // 파일의끝이므로파일포인터를처음으로이동 fseek(fp, 0, SEEK_SET); 지오메트리생성 m_pgeo = new AseGeo[m_nGeo]; INT ngeoidx = -1; AseGeo* pgeo = NULL; 지오메트리해석 while(!feof(fp)) { if(0 == _strnicmp(sline, "*GEOMOBJECT {", strlen("*geomobject {") )) { ++ngeoidx; pgeo = &m_pgeo[ngeoidx]; pgeo->
2. ASE Parsing - Texture Material Geometry 에적용할텍스처파일이름기록 Ex) *BITMAP "d:\psd\mong.jpg" Geometry 끝에사용 Material 인덱스기록 Ex) *MATERIAL_REF 3 Texture 좌표에대한자료해석 3D Max 는 S.T. 좌표계사용 Direct3D U.V. 좌표계로전환 (x,1.f-y) //ASE File *MESH_TVERT 3 0.4811 0.6722 0.1822 //Program FLOAT U = 0.4811, V= 1.0-0.6722 INT nidx=0; FLOAT u=0.f, v=0.f, w=0.f; sscanf(sline, "%*s %d %f %f %f", &nidx, &u, &v, &w); pgeo->plsttvtx[nidx].u = u; pgeo->plsttvtx[nidx].v = 1.0f-v; pgeo->plsttvtx[nidx].w = w;
2. ASE Parsing - Texture Texture Face (T-Face) 3D Max 는하나의정점을여러텍스처좌표가공유해서사용 Texture 좌표수 >= 정점좌표수 정점의수를텍스처좌표수만큼늘림 텍스처 Face List 와정점 Face List 를비교해서새로만들어진정점의위치결정 Ex) T-Face Reading INT INT nidx=0; a=0, b=0, c=0; sscanf(sline, "%*s %d %d %d %d", &nidx, &a, &b, &c); pgeo->plsttfce[nidx].a = a; pgeo->plsttfce[nidx].b = c; pgeo->plsttfce[nidx].c = b;
2. ASE Parsing - Texture T-Face 를기준으로한정점결정 새로운정점의수를텍스처좌표수로정한다. 새로운정점에텍스처좌표를먼저설정한다. T-Face Index 와정점의 Face Index 를비교해서 Face Index 에해당하는위치를새로운정점의위치에복사한다. 최종 Index List 는 T-Face 로한다.
2. ASE Parsing - Texture Face List 와 T-Face List 의인덱스비교 //ASE FILE Face *MESH_FACE_LIST { *MESH_FACE 0: A: 0 B: 58 C: 1 *MESH_FACE 1: A: 59 B: 1 C: 58 *MESH_FACE 479: A: 169 B: 170 C: 120 *MESH_FACE 480: A: 245 B: 120 C: 170 *MESH_FACE 481: A: 170 B: 171 C: 245 *MESH_FACE 482: A: 246 B: 245 C: 171 *MESH_FACE 487: A: 122 B: 144 C: 18 } // ASE FILE- T-Face *MESH_TFACELIST { *MESH_TFACE 0 108 79 255 *MESH_TFACE 1 256 255 179 *MESH_TFACE 479 427 428 520 *MESH_TFACE 480 521 520 428 *MESH_TFACE 481 428 429 521 *MESH_TFACE 482 522 521 429 *MESH_TFACE 487 396 395 398 }
2. ASE Parsing - Texture 프로그램방법 // UV 먼저설정 for(int j=0; j< pgeo->ntvtx; ++j) { pvtxr[j].u = pgeo->ptvtx[j].u; pvtxr[j].v = pgeo->ptvtx[j].v; } // Vertex Setting for(int n=0; n<pgeo->nfce; ++n) { INT nt = 0; INT nv = 0; nt = pgeo->ptfce[n].a; // T-face U V 인덱스를가져온다. nv = pgeo->pfce[n].a; // Vertex 버퍼에서정점의위치를가져온다. pvtxr[nt].p = pgeo->pvtx[nv].p; nt = pgeo->ptfce[n].b; nv = pgeo->pfce[n].b; pvtxr[nt].p = pgeo->pvtx[nv].p; } nt = pgeo->ptfce[n].c; nv = pgeo->pfce[n].c; pvtxr[nt].p = pgeo->pvtx[nv].p;
3. Animation Animation 종류 Rigid Body ( 강체 ) 강체란외부에서물체에힘을가해도크기가변형되지않는물체로 Rigid Body Animation 은이러한점을이용해서 Geometry 를구성하는모든정점사이의거리비율이변환 (Transform) 후에도변하지않는 Animation 오브젝트에하나의행렬을적용해서변환을수행 3D 의변환은 Rigid Body 라할수있음 Skinning 오브젝트에변환을적용하면변환전과변환후정점사이의비율이달라짐 구현방법한정점에변환에관련된여러행렬을적용
3. Animation Animation 구현방법 Key Frame Animation 일정한시간간격으로정점의모든좌표를기록 계산량이적어속도에이점이있으나메모리는정점의수 * 프레임간격에비례 Ex) MD2, MD3 Model Bone Animation Key Frame 처럼모든정점의변환결과를기록하는것이아니라애니메이션을구성하는 Bone 의행렬을시간에따라기록 골격체 (Skeleton) 를이용한애니메이션으로하나의 Bone 행렬에는하나의 Geometry 가대응 Key Frame 보다메모리를적게차지 계층적으로 Bond 을구성 Bone 이많을수록 Animation 에대한연산량이증가 Bone 과 Bone 사이의연결부위에 Crack 이발생 대부분의게임에서가장많이사용되는애니메이션
3. Animation Animation 구현방법 Skinning Animation Bone Animation 의단점을극복하기위해하나의정점에여러행렬을적용한애니메이션으로행렬에각각그영향력에따라다른 Weight( 비중 ) 을주어전체변환행렬을만듦 행렬이하나면 Bone Animation 과동일 이음새부분에서 Crack 을없앨수있음 행렬계산을 CPU 를이용한 Software 계산을하지않고 DirectX 의고정파이프라인을이용한다면하나의정점에네개까지행렬을연결해서사용할수있음 정점쉐이더 (Vertex Shader) 를이용하면간단하게계산됨 그래픽카드의성능에많이의존 Direct3D 의 X-File 은 Skinning Animation 이가능 Ex) DirectX SDK Tiny 예제
3. Animation Scene 해석 SCENE_FILENAME : Export 한파일이름 SCENE_FIRSTFRAME : 시작프레임인덱스 SCENE_LASTFRAME : 마지막프레임인덱스 SCENE_FRAMESPEED : 초당프레임수 = Frame / Second 30 Frame 33.333ms SCENE_TICKSPERFRAME 프레임당 Tick: Max 는 millisecond 보다더정밀한 Tick 단위사용 Tick 은이후에 millisecond 로환원해서사용 160 위에서 30 Frame 이면 1 초를 160* 30 = 4800 Tick
3. Animation Node_TM 해석 Node Transform Matrix ( 변환행렬 ) Geometry(Node) 가가지는하나의월드행렬 행렬또는크기, 위치, 회전을따로해석 ẑ 3D MAX ŷ xˆ Node_TM 해석방법 Max 는오른손좌표계를사용 Direct3D 는왼손좌표계. 변환필요 TM_ROW1 부분과 TM_ROW2 부분교환 Y 와 Z 를교환 ŷ Direct3D ẑ xˆ *TM_ROW0-0.1225-0.3713 0.9204 *TM_ROW1-0.4945-0.7812-0.3810 *TM_ROW2 0.8605-0.5018-0.0879 *TM_ROW3-0.0443-0.2532 10.2929 D3DXMATRIX TM_NODE( -0.1225, 0.9204, -0.3713, 0, 0.8605, -0.0879, -0.50189, 0, -0.4945, -0.3810, -0.78120, 0, -0.0443, 10.2929, -0.25329, 1 );
3. Animation Node TM 3D Max가정한 Node의월드변환행렬 Node TM World = Node TM Local * Parent TM World Node TM Local Parser가구현해야하는지역좌표계에서의변환 ( 크기, 회전, 이동 ) 행렬 ASE의 Node TM은전부월드좌표계로구성되어있음 모델좌표계로전환필요 Node TM Local = Node TM World * (Parent TM World) -1 D3DXMATRIX mtprn = pgeoprn->tminf.mtw; D3DXMATRIX mtprni; // 부모월드행렬의역행렬 D3DXMatrixInverse(&mtPrnI, NULL, &mtprn); // TM Local = TM World * (Parent World) -1 pgeo->tminf.mtl = pgeo->tminf.mtw * mtprni;
3. Animation Animation 은크기, 회전, 이동을따로해석 Geometry 가이동만있는경우이동만기록. 회전, 크기도마찬가지 *TM_ANIMATION { *NODE_NAME: 해당애니메이션 Bone 이름 이동 "*CONTROL_POS_TRACK {": 이동에대한애니메이션 Track "*CONTROL_POS_SAMPLE": Geometry 의 X,Y,Z 위치변환값 크기 "*CONTROL_SCALE_TRACK {": 크기변환에대한애니메이션 Track *CONTROL_SCALE_SAMPLE: X, Y, Z 에대한크기변환값 회전 *CONTROL_ROT_TRACK {: 회전에대한애니메이션 Track *CONTROL_ROT_SAMPLE: 회전변환값 (Quaternion: x,y,z,w) 회전변환은절대적인기준을이용하면 Euler Angle 등을이용해야하는데필연적으로 Gimbal lock 문제발생 3D Max 는회전에대해서만큼이전값에대한상대적인값으로기록하며 Quaternion 을사용 사원수를누적해서사용
3. Animation TM_ANIMATION 위치, 크기변환해석 : y, z 를교환 TM_ANIMATION Quaternion 해석 Max 는사원수의축 (x,y,z), 그리고각도 (w: radian) 를기록하고있다. 회전에서 x, y, z, w 순으로값을읽는다. 마지막 w 값은각도 (radian) 이므로이것에서 sin, cos 을구한다. 최종사원수에다음과같이대입한다. 사원수.x = sin(w/2.f) * x; 사원수.y = sin(w/2.f) * y; 사원수.z = sin(w/2.f) * z; 사원수.w = cos(w/2.f); *CONTROL_ROT_SAMPLE 960 0.0749-0.0561-0.9956 0.0723 D3DXQUATERNION q1; INT ntrck; FLOAT x=0.f, y=0.f, z=0.f, w=0.f; sscanf(sline, "%*s %d %f %f %f %f", &ntrck, &x, &y, &z, &w); q1.x = sinf(w/2.0f) * x; q1.z = sinf(w/2.0f) * y; q1.y = sinf(w/2.0f) * z; q1.w = cosf(w/2.0f);
3. Animation TM_ANIMATION Quaternion 누적 절대적인기준을사용해야하므로사원수를누적 //STL 을이용하는경우에사원수누적 INT isize = pgeo->vrot.size(); if(0==isize) { AseTrack trck(ntrck, q1.x, q1.y, q1.z, q1.w); } else { } pgeo->vrot.push_back(trck); D3DXQUATERNION q2; D3DXQUATERNION q3; AseTrack* ptrck = &pgeo->vrot[isize-1]; q2.x = ptrck->x; q2.y = ptrck->y; q2.z = ptrck->z; q2.w = ptrck->w; D3DXQuaternionMultiply(&q3, &q2, &q1); AseTrack trck(ntrck, q3.x, q3.y, q3.z, q3.w); pgeo->vrot.push_back( trck );
3. Animation 보간 (Interpolation) 컴퓨터마다실행속도가다르므로프레임과프레임사이를보정해서장면을연출 선형보간 (Linear Interpolation) 가장간단한보간방법 선형보간방법 선형보간을위해서두프레임사이의 w 값을계산 Frame 0 time =0.5 w=0. Interpolation Frame time = 1.25 w = (1.25-0.5) /(1.5-0.5) =0.75 Frame 1 time =1.5 w=1.
3. Animation 이동에대한 Interpolation 구현 w = ( 현재프레임 현재프레임보다작거나같은프레임 ) / ( 현재프레임보다작거나같은프레임 - 다음프레임 ) Ex) FLOAT w = (nframe- pgeo->vtrs[nidx].nf)/ (pgeo->vtrs[nidx+1].nf- pgeo->vtrs[nidx].nf); D3DXVECTOR3 p, p1, p2; p1.x = pgeo->vtrs[nidx].x; p1.y = pgeo->vtrs[nidx].y; p1.z = pgeo->vtrs[nidx].z; p2.x = pgeo->vtrs[nidx+1].x; p2.y = pgeo->vtrs[nidx+1].y; p2.z = pgeo->vtrs[nidx+1].z; p = (1-w)*p1 + w* p2; //or p1 + w * (p2-p1);
3. Animation 회전에대한 Interpolation 구현 Ex) FLOAT w = (nframe - pgeo->vrot[nidx].nf)/ (pgeo->vrot[nidx+1].nf- pgeo->vrot[nidx].nf); D3DXQUATERNION q, q1, q2; q1.x = pgeo->vrot[nidx].x; q1.y = pgeo->vrot[nidx].y; q1.z = pgeo->vrot[nidx].z; q1.w = pgeo->vrot[nidx].w; q2.x = pgeo->vrot[nidx+1].x; q2.y = pgeo->vrot[nidx+1].y; q2.z = pgeo->vrot[nidx+1].z; q2.w = pgeo->vrot[nidx+1].w; // 사원수보간 q = (1-w) * q1 + w * q2; //or q = q1 + w * (q2-q1); // 함수사용 D3DXQuaternionSlerp(&q, &q1, &q2, w); // 사원수보간후이를회전행렬로설정 D3DXMatrixRotationQuaternion(&mtA, &q);
3. Animation Tree 형식으로구성된애니메이션구현 크기행렬, 회전행렬, 이동행렬을보간을통해서구한다. 지역행렬 (Local TM) 을구한다. Local TM = Scaling TM * Rotation TM * Translation TM 부모행렬을곱해서 Geometry 의월드행렬을만든다. World TM = Local TM * Parent World TM
3. Animation ASE Parser 의주요내용 위치를 x, z, y 로해석한다. 위치에대한삼각형인데스는 a, c, b 이다. 텍스처좌표는 u, 1.0f v 이다. 텍스처좌표인덱스는 a, c, b 이다. 위치는텍스처좌표보다같거나작다. 텍스처좌표인덱스와위치인덱스수는같다. 텍스처가있다면정점인덱스대신텍스처인덱스를사용한다. 정점을구성하려면 k 번째텍스처좌표인덱스에서텍스처버퍼번호를얻어 uv 좌표를얻고, k 번째정점위치인덱스에서위치버퍼번호를얻어정점위치를얻는다. TM은 2번째와 3번째행을교환한다음, 2번째열과 3번째열또한교환한다. ASE 는월드행렬로구성되어있어서자신의지역행렬 = ( 자신의월드행렬 ) * ( 부모의월드행렬의역행렬 ) 로구한다. 지역좌표계정점위치 = (ASE 에서구한위치 ) * ( 지오메트리월드행렬역행렬 ) 애니메이션의회전값은사원수로저장되어있으며누적값이아닌이전프레임과상대적인값이다. 사원수누적을한다. 회전은사원수보간을이용한다.