7장 연습문제 7.1
문제
7.2장을 참고하여서 도플러 효과를 적용한 게임프로젝트를 수정하자.
- 조건1: 도플러 효과를 적용하기 위해서 리스너와 이벤트 인스턴스 속성 코드를 수정하자.
- 조건2: 도플러 효과를 테스트하기 위해
Game::LoadData
에서 생성된 구 액터를 앞뒤로 빠르게 이동시키자. - 조건3:
set3DSettings
를 활용해서 도플러 효과의 세기를 조절하자
풀이
이번 문제에서 요구하는 기능을 구현하기 위해서 움직이는 구체를 별도의 클래스로 구현하였다. 그리고 이 구체에 도플러효과를 적용하기 위해서는 이 글에 나와있는 것 처럼 setListenerAttributes
와 set3DAttributes
에 올바른 속도값을 넣어야 한다. 그리고 set3DSettings
를 통해서 도플러 효과의 세기를 조정하였다. 아래의 시연영상에서 단계별 구현결과를 확인 할 수 있다.
구현 결과
1단계 움직이는 구체 구현
움직이는 구체의 기능이 다소 복잡하기 때문에 Game::Load()
함수로부터 분리하여 별도의 SoundObject
클래스로 구현하였다. SoundObject
는 Actor
를 상속받으며 생성자와 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단계 올바른 속도값 입력
구체에 도플러효과를 적용하기 위해서는 이 글에 나와있는 것 처럼 setListenerAttributes
와 set3DAttributes
에 올바른 속도값을 넣어야 한다. 그래서 setListenerAttributes
와 set3DAttributes
에 float
타입의 매개변수 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.cpp
의 OnUpdateWorldTransform()
함수와 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}\]
이상으로 도플러 효과 구현설명을 마친다.