계층구조는 이 블로그를 참고하여 작성하였습니다.
5장 - 1 에서는 계층구조의 종류 및 계층구조 구현(행렬 계산)에 대하여 작성되어있고,
5장 - 2 에서는 계층구조를 코드를 이용하여 구현하는것을 작성되어있다.
https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sipack7297&logNo=220427434575
https://blog.naver.com/sipack7297/220428447625
계층구조(Hierarchy)
물체의 대부분이 관절체(Articulated Body)로 이루어져있다.
관절체의 큰 특징은 부모-자식 관계를 갖는 구조로 이루어져 있으며 이를 계층구조라 한다.
(상속 개념 X)
자료구조의 트리(Tree)를 이용하여 구현이 가능하다.
부모 노드(Node) -자식 노드(Node)로 계층구조가 이루어져 있다.
즉, 계층 구조는 부모-자식 관계를 3D로 구현할떄 사용된다.
계층구조의 예시 - 사람
왼쪽 그림은 인간형태로 모델링된 메시, 오른쪽 그림은 인간모델의 계층구조를 도식화 한것
트리 구조로 보자면 허리(코어)가 최상위 부모(Root Node)가 되고 허리의 자식노드들이 가슴(상반신)과 엉덩이(하반신)이 되고 이것이 점점 아래로 내려가면서 트리 구조를 구현한다.
p.s
상속이 계층구조를 띄는거지 계층구조가 상속인것은 아니다.
컴퓨터 그래픽스의 계층구조는 tree자료구조인 nood로 연결되어있는 구조(중요)
계층구조의 구현 (행렬 계산)
계층 구조 구현은 행렬의 계산이므로 vertex들을 월드매트릭스로 옮기는 과정을 똑같이 진행하면 된다.
행렬 곱 계산시 순서(위치)가 매우 중요하므로 , 순서는 반드시 이 순서대로만 해야된다. (중요)
TransformMatrix(변환행렬) = scaleMatrix * rotationMatrix * translationMatrix
정점 변환 (월드) : Vworld = Vlocal * TM(TransformMatrix)
계층구조의 정점 (자식노드의 메쉬 구현) : Vworld = Vlocal * TMChild * TMParent
Vlocal : Local Space에 배치되어있는 Vertex
(Local/Model Space : 3D 오브젝트 모델을 처음 생성할 때에 모델을 가장 편리한 방법으로 배치 / 편리한 방법 : 자기 자신을 원점으로 잡음)
계층구조의 정점 구현은 LocalSpace의 자신(Child)의 vertex들이 배치되어있고 TM을 곱하여 월드 매트릭스로 변환 하고 부모의 변환행렬을 곱하여 자신(Child) LocalSpace의 기준을 부모의 원점을 기준으로 바꾸어 구현한다.
즉, 계층구조의 정점 구현시 LocalSpace가 자기 자신이 아닌 부모의 원점을 기준으로 구현하게 된다.
계층구조는 n개이상으로 구현이 되면 , 부모TM들은 내 위에 부모노드 들을 순서에 유의하며 n개를 전부 곱한다.
n개 계층 표현식 : Vworld = Vlocal * TMChild * TMParent(n) * TMParent(n-1) * ... * TMParent(1)
예를 들어 오른발(foot_r) 메시가 구현(렌더링)되기 위해서는 다음과 같은 과정을 거친다.
Vworld =
Vx3ds_foot_r(오른발의 Local Vertex)
* TMx3ds_foot_r (오른발 변환행렬)
* TMx3ds_leg_dr(오른 다리 변환행렬)
* TMx3ds_leg_ur(오른 허벅지 변환 행렬)
* TMx3ds_hip(엉덩이 변환행렬)
* TMx3ds_waist(허리 변환행렬)
밑에 부분은 그냥 내가 공부했던 내용중에 있어서 기입했다.
p.s 부모 노드들을 다 곱해야되는가?
꼭 그럴필요는 없다. 다 곱하게되면 세밀한 표현 및 정확한 위치를 얻을수 있지만, 구현 및 처리비용에 중점을 두게 된다면 구현시에 LocalVertex를 mesh를 그릴때 연산해주면된다.
즉, TM을 WorldMatrix라고 지칭하고, mesh를 렌더링(draw)하는 구간에 VLocal(Local vertex)를 연산해주면 월드에 구현이 된다.
//예시) 오른 다리의 부모TM을 다 곱했다고 가정
//오른 다리 구현
WorldMatrix = ScaleM * RotationM * TranslationM ;//월드 매트릭스 = TM
//오른 다리 draw
Vworld_3ds_leg_dr = Vx3ds_leg_dr * WorldMatrix * WorldMatrix_parent; // WorldMatrix_parent : 오른 다리의 부모TM의 곱
▼
//오른 발 구현
WorldMatrix = ScaleM * RotationM * TranslationM ;//월드 매트릭스 = TM
//오른 발 draw
Vworld_x3ds_foot_r = Vx3ds_foot_r * WorldMatrix * WorldMatrix_parent; // WorldMatrix_parent : 오른 발의 부모TM의 곱(오른다리가 포함되어있음)
<코드 영역>
※ 공부중인 코드 일부분을 긁어온거라 참고용으로만 봐주세요.
<지구 자전>
//DirectX11Graphics.h
class DirectX11Graphics
{
private:
...
DXGameObject cameraObj; // 카메라 Obj
// PrimitiveModel은 mesh data와 vertex shader에서 사용할 행렬 data가 담겨있다.
// 즉, Textuer를 렌더링하는 data를 담당하는 변수
std::unique_ptr <PrimitiveModel> Earth;
//Component 구조로 설계한 DXGameObject를 사용
//Transform으로 크기행렬,회전행렬,이동행렬 수정 및 Set할때 사용됨
//RenderComponent만 추가하여 Render 할때 사용되는 변수
DXGameObject EarthObj;
...
}
//DirectX11Graphics.cpp
void DirectX11Graphics::Update() // update 영역에서 지구를 자전시키는 코드를 생성
{
...
// timer class를 만들어서 델타타임을 활용 하게 만듬
const float deltaTime = timer->GetMilisecondsElapesed();
static float earthYaw = 0;
earthYaw += deltaTime * 0.1f;
EarthObj.GetTransform()->SetLocalRotation({ 0,earthYaw, 0 }); // 지구의 y축 회전
...
}
bool DirectX11Graphics::InitializeScene()
{
...
//지구
Earth = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화
Earth->Set_size(3); //지구의 scale 크기 Set
Earth->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);// Earth Model Init
Earth->MakePrimitive("Textures\\earth2048.bmp"); // mesh에 텍스쳐 이미지 추가
EarthObj.AddComponent<DXMeshRenderer>()->SetModel(Earth.get()); //Renderer component를 추가후 Earth Mesh를 저장
...
}
void DirectX11Graphics::RenderFrame()
{
...
//GameObject Primitive Mesh Draw
//world space에 배치된 obj를 뷰투영행렬을 연산해 카메라시점으로 변환
//인자값으로 카메라obj에 있는 뷰투영행렬을 Get
EarthObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
swapChain->Present(NULL, NULL);
}
코드 작성후 빌드를 하면
이제 계층구조를 활용하여 간단한 태양계를 만들어 볼건데,
이 그림의 의미는 계층구조를 구성시 부모-자식 관계가 꼭 Mesh 들로만 구성을 하지 않아도 된다는것이다.
(중요한 포인트)
왼쪽 처럼 구성을 한 경우 화면에 보여지는건 정상적으로 움직일 것이다.
하지만
태양의 자전을 멈추게하면 지구는 공전을 하지 않게 된다.
과학적으로는 태양의 자전으로 인해 지구가 공전 이런 내용이 맞다 하지만,
내가 사용하고싶은건 태양은 자전을 하지 않지만 지구는 공전을 하는것이 의도면 왼쪽처럼 구성하면 안된다.
만약 태양과 지구가 직접 node로 설계되어있다면
지구Vworld = 지구Vlocal * 지구TM * 태양TM 이 변환식일것이다.
이러면 태양 TM의 크기,회전,이동 행렬이 전부 지구에 직접적으로 연관을 주게 되어 태양이 자전이 멈추게 되면
지구도 공전을 하지 않게 된다.
하지만 오른쪽 그림의 지구공전이라는 node를 구성하게 되면
지구Vworld = 지구Vlocal * 지구TM * 지구공전TM * 태양TM 이 변환식일것이다.
지구 공전 TM은 회전만하면 되기 때문에 회전행렬만 update가 되고 위치나 크기는 전부 태양TM을 따르게된다.
이렇게 되면 실질적으로 Render 할경우 태양은 자전(회전행렬)을 하지 않아도도 지구공전TM은 회전행렬을 update 하고 있으며 그 동시에 태양 TM의 위치와 크기를 가지고 있다. 이제 지구TM에 부모TM(지구공전TM * 태양TM )을 곱해주면 지구는 지구TM의 회전행렬은 지구공전의 회전행렬이 연산되고 태양 TM의 위치,크기값이 연산됨으로 의도한 결과값을 낼수 있다.
즉, 지구공전,달 공전은 회전행렬만 Update하는 객체(Obj)이며 위치나 크기는 부모들을 따라간다.
내용이 많이 길어지고 복잡해졌지만 적고싶었던 이유는 <계층구조는 무조건 Mesh로만 구성되어있는것이 아니다>는것을 말하고 싶었고, 원하는 의도를 낼려면 이런식으로도 사용 할 수 있다는 것을 적고싶었다.
<태양계 생성>
//DirectX11Graphics.h
class DirectX11Graphics
{
private:
...
DXGameObject cameraObj; // 카메라 Obj
// PrimitiveModel은 mesh data와 vertex shader에서 사용할 행렬 data가 담겨있다.
// 즉, Textuer를 렌더링하는 data를 담당하는 변수
std::unique_ptr <PrimitiveModel> Earth;
//Component 구조로 설계한 DXGameObject를 사용
//Transform으로 크기행렬,회전행렬,이동행렬 수정 및 Set할때 사용됨
//RenderComponent만 추가하여 Render 할때 사용되는 변수
DXGameObject EarthObj;
//====태양계 생성으로 인한 태양,달 추가 =====//
std::unique_ptr <PrimitiveModel> Sun;
std::unique_ptr <PrimitiveModel> Moon;
DXGameObject SunObj;
DXGameObject MoonObj;
DXGameObject Sun_Earth_Obj; //지구 공전 obj
DXGameObject Earth_Moon_Obj; //달 공전 obj
...
}
//DirectX11Graphics.cpp
void DirectX11Graphics::Update() // update 영역에서 지구를 자전시키는 코드를 생성
{
...
// timer class를 만들어서 델타타임을 활용 하게 만듬
const float deltaTime = timer->GetMilisecondsElapesed();
static float earthYaw = 0;
earthYaw += deltaTime * 0.1f;
EarthObj.GetTransform()->SetLocalRotation({ 0,earthYaw, 0 }); // 지구의 y축 회전(자전)
//====태양계 생성으로 인한 태양,달 추가 =====//
static float SunYaw =0 , MoonYaw =0;
SunYaw += deltaTime * 0.05f;
MoonYaw += deltaTime * 0.1f;
SunObj.GetTransform()->SetLocalRotation({ 0, SunYaw, 0 }); // 태양의 y축 회전(자전)
Sun_Earth_Obj.GetTransform()->SetLocalRotation({ 0, SunYaw, 0 }); // 태양-지구의 y축 회전(지구 공전)
Earth_Moon_Obj.GetTransform()->SetLocalRotation({ 0, MoonYaw, 0 }); // 달의 y축 회전(자전)
MoonObj.GetTransform()->SetLocalRotation({ 0, MoonYaw, 0 }); // 지구-달 의 y축 회전(달 공전)
...
}
//Scenc(장면) Init 함수 (화면에 보여지는 구성 요소들을 초기화하는 함수)
bool DirectX11Graphics::InitializeScene()
{
...
//====태양계 생성으로 인한 태양,달 추가 및 node연결 작업 =====//
//태양
Sun = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화
Sun->Set_size(10); //태양의 Scale Set
Sun->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer); //Sun Model Init
Sun->MakePrimitive("Textures\\sun1024.jpg"); // mesh에 텍스쳐 이미지 추가
SunObj.AddComponent<DXMeshRenderer>()->SetModel(parentPrimitive.get());//Renderer component를 추가후 Sun Mesh를 저장
//지구공전(Sun_Earth_Obj)의 부모를 태양(SunObj)로 Set
Sun_Earth_Obj.GetTransform()->SetParent(SunObj.GetTransform());
//지구
Earth = std::make_unique<PrimitiveSphere>(); //mesh를 구 모양으로 초기화
Earth->Set_size(3); //지구의 scale Set
Earth->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);// Earth Model Init
Earth->MakePrimitive("Textures\\earth2048.bmp"); // mesh에 텍스쳐 이미지 추가
EarthObj.AddComponent<DXMeshRenderer>()->SetModel(Earth.get()); //Renderer component를 추가후 Earth Mesh를 저장
// 지구의 x축 좌표 이동(지구 공전 obj 기준으로 x축이동)
add_pos = { 25.0f,0,0 };
EarthObj.GetTransform()->AdjustPosition(add_pos);
//지구(EarthObj)의 부모를 지구공전(Sun_Earth_Obj)로 Set
EarthObj.GetTransform()->SetParent(Sun_Earth_node.GetTransform());
//달 공전(Earth_Moon_Obj)의 부모를 지구(EarthObj)로 Set
Earth_Moon_Obj.GetTransform()->SetParent(EarthObj.GetTransform());
//달
Moon = std::make_unique<PrimitiveSphere>(); // 구 모양 mesh
Moon->Set_size(1);
Moon->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);
Moon>MakePrimitive("Textures\\moon1024.bmp"); // mesh에 텍스쳐 이미지 추가
MoonObj.AddComponent<DXMeshRenderer>()->SetModel(childPrimitive.get());
// 달의 x축 좌표 이동(달 공전 obj 기준으로 x축이동)
add_pos = { 5.0f,0,0 };
MoonObj.GetTransform()->AdjustPosition(add_pos);
//달(MoonObj)의 부모를 지구공전(Earth_Moon_Obj)로 Set
MoonObj.GetTransform()->SetParent(Earth_Moon_Obj.GetTransform());
...
}
void DirectX11Graphics::RenderFrame()
{
...
//GameObject Primitive Mesh Draw
//world space에 배치된 obj를 뷰투영행렬을 연산해 카메라시점으로 변환
//인자값으로 카메라obj에 있는 뷰투영행렬을 Get
EarthObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
//====태양계 생성으로 인한 태양,달의 카메라시점 변환 =====//
SunObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
MoonObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
swapChain->Present(NULL, NULL);
}
'컴퓨터 그래픽스' 카테고리의 다른 글
[DirectX11] 절두체 컬링(Frustum Culing) (0) | 2023.06.21 |
---|---|
[DirectX11] D3D - Sprite2D ( 투영(Projection) 간단한 내용) (0) | 2023.06.17 |
그래픽스 파이프라인 간단 내용 정리 (0) | 2023.06.08 |
기본용어 정리 (0) | 2023.06.03 |