본문으로 바로가기

7장 연습문제 7.1

문제

7.2장을 참고하여서 도플러 효과를 적용한 게임프로젝트를 수정하자.

  • 조건1: 도플러 효과를 적용하기 위해서 리스너와 이벤트 인스턴스 속성 코드를 수정하자.
  • 조건2: 도플러 효과를 테스트하기 위해 Game::LoadData에서 생성된 구 액터를 앞뒤로 빠르게 이동시키자.
  • 조건3: set3DSettings를 활용해서 도플러 효과의 세기를 조절하자

풀이

이번 문제에서 요구하는 기능을 구현하기 위해서 움직이는 구체를 별도의 클래스로 구현하였다. 그리고 이 구체에 도플러효과를 적용하기 위해서는 이 글에 나와있는 것 처럼 setListenerAttributesset3DAttributes에 올바른 속도값을 넣어야 한다. 그리고 set3DSettings를 통해서 도플러 효과의 세기를 조정하였다. 아래의 시연영상에서 단계별 구현결과를 확인 할 수 있다.

Exercise_7_1 전체 소스코드

구현 결과

1단계 움직이는 구체 구현

움직이는 구체의 기능이 다소 복잡하기 때문에 Game::Load() 함수로부터 분리하여 별도의 SoundObject 클래스로 구현하였다. SoundObjectActor를 상속받으며 생성자와 UpdateActor로만 이루어진 간단한 클래스이다. SoundObject는 다음과 같은 특징을 가진다.

  • Sphere 메시를 가진다.
  • event:/FireLoop 이벤트를 반복 재생한다..
  • 3.0초마다 이동 방향이 반전되며 앞뒤로 반복운동 한다.
SoundObject::SoundObject(class Game* game)
    :Actor(game)
    , duration(3.f)
    , speed(-300.f)
{
    SetPosition(Vector3(500.f, -75.f, 0.f));
    SetScale(1.f);

    MeshComponent* msc;
    msc = new MeshComponent(this);
    msc->SetMesh(GetGame()->GetRenderer()->GetMesh("../Assets/Sphere.gpmesh"));

    AudioComponent* ac = new AudioComponent(this);
    ac->PlayEvent("event:/FireLoop");

    mvc = new MoveComponent(this);
    mvc->SetForwardSpeed(speed);
}

void SoundObject::UpdateActor(float deltaTime)
{
    duration -= deltaTime;
    // 3.0초 마다 진행 방향이 반전된다.
    if (duration < 0.f)
    {
        duration = 3.f;
        speed = -1 * speed;
        mvc->SetForwardSpeed(speed);
    }
}

아직까지 도플러 효과가 적용되지 않았기 때문에 거리에 따른 감쇠효과는 나타나지만 속도에 따른 피치의 변화는 나타나지 않는다.

2단계 올바른 속도값 입력

구체에 도플러효과를 적용하기 위해서는 이 글에 나와있는 것 처럼 setListenerAttributesset3DAttributes에 올바른 속도값을 넣어야 한다. 그래서 setListenerAttributesset3DAttributesfloat 타입의 매개변수 velocity를 추가하자.

AudioSystem.cpp에서 AudioSystem::SetListener() 함수 구현

void AudioSystem::SetListener(const Matrix4& viewMatrix, const Vector3& velocity)
{
    // 카메라의 위치는 뷰 행렬의 역행렬에서 구할 수 있다.
    Matrix4 invView = viewMatrix;
    invView.Invert();
    FMOD_3D_ATTRIBUTES listener;
    // 리스너의 위치와 전방 벡터, 상향 벡터를 설정
    listener.position = VecToFMOD(invView.GetTranslation());
    // 뷰 행렬의 역행렬에서 세 번째 행은 전방 벡터
    listener.forward = VecToFMOD(invView.GetZAxis());
    // 뷰 행렬의 역행렬에서 두 번째 행은 상향 벡터
    listener.up = VecToFMOD(invView.GetYAxis());
    // 리스너의 속도 설정. 도플러 효과를 연산하기 위해 필요
    listener.velocity = VecToFMOD(velocity);
    // FMOD로 보낸다 (0 = 리스너는 하나)
    mSystem->setListenerAttributes(0, &listener);
}

SoundEvent.cpp에서 SoundEvent::Set3DAttributes() 함수 구현

