Computer Graphics/HumanGL

HumanGL - 6. Animation 적용

surkim 2024. 10. 23. 08:21

이제 사람 모델에 애니메이션 기능을 적용했다. 각 파트의 움직임에 맞춰 회전 축(피벗)을 설정하고, 애니메이션 동작을 적용한 것이 주요 변경 사항이다.

피벗 설정 및 Transform 계산 변경

사람의 몸을 그리는 과정에서 파츠마다 회전할 때 피벗 위치가 달라야 했다. 그래서 각 파트에 맞는 회전 중심을 계산해 적용했다.

DrawNode 함수 변경

void Human::DrawNode(const Node& node, const Mesh* mesh, const Program* program, const sglm::mat4& transform) {
    sglm::vec3 rotationPivot;
    if (node.part->name == "body") {
        rotationPivot = sglm::vec3(0.0f, 0.0f, 0.0f);
    } else if (node.part->name == "head") {
        rotationPivot = sglm::vec3(0.0f, -node.part->scale.y * node.part->size.y * 0.5f, 0.0f);
    } else if (node.part->name == "leftUpperArm" || node.part->name == "rightUpperArm") {
        rotationPivot = sglm::vec3(0.0f, node.part->scale.y * node.part->size.y * 0.9f * 0.5f, 0.0f);
    } else {
        rotationPivot = sglm::vec3(0.0f, node.part->scale.y * node.part->size.y * 0.5f, 0.0f);
    }

    sglm::mat4 pivotTransform = sglm::translate(sglm::mat4(1.0f), -1.0f * rotationPivot);
    sglm::mat4 pivotTransformBack = sglm::translate(sglm::mat4(1.0f), rotationPivot);
    sglm::mat4 translateModel = sglm::translate(sglm::mat4(1.0f), node.part->translate);
    sglm::mat4 rotateModel = sglm::rotate(sglm::mat4(1.0f), sglm::radians(node.part->rotate.x), sglm::vec3(1.0f, 0.0f, 0.0f));
    rotateModel = sglm::rotate(rotateModel, sglm::radians(node.part->rotate.y), sglm::vec3(0.0f, 1.0f, 0.0f));
    rotateModel = sglm::rotate(rotateModel, sglm::radians(node.part->rotate.z), sglm::vec3(0.0f, 0.0f, 1.0f));
    sglm::mat4 scaleModel = sglm::scale(sglm::mat4(1.0f), node.part->scale);
    sglm::mat4 sizeModel = sglm::scale(sglm::mat4(1.0f), node.part->size);
    sglm::mat4 childTransform = transform * translateModel * pivotTransformBack * rotateModel * pivotTransform * scaleModel;
    sglm::mat4 newTransform = childTransform * sizeModel;

    program->Use();
    program->SetUniform("objectColor", sglm::vec4(node.part->color.x, node.part->color.y, node.part->color.z, 1.0f));
    program->SetUniform("transform", newTransform);
    mesh->Draw(program);

    for (const auto& child : node.children) {
        DrawNode(child, mesh, program, childTransform);
    }
}

 

각 파츠의 회전 중심을 지정하기 위해 rotationPivot 변수를 사용했다. 예를 들어, 몸통(body)은 회전 중심이 (0.0f, 0.0f, 0.0f)로 설정되지만, 팔이나 머리 등은 상대적인 위치에 맞춰 피벗이 설정된다. 이렇게 피벗을 설정한 후, pivotTransformpivotTransformBack을 이용해 회전 변환을 적절히 적용했다.

회전은 피벗을 기준으로 하며, 회전 후 원래 위치로 되돌리는 작업을 통해 각 파츠의 회전이 다른 파츠에 영향을 주지 않도록 하였다.

애니메이션 동작 추가

Idle 애니메이션

0

Idle 함수는 사람 모델이 서 있는 상태에서 간단한 움직임을 주기 위해 작성되었다. 머리가 살짝 흔들리거나 팔이 가볍게 이동하는 정도로 구현했다.

