ObjPaser
obj에는 대략 정보가 아래와 같이 작성되어있다.
mtllib: mtl 파일 이름
v: vertex position
vt: texture uv coord
vn: vertex normal
f: face ( v / vt/ vn )
Parser를 만드는 방법은 간단하다.
v가 보일 때 Position을 저장하는 array에 저장을 한다.
vt가 보일 때 Texture Coordinate을 저장하는 array에 저장을 한다.
vn이 보일 때 normal을 저장하는 array에 저장을 한다.
f를 만나면 index1 / index2/ index3을 만나게 될 텐데
이때 저장해둔 v,vt,vn에서 꺼내오면 된다.
( vn이 없을 때도 있는데, 그때는 index1 // index2로 되어있으니 따로 처리하는 코드를 추가해주자)
아래의 코드는 직접 구현한 코드이다.
수도코드 느낌으로 보자
auto flushSubset = [&]()
{
if (Faces.empty()) return;
//Mesh Data로 생성
MeshesData.push_back(MakeMeshSubset(Positions, Normals, TexCoords, Faces, NameOfSubset));
SubsetMatKeys.push_back(NameOfSubset);
Faces.clear();
};
while (std::getline(File, Line))
{
std::istringstream Tokenizer(Line);
FString Prefix;
Tokenizer >> Prefix;
if (Prefix == "usemtl")
{
// usemtl을 만났을 때 submesh처리
flushSubset();
Tokenizer >> NameOfSubset;
}
else if (Prefix == "o" || Prefix == "g")
{
//o, g는 따로 처리 안했음
}
else if (Prefix == "mtllib")
{
//mtl파일 이름 저장
std::getline(Tokenizer, MTLLibName);
while (!MTLLibName.empty() && (MTLLibName.front() == ' ' || MTLLibName.front() == '\t')) MTLLibName.erase(MTLLibName.begin());
}
if (Prefix == "v") // position
{
FPosition Position;
Tokenizer >> Position.x >> Position.y >> Position.z;
//0,0,0으로 조정 및 scale 조정
Position = (Position - CenterPos);
Position = Position * Scale;
Positions.push_back(Position);
}
else if (Prefix == "vn") // normal
{
FNormal Normal;
Tokenizer >> Normal.x >> Normal.y >> Normal.z;
Normals.push_back(Normal);
}
else if (Prefix == "vt") // uv
{
FTexCoord TexCoord;
Tokenizer >> TexCoord.u >> TexCoord.v;
TexCoords.push_back(TexCoord);
}
else if (Prefix == "f") // face
{
FString FaceBuffer;
FFace FirstVertex;
FFace ThirdVertex;
for (int i = 0; i < 3; i++)
{
Tokenizer >> FaceBuffer;
FFace Face = ParseFaceBuffer(FaceBuffer);
Faces.push_back(Face);
switch (i)
{
case 0:
FirstVertex = Face;
break;
case 2:
ThirdVertex = Face;
break;
}
}
}
}
flushSubset();
File.close();
이렇게만 obj 파싱은 해줘도 잘 될 것이다.
(아직 mtl 파일을 읽지 않아서, 랜덤색을 가지고있는 것이다.)


하지만 작은(?) 문제가 있다. 만약 f에서 조합하는 vertex가 3개가 아니라 4개 5개인 polygon이면??
아래와 같이 obj파일을 파싱하는데 문제가 생길 것이다.

그럴 경우 아래의 그림 처럼 쪼개주면된다.

구현방법은 간단하다.
1. 첫번째 vertex는 고정
2. 삼각형을 만들 때 사용한 3번째 정점을 다음 삼각형의 두번째 정점으로 사용
else if (Prefix == "f") // face
{
FString FaceBuffer;
FFace FirstVertex;
FFace ThirdVertex;
for (int i = 0; i < 3; i++)
{
Tokenizer >> FaceBuffer;
FFace Face = ParseFaceBuffer(FaceBuffer);
Faces.push_back(Face);
switch (i)
{
case 0:
FirstVertex = Face;
break;
case 2:
ThirdVertex = Face;
break;
}
}
// 들어오는 face의 veretx 수가 삼각형이 아닐 때 삼각형화 시켜주는 코드
while (Tokenizer >> FaceBuffer)
{
Faces.push_back(FirstVertex);
Faces.push_back(ThirdVertex);
Tokenizer >> FaceBuffer;
FFace Face = ParseFaceBuffer(FaceBuffer);
Faces.push_back(Face);
ThirdVertex = Face;
}
}
위의 코드를 추가하면 어떤 polygon도 아래와 같이 잘 파싱이될 것이다.

Material
이제 material을 받아와야한다.
obj파일을 읽다보면, usemtl 어쩌구가 등장하는데
이의미는
어쩌구라는 material을 사용할 mesh들입니다 라는 것이다.
mtl파일에 들어가보면
newmtl 어쩌구
Ka 1.000 1.000 1.000
Kd 1.000 1.000 1.000
Ks 0.000 0.000 0.000
d 1.0
illum 2
# 앰비언트 텍스처 맵
map_Ka lemur.tga
이렇게 정보있을 텐데, 이름으로 매핑된다고 생각하면된다.
mtl에 적혀있는 정보는 아래와 같다.
Ka: ambient color
Kd: diffuse color
Ks: Specular color
Ns: Specular exponent
# 앰비언트 텍스처 맵
map_Ka lemur.tga
map_Ks 등등
위와 같이 naming convention이 정해져있기에 그에 따라서 파싱을 하고,
pixel shader에서 map이 있으면 texturing을 map이 없으면 상수로 색을 칠해주면 된다.
renderdox으로 보면 texture가 잘 들어오는 것을 확인할 수 있다.


mtl을 파싱 하고 나면 아래와 같이 이쁜 차를 볼 수 있다.

지금은 grid, axis, object가 모두 동일한 vertex, pixel shader를 사용하고 있기에 이를 나눠주는 작업도 해주었다.


위에서 Mesh와 Material을 파싱했는데, 이것들을 아래의 구조로 저장을 하면, mesh의 material을 변경시킬 수 있다.

멋이게 꾸민... 모습..!

2. Obj Viewer
ObjViewerDebug의 exe파일에서는 UI를 빼고 draw하도록 설정했다.


obj 파일을 클릭할 때, ObjViewerDebug.exe파일로 실행되도록 설정해주었다.
이때 경로도 얻어올 수 있으니, obj파일을 잘 불러올 수 있을 것이다.
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
std::wstring objStringPath;
if (argv && argc >= 2)
{
objStringPath = argv[1];
}

Troblue Shooting
1. 경로 인식
앞에 띄어쓰기 하나 있다고, 경로를 인식을 못하고 투정을 부린다..푸하하

OBJ파일이 저장될 때 mtl에 적혀있는 material에 대한 Path가 상대경로로 저장될 때, 절대 경로로 저장 될 때가 있다.
심지어 상대경로가 obj제작자의 상대경로라서 사실상 의미없는 경로가 될 수 도 있다.
이를 해결하기 위해서 지정된 경로에서 파일명 + 확장자(.png or .dds)만 가져오고, 나의 상대 경로에 붙이는 작업을 수행했다.
'ComputerGraphics > 자체엔진' 카테고리의 다른 글
| [자체 엔진] Features (0) | 2026.03.09 |
|---|---|
| [자체 엔진] Bgario (0) | 2026.03.09 |
| [자체 엔진] DirectX 3D 게임 (Jungle Smash) (0) | 2025.12.12 |
| [자체 엔진] DirectX 3D 게임 (코치의 분노) (3) | 2025.12.12 |
| [자체엔진] PIE (Play In Editor) (2) | 2025.10.09 |