바닥 그리드로 공간 기준을 잡습니다.
하드코딩된 큐브를 Scene/Entity 구조로 바꿉니다.
Hierarchy/Inspector 패널에서 선택하고 값을 편집합니다.
InGuizmo로 뷰포트 안에서 직접 이동/회전/스케일을 조정합니다.
그리드 바닥
DX12의 PSO에는 PrimitiveTopologyType이 들어갑니다. TRIANGLE로 만든 PSO로는 선을 그릴 수 없습니다. 그래서 같은 셰이더와 루트 시그니처를 쓰더라도 PSO는 하나 더 만들어야 합니다.
auto linePso = pso; // 삼각형 PSO 설정을 복사
linePso.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE;
device->CreateGraphicsPipelineState(&linePso, IID_PPV_ARGS(&mLinePso));
드로우할 때는 IASetPrimitiveTopology(LINELIST)로 맞춥니다. PSO의 타입과 커맨드 리스트의 토폴로지(LIST/STRIP 등)는 별개 설정이고, 둘이 서로 호환돼야 합니다.
Scene / Entity 구조
라이트, 카메라, 스크립트처럼 컴포넌트가 늘어나는 시점에 옮길 계획입니다.
struct Entity
{
std::string name;
Transform transform;
const Mesh* mesh = nullptr; // 소유하지 않음
bool visible = true;
};
모듈구성
| 모듈 | 역할 |
| Mesh | VB/IB와 뷰를 소유하고 Draw(cmd)를 제공. CreateBue()는 1x1x1 단위 큐브 |
| Transform | position, rotation(오일러, 도 단위), scale을 들고 Matrix()를 계산 |
| Scene | std::vector<Entity>와 Create(name) |
| SceneRenderer | 그리드를 그린 뒤, 씬의 엔티티를 순회하며 mesh->Draw 호출 |
핵심은 메시와 인스턴스의 분리입니다. 큐브 버퍼는 하나인데, 엔티티 세 개가 각자 다른 Transform으로 그 메시를 그립니다.
Transform 행렬 순서
S * Rx * Ry * Rz * T
- S->R->T 순서여야 물체가 제자리에서 돌고 커집니다(DX12에서는 SRT순으로 곱해짐). 순서가 바뀌면 원점을 중심으로 공전합니다.
- Transform은 항상 메시 원점(피벗) 기준으로 적용됩니다. 큐브는 중심 피벗이라 scale을 키우면 위 아래로 같이 커집니다. 바닥에 붙이려면 position.y = 0.5 x scale.y로 맞춰야 합니다.
- 피벗 위치는 Transform이 아니라 메시 정점을 어떻게 만들었느냐로 결정됩니다.
주의: vector 재할당과 참조
Scene::Create가 Entity&를 반환하는데, 다음 Create에서 vector가 재할당되면 이전 참조는 무효가 됩니다. 그래서 선택 상태는 포인터가 아니라 인덱스로 저장했습니다. 엔티티 삭제 기능이 들어가면 안정적인 ID방식으로 바꿔야 합니다.
Hierarchy / Inspector 패널
Hierarchy: 엔티티 목록을 ImGui::Selectable로 표시합니다. PushID(i)를 써서 이름이 같은 엔티티가 있어도 ID가 충돌하지 않게 했습니다. 빈 공간을 클릭하면 선택이 해제 됩니다.
Inspector: 선택한 엔티티의 이름(InputText), 표시 여부(Checkbox), Transform(DragFloat3 세 개)을 편집합니다.
DragFloat3에는 &transform.position.x를 넘기는데, 이유는 float* 구조체이기에 다음 참조로 넘어가면 x->y->z 순으로 넘어가기 때문에 x 원소만 넘겨주면 된다.
도킹 레이아웃
DockBuilder로 4분할 기본 배치를 만들었습니다. Hierarchy는 왼쪽, Viewport는 가운데, Inspector는 오른쪽 위, Stats는 오른쪽 아래입니다.
ImGuizmo
연결방식
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(min.x, min.y, size.x, size.y); // Viewport 이미지 영역
if (ImGuizmo::Manipulate(view, proj, op, mode, world))
ImGuizmo::DecomposeMatrixToComponents(world, &t.position.x, &t.rotation.x, &t.scale.x);
- DirectXMath의 XMFLOAT4X4 메모리 배치는 ImGuizmo가 기대하는 float[16]과 같습니다. 그래서 변환 없이 포인터만 넘깁니다.
- Mainpulate는 조작이 있었던 프레임에만 true를 반환합니다. 수정된 월드 행렬을 위치/회전/스케일로 분해해서 Transform으로 되돌립니다.
- 회전 순서 일치: ImGuizmo는 X->Y->Z 순서로 오일러 각을 분해합니다. Transform::Matrix도 같은 순서로 맞춰야 기즈모를 놓을 때 물체가 튀지 않습니다.
- 스케일은 로컬 축에서만 의미가 있어서 LOCAL 모드를 쓰고, 이동 회전은 WORLD 모드를 씁니다.
단축키
W(이동), E(회전), R(회전)로 전환합니다.
우클릭 중(카메라 플라이)이거나 텍스트 입력 중에는 무시해서, 카메라의 WASD나 이름 입력과 충돌하지 않습니다.
에디터 설정 파일 위치
imgui.ini는 기본적으로 작업 디렉터리에 저장됩니다. launch.vs.json의 currentDir이 Assets로 잡혀 있어서 콘텐츠 폴더에 설정 파일이 섞였습니다. 언리얼의 Saved/처럼 분리했습니다.
std::filesystem::create_directories(BAEK_ROOT_DIR "Saved"); // ImGui는 폴더를 만들어주지 않음
io.IniFilename = BAEK_ROOT_DIR "Saved/imgui.ini";
결과

'DirectX12 > 엔진 만들어보기' 카테고리의 다른 글
| cgltf API 정리 (사용한것 기준) (0) | 2026.10.09 |
|---|---|
| 라이팅에서 PBR 머티리얼까지 (0) | 2026.10.06 |
| 서브모듈 정리, 카메라 (0) | 2026.10.03 |
| Viewport 만들기 (0) | 2026.10.02 |
| 엔진 구상 (0) | 2026.10.01 |