void Human::Idle() {
    if (m_state != 1 && m_idleTime == 0) {
        return;
    }
    if (m_state == 1 && m_walkTime != 0) {
        return;
    }
    m_idleTime++;
    if (m_idleTime > 100) {
        m_idleTime = 0;
    }

    float headAngle = 5.0f * sin(sglm::radians(m_idleTime * 1.8f));
    m_head.rotate = sglm::vec3(headAngle - 1.0f, m_head.rotate.y, m_head.rotate.z);

    if (m_idleTime < 50) {
        m_head.translate = sglm::vec3(m_head.translate.x, 0.75f + m_idleTime * 0.0006f, m_head.translate.z);
        m_body.size = sglm::vec3((1.0f + m_idleTime * 0.001f), (1.0f + m_idleTime * 0.001f), m_body.size.z);
        m_leftUpperArm.translate = sglm::vec3(-0.65f - m_idleTime * 0.0006f, 0.15f + m_idleTime * 0.0001f, m_leftUpperArm.translate.z);
        m_rightUpperArm.translate = sglm::vec3(0.65f + m_idleTime * 0.0006f, 0.15f + m_idleTime * 0.0001f, m_rightUpperArm.translate.z);
    } else {
        m_head.translate = sglm::vec3(m_head.translate.x, 0.75f + (100 - m_idleTime) * 0.0006f, m_head.translate.z);
        m_body.size = sglm::vec3((1.0f + (100 - m_idleTime) * 0.001f), (1.0f + (100 - m_idleTime) * 0.001f), m_body.size.z);
        m_leftUpperArm.translate = sglm::vec3(-0.65f - (100 - m_idleTime) * 0.0006f, 0.15f + (100 - m_idleTime) * 0.0001f, m_leftUpperArm.translate.z);
        m_rightUpperArm.translate = sglm::vec3(0.65f + (100 - m_idleTime) * 0.0006f, 0.15f + (100 - m_idleTime) * 0.0001f, m_rightUpperArm.translate.z);
    }
}

Idle 함수는 사람의 머리를 살짝 흔들리게 하고, 팔과 몸통을 가볍게 움직인다. sin 함수를 이용해 부드러운 움직임을 구현했으며, 애니메이션이 자연스럽게 반복되도록 m_idleTime을 이용해 시간에 따른 변화량을 계산했다.

Walk 애니메이션

0

void Human::Walk() {
    if (m_state != 2 && m_walkTime == 0) {
        return;
    }
    m_walkTime++;
    if (m_walkTime > 200) {
        m_walkTime = 0;
    }

    float frontLegAngle = 30.0f * sin(sglm::radians(m_walkTime * 1.8f));
    float backLegAngle = 15.0f * sin(sglm::radians(m_walkTime * 1.8f));
    float bodyTranslate = sin(sglm::radians(m_walkTime * 3.6f)) * 0.05f;

    m_body.translate = sglm::vec3(m_body.translate.x, 0 + bodyTranslate, m_body.translate.z);

    float armAngle = 30.0f * sin(sglm::radians(m_walkTime * 1.8f));
    m_leftUpperArm.rotate = sglm::vec3(-armAngle, m_leftUpperArm.rotate.y, m_leftUpperArm.rotate.z);
    m_rightUpperArm.rotate = sglm::vec3(armAngle, m_rightUpperArm.rotate.y, m_rightUpperArm.rotate.z);

    if (m_walkTime < 100) {
        m_leftUpperLeg.rotate = sglm::vec3(frontLegAngle, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z);
        m_rightUpperLeg.rotate = sglm::vec3(-backLegAngle, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z);
    } else {
        m_leftUpperLeg.rotate = sglm::vec3(backLegAngle, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z); 
        m_rightUpperLeg.rotate = sglm::vec3(-frontLegAngle, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z); 
    }
}

설명

Walk 함수는 사람의 팔과 다리를 반대 방향으로 움직이며, 몸이 앞뒤로 약간 흔들리도록 했다. 이때도 sin 함수를 사용해 자연스러운 동작을 구현했다. 상하 흔들림을 추가하여 걷는 동작이 조금 더 현실적으로 보이도록 했다.

Jump 애니메이션

0

