===================================================================================
게임 강좌 - 게임개발 문턱 넘기기
by 조무영
제 4 편 : Max Exporter #4 - 3D Model Exporter
Max Script 로 Exporter 만들기에 관한 마지막 강좌입니다.
여기까지 오시느라 수고 많으셨어요.
- 들어가며
- 몇가지 알아둘 점
매터리얼, 텍스쳐, 스키닝 등의 용어에 대한 설명은 자세히 하지 않았습니다.
별도로 책/인터넷을 참고하시거나 델마당 게임제작 게시판에 질문을 하시면 되겠습니다.
- 강좌의 내용
3D 모델링/매핑 데이타를 뽑아내는 Exporter 를 살펴보겠습니다.
지난번과 마찬가지로 스크립트 소스를 풀로 올리고 주석을 다는 방식으로 진행하겠습니다.
설명하는 내용은 // 로 처리하겠습니다. (이건 스크립트 실제 주석처리는 아닙니다)
지난 시간과 중복되는 내용에 대한 설명은 생략하겠습니다.
- DelDX_Dobject_Export.ms 소스 분석
macroScript DBJExport category: “DelDX”
(
–==============================================================상수 (어차피 변수로 처리되지만)
global DBJVersion = 0010
global Dir_Output = “D:\Work\델마당\세미나 강의\예제\Dat\DBJ”
global StrLen_Comment = 64
–==============================================================변수
global ExportNow = False
global Comment = ""
global TickToMS
global DBJ_Weight_Max = 4
global UseSkin = False
global BoneExists = False
// DBJ_Weight_Max 는 버텍스(점) 하나에 최대 몇개의 본이 연결될 수 있는가를 나타냅니다.
// 스키닝에서 일반적으로 네개가 적당하다고 하여 4 로 잡았습니다.
–==============================================================라이브러리 파일을 포함하는 부분
Include “Lib_Fnc.ms”
Include “DelDX_Fnc.ms”
–==============================================================뭔가 입력받기 위한 창 (이벤트 핸들러도 있다 !!!)
rollout InputInfoBox “DBJ Exporter” width:291 height:238
(
label TitleLable “DBJ Exporter v” pos:[11,8] width:272 height:17
label lbl1 “간단한 설명을 적어보셔요…” pos:[11,42] width:272 height:17
edittext CommentEdit "" pos:[6,63] width:273 height:20 enabled:true
button OKBtn “OK” pos:[82,172] width:118 height:24
checkbox UseSkinCheck “Use Skin” pos:[10,97] width:139 height:19 enabled:true checked:false
on InputInfoBox open do
(
TitleLable.Caption = (“DBJ Exporter v”+(DBJVersion as String))
UseSkinCheck.Enabled=BoneExists
)
on InputInfoBox close do
(
Comment = CommentEdit.Text
UseSkin = UseSkinCheck.Checked
)
on OKBtn pressed do
(
DestroyDialog InputInfoBox
ExportNow = True
)
)
// 롤아웃 창에서 입력받는 값중에 UseSkin 은 스키닝을 할 것인가의 여부를 나타냅니다.
// 체크박스에 체크를 하지 않을 경우에는 스키닝 관련 정보를 저장하지 않게 됩니다.
undo off
(
–==============================================================본 목록 얻기 (◀ 요기서부터 실제 프로그램 시작)
local BoneObjCount = 0
local BoneObjList = GetBoneObjList()
if (BoneObjList != Undefined) then BoneObjCount = BoneObjList.Count
if BoneObjList.Count>0 then BoneExists = True
–==============================================================메쉬 목록 얻기 (선택된 메쉬가 없을때에는 모든 메쉬를…)
local MeshObjCount = GetMeshObjCount (Selection.Count>0) True
local MeshObjList = GetMeshObjList (Selection.Count>0) True
// 메쉬 목록을 얻는 부분입니다.
// GetMeshObjCount, GetMeshObjList 함수는 라이브러리에 넣어놨습니다.
// 함수 본체는 뒤쪽에서 따로 설명드립죠.
–==============================================================메쉬가 하나라도 있어야만 익스포트를 진행한다.
if (MeshObjList != Undefained) and (MeshObjCount > 0) then
(
–저장할 파일 이름 선택 (확장자는 dbj 를 기본으로 하자)
local DBJFileName = getSaveFileName caption:“DBJ Export” types: “DBJ Files (*.dbj)|*.dbj|All Files (*.*)|*.*|” filename: (Dir_Output+"\*.dbj")
–파일 이름이 선택되었어야 진행을 한다.
if DBJFileName != Undefined then
(
–필요한 정보를 미리 입력받고…
CreateDialog InputInfoBox modal:True
–==============================================================취소된게 아니면 계속 진행…
if ExportNow then
(
–매터리얼 목록 얻기
local MaterialList = GetMaterialList MeshObjList True
// 매터리얼(재질) 목록을 얻습니다.
// 맥스의 한 씬에는 사용되지 않는 매터리얼도 들어있을 수 있습니다.
// 이런걸 쓸데없이 다 파일로 저장할 필요가 없기 때문에
// 메쉬 목록을 참조해서 실제로 사용된 매터리얼만 목록에 담도록 하였습니다.
// 함수 본체는 뒤쪽에서 따로 설명을 하도록 하겠습니다.
–텍스쳐 목록 얻기
local TextureList = GetTextureList MaterialList
// 텍스쳐 목록 역시 매터리얼 목록을 참조해서 구하도록 하였습니다.
// 함수 본체는 역시나 뒤쪽에서 설명을… (앞으로 이런 얘기도 생략…)
–본 목록 얻기
// 본 목록을 앞에서 구했는데 여기에 주석이 붙어있네요.
// 실수입니다.
–프레임 틱을 ms 단위로 바꾸기 위한 상수
TickToMS = ticksPerFrame * 1000 / 4800
–파일 열기
local DBJFile = fOpen DBJFileName “wb”
// 지금까지 본, 메쉬, 매터리얼, 텍스쳐 목록을 구하고 파일을 열었습니다.
// 이후의 코드들은 실제로 저장할 정보들을 구하고 또 저장하는 과정을 보여줍니다.
// 다소 복잡하더라도 흐름을 파악하도록 쭈욱 훑어보아주셔요.
–=========================================================파일이 열렸으면 계속 진행 (◀ 요기서부터 실제 익스포트 시작)
if DBJFile != Undefined then
(
–==============================================================================전체 헤더 sHeader 쓰기
WriteLong DBJFile DBJVersion –버전
WriteStringByLen DBJFile Comment StrLen_Comment –이 오브젝트에 대한 간단한 설명
WriteLong DBJFile MeshObjCount –메쉬 수
WriteLong DBJFile MaterialList.Count –매터리얼 수
WriteLong DBJFile TextureList.Count –텍스쳐 수
WriteLong DBJFile 0 –UV 애니메이션 수
WriteLong DBJFile 0 –TI 애니메이션 수
// 헤더에 0 이라는 정수 두개를 그냥 괜히 쓰는 부분이 있는데요.
// 이것은 무시하시기 바랍니다.
// 텍스쳐 자체의 애니메이션 등을 지원하기 위해 달아둔 카운터인데요
// 맥스에서 익스포트할때에는 적용되지 않거든요…
// 나중에 별도의 툴을 만들어서 이 부분을 채워줄 수 있습니다만, 거기까지 강좌가 진행될지 의문…
local BoundBox = GetBoundBox MeshObjList –바운드 박스
WriteByte DBJFile 2 –바운드는 박스(2)타입.
WriteVector DBJFile BoundBox.MinPos
WriteVector DBJFile BoundBox.MaxPos
// 바운드박스를 구해서 저장합니다.
// 바운드박스 타입은 DelDX 에서의 바운드박스 구조체와 일치시키기 위해 넣어둔 부분입니다.
// 숫자 2 를 써준 부분이 바로 “박스” 형태로 바운드박스를 정의한 부분입니다.
// 아마 1 을 써주면 바운드"구"로 인식하도록 되어있을꺼예요… (기억이 가물~)
// GetBoundBox 함수는 메쉬 목록으로부터 바운드박스를 계산해내는 부분인데요,
// 라이브러리에 만들어놨어요.
–==============================================================================메쉬 목록 sMeshList 쓰기
gc()–//«
// 본, 애니메이션 익스포터와 달리 오브젝트 익스포터는 메모리를 많이 사용합니다.
// 스크립트의 단점이기도 한데요.
// 암튼, 메모리가 너무 많이 사용되는걸 막기 위해 이렇게 중간중간에 가비지 컬렉션을 해줘야 합니다.
// 이제 메쉬 정보를 파일에 저장하게 될텐데요,
// 메쉬 인덱스 순서대로 저장하는게 아니라 매터리얼에 따라 저장을 하게 됩니다.
// 같은 매터리얼이 적용된 메쉬끼리 모이게 되는거죠.
iMeshMax = MeshObjList.Count
for iMesh = 1 to iMeshMax do
(
–멀티/서브 매터리얼인 경우에는 매터리얼 리스트로 나눠서 처리…
local isMultiMat = ((GetEnabledSubMatCount MeshObjList[iMesh].material)>0)
if isMultiMat then local TempMatList = GetEnabledSubMat MeshObjList[iMesh].material
else local TempMatList = #(MeshObjList[iMesh].material)
iSubMax = TempMatList.Count
// 맥스에서의 매터리얼(재질) 구조는 여러가지가 있는데요,
// 여기서는 딱 두가지 경우만 지원을 합니다.
// “스탠다드” 형태와 “멀티/서브 매터리얼” 형태입니다.
// 멀티/서브 매터리얼이란건 매터리얼 안에 다수의 매터리얼이 들어있는 구조인데요,
// 각 매터리얼 안에 또다시 다수의 매터리얼이 들어있을 수도 있고… 이게 무한 반복될 수도 있답니다.
// 하지만 귀챠니즘과 프로그램의 효율성을 생각해서
// 멀티/서브 매터리얼 안의 매터리얼은 모두 스탠다드 형태여야 하는 것으로 제한하였습니다.
// 디자이너들이 자신의 개성을 맘껏 발휘하지 못하도록 미리 합의해둘 필요가 있는 부분이죠.
// 멀티/서브 매터리얼을 지원하기 위해서 이렇게 구조가 복잡해졌는데요,
// 결국 여러개의 매터리얼이 적용된 하나의 메쉬를 매터리얼 수 대로 쪼개서 해결한 것이랍니다.
// DX 에서 매터리얼은 한번에 하나밖에는 적용할 수 없기때문에 이렇게 메쉬를 쪼갤 수밖에 없는거죠.
–매터리얼 수 대로 처리한다.
for iSub = 1 to iSubMax do –Max Script 에서 배열은 항상 1 부터 시작한다.
(
// 이제부터 메쉬 정보를 저장합니다.
—————————————————-메쉬 헤더 smHeader 쓰기
local MyBoneID = 0
if MeshObjList[iMesh].Parent != Undefined then
(
MyBoneID = GetIndexInList MeshObjList[iMesh].Parent BoneObjList – 0 이면 부모본 없다
)
// 본 인덱스를 저장하기 위해 본 목록중에 부모본이 몇번째인가의 인덱스를 MyBoneID 변수에 저장했습니다.
// GetIndexInList 함수는 리스트에서 해당하는 것이 몇번째에 위치하는가를 알아내는 함수로, 라이브러리에 넣어뒀어요.
local MaterialIndex = (GetIndexInList TempMatList[iSub] MaterialList)-1 –매터리얼 인덱스
// 매터리얼 번호도 마찬가지 방법으로 구했습니다.
local BillBoardType = GetBillBoardTypeByName MeshObjList[iMesh].Name –메쉬 이름에 따라 메쉬 빌보드 타입을 정해준다.
// 빌보드 타입은 맥스에서 따로 정할 수가 없어서 메쉬의 이름에 표시하도록 하였습니다.
// 메쉬 이름이 “[b0]” 로 시작하면 Y 축 중심으로 회전하는 빌보드, “[b1]” 로 시작하면 카메라를 바라보는 빌보드입니다.
// Y 축 빌보드는 수직으로 세워져있어야 하는 빌보드(멀리 보이는 나무 등)에서 사용하구요
// 카메라를 바라보는 빌보드는 반짝이는 효과나 조명 글로우, 후광효과 등에서 사용합니다.
local FaceIndexList = #() –요기서부텀 Face 의 목록을 얻는다…
if isMultiMat then –멀티/서브 매터렬일 경우에는 그에 따라 Face 배열 일부만을 얻어야 한다.
(
local MatSubIndex = MeshObjList[iMesh].Material.materialIDList[GetIndexInList TempMatList[iSub] MeshObjList[iMesh].Material.MaterialList]
iFaceMax = MeshObjList[iMesh].Mesh.NumFaces
for iFace = 1 to iFaceMax do
(
local FaceMatID = getFaceMatID MeshObjList[iMesh].Mesh iFace
if MatSubIndex == FaceMatID then
(
Append FaceIndexList iFace
)
)
// 바로 위의 코드가 Face(폴리곤) 정보를 얻는 부분입니다.
// 근데 멀티/서브 매터리얼의 적용을 받는 메쉬인 경우에는 일부 폴리곤(해당 서브 매터리얼에 적용된 폴리곤들)들의 목록을
// 따로 떼어 저장해야 합니다.
// 해놓고 나니 별거 없지만 처음에 만들때에는 참 골아프더군요.
)
else –평범한 매터렬일 경우에는 몽땅…
(
iFaceMax = MeshObjList[iMesh].Mesh.NumFaces
for iFace = 1 to iFaceMax do Append FaceIndexList iFace
// 그냥 스탠다드 매터리얼인 경우에는 이렇게 메쉬의 모든 폴리곤 정보를 사용하면 됩니다. (이렇게 편한것을…)
)
local FaceCount = FaceIndexList.Count
local NumberOfVertex = FaceCount*3 –버텍스는 Face 수의 3 배…
// 아래 코드는 매터리얼 정보를 얻는 부분입니다.
// 해당 매터리얼의 옵셋(이동값), 타일링(반복값), 미러(대칭구조로 반복할 것인가의 여부) 정보를 얻습니다.
if MaterialIndex>=0 then
(
local OffsetU = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.U_Offset
local OffsetV = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.V_Offset
local TileU = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.U_Tiling
local TileV = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.V_Tiling
local MirrorU = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.U_Mirror
local MirrorV = MaterialList[MaterialIndex+1].DiffuseMap.coordinates.V_Mirror
)
else
(
local OffsetU = 0.0
local OffsetV = 0.0
local TileU = 1.0
local TileV = 1.0
local MirrorU = False
local MirrorV = False
)
// 아래 코드는 메쉬의 부모메쉬 인덱스를 구하는 부분입니다.
// 역시나 GetIndexInList 라이브러리 함수를 사용했습니다.
local ParentMeshIndex = -1 –부모메쉬의 인덱스를 얻고
if MeshObjList[iMesh].Parent != Undefined then
(
ParentMeshIndex = (GetIndexInList MeshObjList[iMesh].Parent MeshObjList)-1
)
// 스키닝을 하는 물체라 하더라도 각각의 메쉬는 스킨 정보를 가질수도, 안 가질수도 있겠지요.
// 그래서 스키닝 정보를 저장할지 여부를 미리 선택해놓은 UseSkin 변수를 참조하면서도
// 각각의 메쉬에 대한 스키닝 여부를 별도로 알아내어 isSkinned 변수에 넣도록 하였습니다.
local isSkinned = false – 스킨 사용 여부
if UseSkin then –스킨 사용 옵션이 True 이면 메쉬별 스킨 사용 여부를 알아본다.
(
try – 요기 왜 try 를 썼는지 당췌 기억이 나질 않네… -.-;
(
if (physiqueops.getPhysiqueModifier MeshObjList[iMesh]) != undefined then
(
if (physiqueops.getBoneCount MeshObjList[iMesh])>0 then isSkinned = true
)
)
catch
(
)
)
// 메쉬 자체도 별도의 애니메이션을 가질 수 있습니다.
// 이것은 앞서 다루었던 본애니메이션과는 달리, 오브젝트 자체만으로 이루어지는 애니메이션입니다.
// 예를 들어, 헬기의 프로펠러가 회전운동을 계속 하는 것을 만들어야 한다면
// 프로펠러 메쉬에 회전 애니메이션을 적용해놓으면 되는거죠.
// UseAnimKey 는 저장할 메쉬의 애니메이션이 있는가의 여부입니다.
UseAnimKey = MeshObjList[iMesh].Position.IsAnimated or
MeshObjList[iMesh].Rotation.IsAnimated or
MeshObjList[iMesh].Scale.IsAnimated
// 아래 코드는 지금까지 구해놓은 메쉬 정보들을 파일에 저장하는 부분입니다.
WriteLong DBJFile (MyBoneID-1)
WriteLong DBJFile MaterialIndex
WriteLong DBJFile NumberOfVertex
WriteLong DBJFile ParentMeshIndex
WriteMatrix3 DBJFile MeshObjList[iMesh].ObjectTransform – 그냥 Transform 과는 무슨 차이일까 ?
if MeshObjList[iMesh].Parent == Undefined then WriteMatrix3 DBJFile BaseMatrix
else WriteMatrix3 DBJFile MeshObjList[iMesh].Parent.Transform
WriteByte DBJFile BillBoardType
WriteBoolean DBJFile isSkinned
WriteBoolean DBJFile UseAnimKey
// 위 코드에서 부모메쉬의 행렬을 저장하는 부분이 있지요 ?
// 부모가 없을때에는 그냥 항등행렬(BaseMatrix)을 저장하도록 되어있습니다.
// BaseMatrix 는 라이브러리에 따로 정의를 해놓았습니다.
// 기본적인 정보를 저장한 후에는 좀 무거운 정보를 저장합니다.
// 아래는 버텍스 정보를 저장하는 부분입니다.
—————————————————-버텍스 배열 저장…
gc()–//«
for iFace = 1 to FaceCount do
(
FaceVerts = GetFace MeshObjList[iMesh].Mesh FaceIndexList[iFace]
// GetFace 는 메쉬의 각 폴리곤을 구성하는 버텍스의 인덱스를 돌려주는 맥스 스크립트 함수입니다.
UVVerts = GetTVFace MeshObjList[iMesh].Mesh FaceIndexList[iFace]
// GetTVFace 는 메쉬의 각 폴리곤을 구성하는 UV 값의 인덱스를 돌려주는 맥스 스크립트입니다.
for i = 1 to 3 do
(
vXYZ = GetVert MeshObjList[iMesh].Mesh FaceVerts[i] – xyz
// 위에서 구한 버텍스 인덱스의 실제 좌표값을 구하고
vNorm = GetNormal MeshObjList[iMesh].Mesh FaceVerts[i] – Normal
// 노멀벡터도 구하고
UVOrg = GetTVert MeshObjList[iMesh].Mesh UVVerts[i] – UV
// UV 값도 구합니다.
U = ((UVOrg.x-OffsetU-0.5)*TileU)+0.5
V = ((UVOrg.y-OffsetV-0.5)*TileV)+0.5
if MirrorU then U = U*2
if MirrorV then V = V*2
// 이때, UV 값은 매터리얼의 옵셋, 타일링 값에 따라 약간의 연산이 필요합니다.
// 예를 들어서…
// 옵셋이 0.5 라면 텍스쳐를 전체적으로 0.5 만큼 이동해서 그린다는 것이므로
// 미리 각 버텍스의 UV 값에 0.5 를 더해주면
// 나중에 매터리얼의 옵셋을 따로 계산할 필요가 없게 되는거죠.
// 아래와 같이 파일로 저장해주면 됩니다.
WriteVector DBJFile vXYZ
WriteVector DBJFile vNorm
WriteFloat DBJFile U
WriteFloat DBJFile (1-V)
// 또한, V 값은 DX 에서와는 달리 맥스에서는 위쪽을 1, 아래쪽을 0 으로 잡기때문에
// 이렇게 1-V 의 값을 저장해줘야 합니다.
)
gc()–//«
)
// 이제 스키닝이 적용된 메쉬에 대해서 처리하는 부분입니다.
—————————————————-스킨 가중치 저장
if isSkinned and (FaceCount>0) then
(
for iFace = 1 to FaceCount do
(
local FaceVerts = GetFace MeshObjList[iMesh].Mesh FaceIndexList[iFace]
for i = 1 to 3 do
(
Skin_BoneList = physiqueops.getVertexBones MeshObjList[iMesh] FaceVerts[i]
// physiqueops 는 맥스에 있는게 아니라 별도의 플러그인에서 지원해주는 것입니다.
// 이게 있어야만 맥스 스크립트에서 “피지크"를 이용한 스키닝 값을 얻어올 수 있습니다.
// 강의자료의 스크립트 폴더에 보면 Scripts 폴더 말고 Plugins 폴더가 있는데요
// 여기에 IPhysique.gup 를 넣어두었습니다.
// 이게 맥스 플러그인 파일인데요, 바로 physiqueops 를 스크립트에서 사용할 수 있도록 해주는
// 고마운 파일이지요…
// physiqueops.getVertexBones 는 해당 메쉬의 해당 버텍스가 어느어느 본에 연결되어있는지,
// 그리고 그 가중치(Weight 값)는 얼마인지를 목록으로 돌려주는 함수입니다.
// 이제 스키닝 정보를 저장합니다.
// 단, 저장되는 본 목록의 수가 DBJ_Weight_Max(앞서 4 로 정의했지요)를 넘지 못하게 해야 합니다.
WriteLong DBJFile Skin_BoneList.Count
for j = 1 to DBJ_Weight_Max do
(
if j<=Skin_BoneList.Count then
(
WriteLong DBJFile ((GetIndexInList Skin_BoneList[j] BoneObjList)-1)
WriteFloat DBJFile (physiqueops.getVertexWeight MeshObjList[iMesh] FaceVerts[i] j)
)
else
(
WriteLong DBJFile -1
WriteFloat DBJFile 0.0
// 연결된 본의 수량이 4 미만인 경우에는 빈 칸을 이렇게 채워줍니다.
// 본 인덱스가 -1 인 경우에는 무시하도록 엔진에서 프로그래밍을 해두면 되겠지요.
)
)
)
)
)
gc()–//«
// 이제 메쉬 자체의 애니메이션을 저장하는 부분입니다.
// 메쉬 애니메이션은 복잡하게 RTS 로 하지 않고 행렬을 이용해서 쉽게쉽게 처리하였습니다.
—————————————————-메쉬 애니메이션 Matrix 저장
if UseAnimKey then
(
local TMKeyList = #()
local TMKeyCount = 0
local Loop = True –시간을 잘게 쪼개서 애니 데이타 배열을 만든다
local CurrTime = AnimationRange.Start
while Loop do at Time CurrTime
(
// 지난 시간에 본 at Time 구문 기억하시지요…?
local TMData = TTimeAndValue Time: CurrTime Value: MeshObjList[iMesh].ObjectTransform
// TMData 라는 변수에 TTimeAndValue 라는 자료형으로 현재시각(CurrTime)과 메쉬의 행렬(ObjectTransform)을
// 저장했습니다.
// 각 시각에 맞춰서 메쉬에 이 행렬을 차례로 적용시켜주면 그게 바로 애니메이션이지요…
// RTS 를 따로 저장하면 용량이 줄겠지만 귀챦기도 하고 또 용량 차이가 그리 크게 나지도 않아서
// 이렇게 행렬로 저장하도록 하였습니다.
Append TMKeyList TMData
TMKeyCount = TMKeyCount+1
–마무리——————————————–
if CurrTime < AnimationRange.End then
(
CurrTime = CurrTime + 1 – SamplingGap = 1
if CurrTime > AnimationRange.End then CurrTime = AnimationRange.End
)
else
(
Loop = False;
)
)
// While 문을 벗어났다면 이제 저장된 시간/행렬 목록을 파일로 저장하면 되겠습니다.
WriteLong DBJFile TMKeyCount
for iKey = 1 to TMKeyCount do
(
WriteLong DBJFile ((TMKeyList[iKey].Time-AnimationRange.Start)*TickToMS)
WriteMatrix3 DBJFile TMKeyList[iKey].Value
)
)
)
)
// 지금까지 메쉬 정보를 파일로 저장했습니다.
// 이제 매터리얼 목록을 저장하겠습니다.
–==============================================================================매터리얼 목록 sMaterial 쓰기
iMatMax = MaterialList.Count
for iMat = 1 to iMatMax do
(
WriteVector DBJFile (ColorToVector MaterialList[iMat].Diffuse)
WriteVector DBJFile (ColorToVector MaterialList[iMat].Ambient)
WriteVector DBJFile (ColorToVector MaterialList[iMat].specular)
// 매터리얼 조명값입니다만, 이걸 디자이너들이 늘 쓰는건 아니더군요.
// 경우에 따라서는 쓰지 않기를 원하는 경우도 있더군요.
// DelDX 에서는 이게 적용되지 않도록 되어있으니 참고하시구요.
// 디자이너와도 협의를 해놓으셔야 합니다.
local TextureIndex = (GetIndexInList MaterialList[iMat].diffuseMap TextureList)-1
WriteLong DBJFile TextureIndex
// 매터리얼에서 사용하는 텍스쳐 번호를 적어줍니다.
local AlphaValue = (MaterialList[iMat].Diffuse.a as Integer)
WriteByte DBJFile AlphaValue
// 알파블렌딩 비율을 적어줍니다.
if MaterialList[iMat].DiffuseMap.coordinates.U_Mirror then UWrap = Wrap_Mirror
else if MaterialList[iMat].DiffuseMap.coordinates.U_Tile then UWrap = Wrap_Wrap
else UWrap = Wrap_Border
if MaterialList[iMat].DiffuseMap.coordinates.V_Mirror then VWrap = Wrap_Mirror
else if MaterialList[iMat].DiffuseMap.coordinates.V_Tile then VWrap = Wrap_Wrap
else VWrap = Wrap_Border
WriteLong DBJFile UWrap
WriteLong DBJFile VWrap
// 텍스쳐 반복 방법을 적어줍니다.
// Wrap_Mirror 등의 값은 라이브러리에 미리 상수로 정의해놓은 것들입니다.
// 일부러 DX 에서 사용하는 D3DTADDRESS_… 값과 일치시켜놨지요.
)
gc()–//«
// UVAnimInfo, TIAnimInfo 등은 별도의 툴에서 끼워넣을 정보입니다.
–==============================================================================다음 내용은 별도의 툴에서만 추가할 수 있다.
–UVAnimInfo
–TIAnimInfo
–TextureInfo
// TextureInfo 는 오타입니다.
// 이제 텍스쳐 정보를 저장할 차례입니다.
–==============================================================================끝으로 텍스쳐… (편의상 텍스쳐 자체를 파일에 포함시켰다)
iTexMax = TextureList.Count
for iTex = 1 to iTexMax do
(
–텍스쳐 파일 이름
local TempStr = FilenameFromPath TextureList[iTex].FileName
// 파일 이름을 저장해줍니다.
–bUseColorKey, ColorKey
WriteLong DBJFile 0
// 컬러키(투명색)는 툴에서 별도로 처리해주도록 하였습니다.
// 요즘은 컬러키를 잘 안쓰게 되더군요.
// 암튼 0 으로 해두면 컬러키를 쓰지 않는다는 뜻입니다.
–AlphaType
local AlphaType = GetAlphaTypeFromName TempStr
WriteByte DBJFile AlphaType
// 알파블렌딩 타입을 두가지로 분류하였습니다.
// GetAlphaTypeFromName 함수는 텍스쳐 파일의 이름이 [al] 로 시작되면 보통의 블렌딩 방식으로,
// [sc] 로 시작되면 덧셈 알파블렌딩 방식(좀 더 밝아집니다)으로 판단하도록 만들어놓은 라이브러리 함수입니다.
–FileSize
local TexFileSize = GetFileSize TextureList[iTex].FileName
WriteLong DBJFile TexFileSize
// 이미지파일의 크기도 저장해둡니다.
)
// 여기까지는 텍스쳐 목록들의 기본적인 정보만을 저장한 부분입니다.
// 이제 텍스쳐 목록들에 해당하는 이미지파일을 몽땅 불러서 줄줄이 붙여서 저장하도록 하겠습니다.
–Texture File Body
iTexMax = TextureList.Count
for iTex = 1 to iTexMax do
(
local TexFileSize = GetFileSize TextureList[iTex].FileName
local TexFile = fOpen TextureList[iTex].FileName “rb”
// 이미지 파일을 바이너리 읽기전용으로 열고
// 그대로 복사해서 저장합니다.
// 저장한 후에는 원본 이미지파일을 닫아줘야 되구요.
// 근데요, 에러가 날 수도 있기때문에 이렇게 메시지박스 처리를 좀 해줬습니다.
// 물론 오류가 나지 않는게 정상이겠지만요…
if TexFile != Undefined then
(
if not (CopyFromToFile DBJFile TexFile TexFileSize) then
(
MessageBox (“텍스쳐파일 복사 오류 : “+TempStr)
)
if not (fClose TexFile) then
(
MessageBox (“텍스쳐파일 닫기 오류 : “+TempStr)
)
)
else
(
TexFileSize = 0
MessageBox (“텍스쳐파일 접근 실패 : “+TempStr)
)
)
gc()–//«
–마무리
if fClose DBJFile then
(
MessageBox “DBJ 파일로 Export 되었습니다.”
)
else
(
MessageBox “파일 닫기 실패”
)
)
else
(
MessageBox “파일 열기 실패”
)
)
)
)
else
(
MessageBox “메쉬를 찾을 수 없네요…”
)
gc()
)–End of undo off
)
- 주요 라이브러리 함수들
- GetMeshObjCount
// GetMeshObjList 함수랑 비슷하니깐 생략할께요… (좀 졸려서…=.=)
- GetMeshObjList
// 메쉬 목록을 얻는 함수입니다.
// 파라미터 OnlySelected 는 선택된 메쉬만 익스포트할때 사용합니다.
// UseMultiMat 는 멀티/서브 매터리얼이 적용된 메쉬도 처리할 것인가의 여부인데요
// 그냥 True 로 하시면 됩니다.
fn GetMeshObjList OnlySelected UseMultiMat =
(
MeshObjList = #()
NotSupported = 0
// NotSupported 는 제외된 것의 수량을 저장하는건데요.
// 디버그 목적으로만 넣어뒀을 뿐, 익스포트에는 전혀 영향을 주지 않습니다.
// 무시하셔도 되어요.
if OnlySelected then
(
local ObjList = Selection
// Selection 은 선택된 것들의 목록을 뜻하는 맥스 스크립트 명령입니다.
)
else
(
local ObjList = $*
// $* 는 씬 안의 모든것을 뜻하는 맥스 스크립트 명령입니다.
)
if ObjList == undefined then Return Undefined
if ObjList.Count <= 0 then Return Undefined
// 목록을 하나도 못 얻었다면 Nil 을 리턴하고 함수 종료…
// 지난시간에 보았던 for .. in .. 구문 기억하시지요 ?
for iObj in ObjList do
(
// 모든걸 다 저장하진 않을꺼거든요…
// 우선, 메쉬라고 할만 한 것(GeometryClass)들이어야 하고
// 숨겨진 것은 제외해야 하고요, 레이어가 통째로 숨겨져있을 경우도 제외해야 하지요.
// 레이어는 포토샵의 레이어처럼 맥스에도 있는데요, 그냥 현재 보여지는 레이어만 신경쓰면 됩니다.
if (SuperClassOf iObj == GeometryClass) and (not iObj.isHidden) and (not iObj.Layer.isHidden) then
(
case ClassOf iObj of
(
Biped_object : NotSupported = NotSupported+1
// Biped_object 는 본으로만 사용되는 것이니깐 메쉬 목록에는 넣지 않구요.
Helper : NotSupported = NotSupported+1
// 헬퍼라는게 있는데 이것도 제외…
Dummy : NotSupported = NotSupported+1
// 더미도 제외…
Targetobject : NotSupported = NotSupported+1
// Targetobject 라는건 뭔지 저도 잘 모르겠지만 암튼 제외…
Default:
// 이것저것 제외하고 남은것들을 메쉬로 판단하고 목록에 넣어줍니다.
(
if ClassOf iObj.Material == Multimaterial then
(
if UseMultiMat then
(
// 멀티/서브 매터리얼이 적용된 메쉬인 경우에는 UseMultiMat 파라미터가 True 일때에만
// 목록에 추가해줍니다.
Append MeshObjList iObj
SubCount = GetEnabledSubMatCount iObj.Material
// SubCount 는 GetMeshObjCount 함수에서 쓰던건데 여기에 묻어왔군요.
// 없어도 되는 코드입니다… -.-; (바본가…)
)
else
(
NotSupported = NotSupported+1
)
)
else
(
Append MeshObjList iObj
// 스탠다드 매터리얼이 적용된 메쉬는 편안한 마음으로 목록에 추가하고요.
)
)
)
)
)
// 목록을 리턴해주면 함수가 끝나지요.
Return MeshObjList
)
- GetBoundBox
// 메쉬목록을 참조해서 바운드박스를 얻어내는 함수입니다.
fn GetBoundBox MeshObjList =
(
if (MeshObjList == Undefined) or (MeshObjList.Count<=0) then
(
ReturnValue = TBoundBox [0, 0, 0] [0, 0, 0]
// 메쉬목록이 비어있으면 크기가 0 인 박스를 리턴합니다.
)
else
(
ReturnValue = TBoundBox [0, 0, 0] [0, 0, 0]
ReturnValue.MinPos = MeshObjList[1].Min
ReturnValue.MaxPos = MeshObjList[1].Max
// 메쉬를 쭈욱 돌면서 x, y, z 좌표의 최대값과 최소값을 구합니다.
for iObj = 2 to MeshObjList.Count do
(
if ReturnValue.MaxPos.x if ReturnValue.MaxPos.y if ReturnValue.MaxPos.z if ReturnValue.MinPos.x>MeshObjList[iObj].Min.x then ReturnValue.MinPos.x = MeshObjList[iObj].Min.x
if ReturnValue.MinPos.y>MeshObjList[iObj].Min.y then ReturnValue.MinPos.y = MeshObjList[iObj].Min.y
if ReturnValue.MinPos.z>MeshObjList[iObj].Min.z then ReturnValue.MinPos.z = MeshObjList[iObj].Min.z
)
)
// 구한 값을 리턴합니다.
Return ReturnValue
)
- GetIndexInList
// 목록에서 해당하는 것이 몇번째 위치에 있는지를 돌려주는 함수입니다.
// 간단하니 그냥 훑어보시면 될 듯…
fn GetIndexInList SrcObj SrcList =
(
ReturnValue = 0;
for iObj=1 to SrcList.Count do
(
if SrcList[iObj]==SrcObj then ReturnValue = iObj
)
Return ReturnValue
)
드디어 끝났군요.
아 ~ 속시원해… (^o^)v
지금까지 맥스 스크립트를 이용해서 원하는 정보를 뽑아내는 과정을 살펴보았습니다.
모두 이해가 되지 않더라도 일단 넘어가시구요,
이 스크립트를 이용하여 데이타를 뽑아내는 방법만이라도 꼭 익히시기 바랍니다.
그럼 뽑아낸 정보를 이용해서 델파이로 3D 를 구현하실 수 있을테니까요.
다음 시간에는 다시 정다운 델파이로 돌아가게 되겠습니다.
문턱 강좌에서 가장 중요한 내용이 될 듯 하구요,
3D 를 구현했다는 기분이 들만한 내용이 될 것입니다.
개인적으로 좀 피곤해서요, 당분간 잠시 좀 쉬다가 다시 강좌를 이어나가도록 하겠습니다.
아무래도 내년으로 넘어갈 듯 하네요… -.-;