void SoundEvent::Set3DAttributes(const Matrix4& worldTrans, const Vector3& velocity)
{
    auto event = mSystem ? mSystem->GetEventInstance(mID) : nullptr;
    if (event)
    {
        FMOD_3D_ATTRIBUTES attr;
        // Set position, forward, up
        attr.position = VecToFMOD(worldTrans.GetTranslation());
        // 세계 공간에서 첫 번째 행은 전방 벡터
        attr.forward = VecToFMOD(worldTrans.GetXAxis());
        // 세 번째 행은 상향 벡터
        attr.up = VecToFMOD(worldTrans.GetZAxis());
        // 사운드 이벤트가 발생할 엑터의 속도 설정
        // 도플러 효과를 연산하기 위해 필요
        attr.velocity = VecToFMOD(velocity);
        event->set3DAttributes(&attr);
    }
}

SetListener()는 기본 리스너로 지정해둔 카메라의 UpdateActor()함수에서 리스너(카메라)의 속도를 계산해서 호출하면 리스너의 속도를 전달 할 수 있다. 엑터의 속도를 수정하기 위해서는 Actor 클래스 일부를 수정해야 하는데 이는 전체 소스코드를 참고하자.

CameraActor.cpp에서 void CameraActor::UpdateActor()

void CameraActor::UpdateActor(float deltaTime)
{
    Actor::UpdateActor(deltaTime);

    // ... (중략)

    GetGame()->GetRenderer()->SetViewMatrix(view);
    // audioSystem에서 리스너 설정 갱신
    GetGame()->GetAudioSystem()->SetListener(view, mForwardSpeed * GetForward());
}

Set3DAttributes()AudioComponent.cppOnUpdateWorldTransform() 함수와 PlayEvent() 함수에서 속도를 계산하도록 수정하면 된다.

void AudioComponent::OnUpdateWorldTransform()
{
    Matrix4 world = mOwner->GetWorldTransform();
    for (auto& event : mEvents3D)
    {
        if (event.IsValid())
        {
            // 엑터의 속도를 계산하여 전달하도록 수정
            event.Set3DAttributes(world, mOwner->GetForward() * mOwner->GetForwardSpeed());
        }
    }
}

SoundEvent AudioComponent::PlayEvent(const std::string& name)
{
    SoundEvent e = mOwner->GetGame()->GetAudioSystem()->PlayEvent(name);
    // 이벤트의 3D 여부 확인
    if (e.Is3D())
    {
        mEvents3D.emplace_back(e);
        // 엑터의 속도를 계산하여 전달하도록 수정
        e.Set3DAttributes(mOwner->GetWorldTransform(), mOwner->GetForward() * mOwner->GetForwardSpeed());
    }
    else
    {
        mEvents2D.emplace_back(e);
    }

    return e;
}

3단계 set3DSettings 함수로 Distance factor 조정

게임 속 1.0f의 거리를 현실 세계의 1m에 어떻게 적용하는지에 따라서 도플러 효과에 큰 영향을 주게 된다. 이 비율을 설정하는 요소가 Distance factor이고 이 Distance factor는 set3DSettings함수를 통해서 설정할 수 있다. Distance factor로 현실속 거리를 계산하는 방법은 다음과 같다.

\[현실 거리 = \frac{게임 속 거리}{Distance factor}\]

FMOD에서 Distance factor의 기본 값은 1이다. 그래서 Distance factor를 위와 같이 별도 설정없이 사용할 경우 게임 속도가 300.0f/s인 구체가 현실 속도 300m/s로 적용되어 이동하는 것으로 간주하게 된다. 때문에 시연영상에서 음속을 돌파했을 때나 들을 수 있는 소리들이 났던 것이다.
이를 현실적은 수치로 조정해줄 필요가 있다. 이때 사용하는 것이 set3DSettings 함수이다. 이 함수는 로우레벨에서 접근 하기 때문에 아래와 같이 AudioSystem::Initialize() 함수에서 mLowLevelSystem객체로 접근할 수 있다.

bool AudioSystem::Initialize(){
    // ... (생략)

    // 저수준의 시스템 포인터를 얻어와서 mLowLevelSyste에 저장한다.
    result = mSystem->getLowLevelSystem(&mLowLevelSystem);
    if (result != FMOD_OK)
    {
        SDL_Log("Failed to create FMOD system %s", FMOD_ErrorString(result));
        return false;
    }

    mLowLevelSystem->set3DSettings(
        1.f,    // 도플러 스케일, 1 = 정상, 1보다 더 크면 과장된 소리를 낸다
        50.f,   // 게임 단위의 크기 = 1미터 (7장 프로젝트는 50이 적당함)
        1.f     // (도플러와 관계없음, 1로 남겨둔다)
    );

    // ... (생략)
}

이렇게 Distance factor를 50.0f로 설정하게 되면 아래와 같이 현실 속도는 6m/s로 적용된다

\[6 = \frac{300.0}{50.0}\]

이상으로 도플러 효과 구현설명을 마친다.