void Human::Jump() {
    if (m_state != 3 && m_jumpTime == 0) {
        return ;
    }
    if (m_walkTime != 0 || m_idleTime != 0) {
        return ;
    }
    m_jumpTime++;
    if (m_jumpTime > 400) {
        m_jumpTime = 0;
        m_state = 1;
        return ;
    }

    float velocity = 0.06f;
    float gravity = -0.0006f;
    float time = m_jumpTime - 100.0f;
    float jumpHeight = velocity * time + 0.5f * gravity * time * time;


    if (m_jumpTime < 100) {
        m_body.translate = sglm::vec3(m_body.translate.x, 0.0f - sin(sglm::radians(m_jumpTime * 0.9f)) * 0.5f, m_body.translate.z);
        m_body.rotate = sglm::vec3(- sin(sglm::radians(m_jumpTime * 0.9f)) * 45.0f, m_body.rotate.y, m_body.rotate.z);
        m_leftUpperArm.rotate = sglm::vec3(- sin(sglm::radians(m_jumpTime * 0.9f)) * 30.0f, m_leftUpperArm.rotate.y, m_leftUpperArm.rotate.z);
        m_rightUpperArm.rotate = sglm::vec3(- sin(sglm::radians(m_jumpTime * 0.9f)) * 30.0f, m_rightUpperArm.rotate.y, m_rightUpperArm.rotate.z);
        m_leftLowerArm.rotate = sglm::vec3(sin(sglm::radians(m_jumpTime * 0.9f)) * 20.0f, m_leftLowerArm.rotate.y, m_leftLowerArm.rotate.z);
        m_rightLowerArm.rotate = sglm::vec3(sin(sglm::radians(m_jumpTime * 0.9f)) * 20.0f, m_rightLowerArm.rotate.y, m_rightLowerArm.rotate.z);
        m_leftUpperLeg.rotate = sglm::vec3(sin(sglm::radians(m_jumpTime * 0.9f)) * 100.0f, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z);
        m_rightUpperLeg.rotate = sglm::vec3(sin(sglm::radians(m_jumpTime * 0.9f)) * 100.0f, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z);
        m_leftLowerLeg.rotate = sglm::vec3(- sin(sglm::radians(m_jumpTime * 0.9f)) * 90.0f, m_leftLowerLeg.rotate.y, m_leftLowerLeg.rotate.z);
        m_rightLowerLeg.rotate = sglm::vec3(- sin(sglm::radians(m_jumpTime * 0.9f)) * 90.0f, m_rightLowerLeg.rotate.y, m_rightLowerLeg.rotate.z);
    }
    else if (m_jumpTime < 200) {
        m_body.translate = sglm::vec3(m_body.translate.x, -0.5f + jumpHeight, m_body.translate.z);
        m_body.rotate = sglm::vec3(-45.0f + sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 45.0f, m_body.rotate.y, m_body.rotate.z);
        m_leftUpperArm.rotate = sglm::vec3(-30.0f + sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 210.0f, m_leftUpperArm.rotate.y, m_leftUpperArm.rotate.z);
        m_rightUpperArm.rotate = sglm::vec3(-30.0f + sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 210.0f, m_rightUpperArm.rotate.y, m_rightUpperArm.rotate.z);
        m_leftUpperLeg.rotate = sglm::vec3(100.0f - sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 100.0f, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z);
        m_rightUpperLeg.rotate = sglm::vec3(100.0f - sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 100.0f, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z);
        m_leftLowerLeg.rotate = sglm::vec3(-90.0f + sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 90.0f, m_leftLowerLeg.rotate.y, m_leftLowerLeg.rotate.z);
        m_rightLowerLeg.rotate = sglm::vec3(-90.0f + sin(sglm::radians((m_jumpTime - 100) * 0.9f)) * 90.0f, m_rightLowerLeg.rotate.y, m_rightLowerLeg.rotate.z);
    }
    else if (m_jumpTime < 300) {
        m_body.translate = sglm::vec3(m_body.translate.x, -0.5f + jumpHeight, m_body.translate.z);
        m_body.rotate = sglm::vec3(0.0f - sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 45.0f, m_body.rotate.y, m_body.rotate.z);
        m_leftUpperArm.rotate = sglm::vec3(180.0f - sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 210.0f, m_leftUpperArm.rotate.y, m_leftUpperArm.rotate.z);
        m_rightUpperArm.rotate = sglm::vec3(180.0f - sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 210.0f, m_rightUpperArm.rotate.y, m_rightUpperArm.rotate.z);
        m_leftUpperLeg.rotate = sglm::vec3(0.0f + sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 100.0f, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z);
        m_rightUpperLeg.rotate = sglm::vec3(0.0f + sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 100.0f, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z);
        m_leftLowerLeg.rotate = sglm::vec3(0.0f - sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 90.0f, m_leftLowerLeg.rotate.y, m_leftLowerLeg.rotate.z);
        m_rightLowerLeg.rotate = sglm::vec3(0.0f - sin(sglm::radians((m_jumpTime - 200) * 0.9f)) * 90.0f, m_rightLowerLeg.rotate.y, m_rightLowerLeg.rotate.z);
    }
    else {
        m_body.translate = sglm::vec3(m_body.translate.x, -0.5f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 0.5f, m_body.translate.z);
        m_body.rotate = sglm::vec3(-45.0f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 45.0f, m_body.rotate.y, m_body.rotate.z);
        m_leftUpperArm.rotate = sglm::vec3(-30.0f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 30.0f, m_leftUpperArm.rotate.y, m_leftUpperArm.rotate.z);
        m_rightUpperArm.rotate = sglm::vec3(-30.0f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 30.0f, m_rightUpperArm.rotate.y, m_rightUpperArm.rotate.z);
        m_leftLowerArm.rotate = sglm::vec3(20 - sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 20.0f, m_leftLowerArm.rotate.y, m_leftLowerArm.rotate.z);
        m_rightLowerArm.rotate = sglm::vec3(20 - sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 20.0f, m_rightLowerArm.rotate.y, m_rightLowerArm.rotate.z);
        m_leftUpperLeg.rotate = sglm::vec3(100.0f - sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 100.0f, m_leftUpperLeg.rotate.y, m_leftUpperLeg.rotate.z);
        m_rightUpperLeg.rotate = sglm::vec3(100.0f - sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 100.0f, m_rightUpperLeg.rotate.y, m_rightUpperLeg.rotate.z);
        m_leftLowerLeg.rotate = sglm::vec3(-90.0f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 90.0f, m_leftLowerLeg.rotate.y, m_leftLowerLeg.rotate.z);
        m_rightLowerLeg.rotate = sglm::vec3(-90.0f + sin(sglm::radians((m_jumpTime - 300) * 0.9f)) * 90.0f, m_rightLowerLeg.rotate.y, m_rightLowerLeg.rotate.z);
    }
}

