이번에는 SCOP 프로젝트에서 배운 내용을 바탕으로 계층적 모델링과 변환 행렬 스택을 적용하여 Human Model을 구현해보았다.
Human Model을 구현하면서 고려해야 할 주요 사항은 다음과 같았다:
이러한 요구사항을 충족하기 위해 다음과 같은 클래스를 설계했다.
Mesh 클래스는 1 x 1 x 1 크기의 Box를 그리기 위한 간단한 기능만을 제공한다.
# include "vertexArray.h"
# include "buffer.h"
struct Vertex {
glmath::vec3 pos;
glmath::vec2 texCoord;
glmath::vec3 normal;
};
class Mesh {
public:
static std::unique_ptr<Mesh> createBox();
void draw();
private:
Mesh() = default;
void init(std::vector<Vertex>& vertices, std::vector<uint32_t>& indices);
std::unique_ptr<VertexArray> m_vertexArray;
std::shared_ptr<Buffer> m_vertexBuffer;
std::shared_ptr<Buffer> m_elementBuffer;
GLsizei m_elementSize;
};
#endif
Model 클래스는 Human Model의 각 파트를 나타낸다. 주요 특징은 다음과 같다:
#ifndef MODEL_H
# define MODEL_H
# include "mesh.h"
# include "program.h"
# include <cmath>
# include <algorithm>
# include <map>
# include <string>
# include <stack>
enum class ePart
{
BODY,
HEAD,
LEFT_UPPER_ARM,
LEFT_LOWER_ARM,
RIGHT_UPPER_ARM,
RIGHT_LOWER_ARM,
LEFT_UPPER_LEG,
LEFT_LOWER_LEG,
RIGHT_UPPER_LEG,
RIGHT_LOWER_LEG,
NONE,
};
struct PartInfo {
std::string name; // 파트 이름
glmath::vec3 position; // 각 파트의 상대 위치
glmath::vec3 translation; // 각 파트의 이동
glmath::vec3 rotateTranslation; // 각 파트의 회전축으로의 이동
glmath::vec3 eulerAngle; // 각 파트의 회전 (x, y, z 축의 각도)
glmath::vec3 scale; // 각 파트의 크기
glmath::vec3 color; // 각 파트의 색상
};
class Model {
public:
static std::unique_ptr<Model> createHuman(ePart part = ePart::BODY);
static std::stack<glmath::mat4> s_stack;
void draw(Program* program);
private:
void createMesh(ePart part);
bool createChildren(std::vector<ePart> parts);
PartInfo getPartInfo(ePart part);
std::vector<ePart> getPartChildrenInfo(ePart part);
std::vector<std::unique_ptr<Model>> m_children;
std::unique_ptr<Mesh> m_mesh;
PartInfo m_partInfo;
};
#endif
static std::unique_ptr<Model> createHuman(ePart part = ePart::BODY);
Human Model을 계층적으로 생성하는 핵심 함수이다. ePart 파라미터로 각 파트를 지정하고, 그에 맞는 createMesh() 함수와 createChildren() 함수를 실행한다.
std::unique_ptr<Model> Model::createHuman(ePart part) {
std::unique_ptr<Model> model(new Model());
// 1단계
model->createMesh(part);
// 2단계
if (!model->createChildren(model->getPartChildrenInfo(part))) {
return nullptr;
}
std::cout << "Part " << model->m_partInfo.name << " is complete!\n";
return model;
}
1단계 createMesh() 함수
void Model::createMesh(ePart part) {
m_mesh = Mesh::createBox();
m_partInfo = getPartInfo(part);
}
PartInfo Model::getPartInfo(ePart part) {
static std::map<ePart, PartInfo> partInfoMap = {
{ ePart::BODY, {"BODY", glmath::vec3(0.0f, 0.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f), glmath::vec3(2.0f, 4.0f, 1.0f), glmath::vec3(1.0f, 0.5f, 0.3f) } }, // 몸통
{ ePart::HEAD, {"HEAD", glmath::vec3(0.0f, 2.5f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f), glmath::vec3(0.95f, 0.80f, 0.72f) } }, // 머리
{ ePart::LEFT_UPPER_ARM, {"LU_ARM", glmath::vec3(-1.5f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.95f, 0.80f, 0.72f) } }, // 왼쪽 상부 팔
{ ePart::LEFT_LOWER_ARM, {"LL_ARM", glmath::vec3(0.0f, -2.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.95f, 0.80f, 0.72f) } }, // 왼쪽 하부 팔
{ ePart::RIGHT_UPPER_ARM, {"RU_ARM", glmath::vec3(1.5f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.95f, 0.80f, 0.72f) } }, // 오른쪽 상부 팔
{ ePart::RIGHT_LOWER_ARM, {"RL_ARM", glmath::vec3(0.0f, -2.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.95f, 0.80f, 0.72f) } }, // 오른쪽 하부 팔
{ ePart::LEFT_UPPER_LEG, {"LU_LEG", glmath::vec3(-0.5f, -3.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.3f, 1.0f, 0.3f) } }, // 왼쪽 상부 다리
{ ePart::LEFT_LOWER_LEG, {"LL_LEG", glmath::vec3(0.0f, -2.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.3f, 1.0f, 0.3f) } }, // 왼쪽 하부 다리
{ ePart::RIGHT_UPPER_LEG, {"RU_LEG", glmath::vec3(0.5f, -3.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.3f, 1.0f, 0.3f) } }, // 오른쪽 상부 다리
{ ePart::RIGHT_LOWER_LEG, {"RL_LEG", glmath::vec3(0.0f, -2.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(0.0f, 1.0f, 0.0f), glmath::vec3(0.0f), glmath::vec3(1.0f, 2.0f, 1.0f), glmath::vec3(0.3f, 1.0f, 0.3f) } } // 오른쪽 하부 다리
};
return partInfoMap.at(part);
}
2단계 createChildren() 함수
bool Model::createChildren(std::vector<ePart> parts) {
for (ePart part : parts) {
if (part == ePart::NONE) {
continue;
}
if (std::unique_ptr<Model> child = createHuman(part)) {
m_children.push_back(std::move(child));
} else {
return false;
}
}
return true;
}
std::vector<ePart> Model::getPartChildrenInfo(ePart part) {
static std::map<ePart, std::vector<ePart>> partChildrenInfo = {
{ ePart::BODY, { ePart::HEAD, ePart::LEFT_UPPER_ARM, ePart::LEFT_UPPER_LEG, ePart::RIGHT_UPPER_ARM, ePart::RIGHT_UPPER_LEG } },
{ ePart::HEAD, { ePart::NONE } },
{ ePart::LEFT_UPPER_ARM, { ePart::LEFT_LOWER_ARM } },
{ ePart::LEFT_UPPER_LEG, { ePart::LEFT_LOWER_LEG } },
{ ePart::RIGHT_UPPER_ARM, { ePart::RIGHT_LOWER_ARM } },
{ ePart::RIGHT_UPPER_LEG, { ePart::RIGHT_LOWER_LEG } },
{ ePart::LEFT_LOWER_ARM, { ePart::NONE } },
{ ePart::LEFT_LOWER_LEG, { ePart::NONE } },
{ ePart::RIGHT_LOWER_ARM, { ePart::NONE } },
{ ePart::RIGHT_LOWER_LEG, { ePart::NONE } },
};
return partChildrenInfo.at(part);
}
변환 행렬 스택을 사용하여 부모 파트의 변환을 자식에게도 적용하며 그리는 과정이다.
1. 변환 행렬 계산
glmath::mat4& parentsTransform = s_stack.top();
glmath::mat4 childTransform = glmath::translate(glmath::mat4(1.0f), m_partInfo.position) *
glmath::translate(glmath::mat4(1.0f), m_partInfo.translation) *
glmath::translate(glmath::mat4(1.0f), m_partInfo.rotateTranslation) *
glmath::mat4_cast(glmath::quat(m_partInfo.eulerAngle)) *
glmath::translate(glmath::mat4(1.0f), -1 * m_partInfo.rotateTranslation);
s_stack.push(parentsTransform * childTransform);
childTransform은 적용되는 순서대로 다음과 변환들이 곱해진다.
1. 회전 중심축으로 이동
2. part 고유의 rotatation
3. 다시 원위치로 이동
4. part 고유의 translation
5. 부모의 위치에 대한 상대적인 위치로 이동
2. 스케일 변환 제외
program->setUniform("color", m_partInfo.color);
program->setUniform("transform", s_stack.top() * glmath::scale(glmath::mat4(1.0f), m_partInfo.scale));
m_mesh->draw();
3. 계층적 그리기
현재 part를 다 그렸으면 이제 stack에 현재 part의 변환이 추가된 상태로 자식들의 draw를 호출한다.
그리고 자식들의 draw호출도 종료됐다면 현재 part의 변환 정보를 stack에서 pop()한다.
for (std::unique_ptr<Model>& child : m_children) {
child->draw(program);
}
s_stack.pop();
계층적 구조에 맞게 Human Model을 성공적으로 구현한 후, PartInfo에 저장된 색상 정보를 이용해 각 파트를 색칠하여 그린 결과는 다음과 같다.
각 파트가 계층적으로 잘 렌더링된 것을 확인할 수 있다.
다음 단계로는 마우스와 키보드 입력을 받아 카메라 제어 기능을 구현할 계획이다. 이를 통해 Human Model을 다양한 각도에서 조작하고 관찰할 수 있는 기능을 추가할 것이다.
HumanGL: Human Model에 애니메이션을 적용하기 위한 준비 - (6) (2) | 2024.10.19 |
---|---|
HumanGL: 카메라 제어 - (5) (3) | 2024.10.14 |
HumanGL: 계층적 모델링과 변환 행렬 스택 - (3) (1) | 2024.10.09 |
HumanGL: 프로젝트 진행 계획 - (2) (1) | 2024.10.09 |
HumanGL: 계층적 모델링과 애니메이션 - (1) (2) | 2024.10.09 |