Jump 함수는 점프하는 동작을 시간에 따라 단계적으로 구현했다.

  1. 도약 단계(0-100 프레임): 처음 100 프레임 동안 몸이 아래로 살짝 숙여진 후, 도약하면서 위로 상승한다. 이 과정에서는 sin 함수를 사용해 부드러운 움직임을 주었다.
  2. 공중 단계(100-300 프레임): 점프 후 공중에 떠 있는 동안 중력과 가속도를 반영한 수식으로 높이를 조절한다.
    • velocitygravity 변수를 통해 시간 경과에 따른 위치 변화를 계산해 자연스러운 체공을 구현했다.
  3. 착지 단계(300-400 프레임): 착지할 때는 다시 sin 함수를 사용해 부드럽게 지면에 닿도록 하였고, 착지 시 몸의 회전 각도나 팔, 다리의 자세 변화도 추가하여 더 사실적인 효과를 구현했다.

'Computer Graphics > HumanGL' 카테고리의 다른 글

HumanGL - 7. ImGui 추가  (1) 2024.10.23
HumanGL - 5. transform 행렬 계산식 변경  (0) 2024.10.18
HumanGL - 4. 문제 발견  (0) 2024.10.17
HumanGL - 3. 사람 그리기  (0) 2024.10.17
HumanGL - 2. Scale 적용 해보기  (1) 2024.10.17