상세 컨텐츠

본문 제목

FT_Newton: sleep 상태 추가 및 solve constraint 반복 수정 - (13)

Computer Graphics/ALEngine

by Banjosh 2025. 2. 12. 02:50

본문

 이번에 물리 엔진을 메모리 최적화하면서 성능이 확 올라갔는데, 이게 오히려 새로운 문제를 만들었다.

 

 성능이 좋아지다 보니 duration(물리 연산에 사용하는 시간 단위)을 크게 설정했는데, 7 x 7 x 7의 박스 탑에서 이상한 현상이 벌어졌다. 

 

 duration이 커서 중력 영향을 크게 받게 되니까, 박스들이 서로 요동치면서 건드리지도 않았는데 스스로 무너져버렸다.

나는 이 문제를 정지 접촉(Rest Contact) 쪽 이슈라고 판단했고, 그래서 Sleep 상태 Awake 상태를 추가해서 해결을 시도했다.

 


1. Sleep 상태

먼저 setSleep() 함수를 살펴보면:

void Rigidbody::setSleep(float duration)
{
	if (m_canSleep)
	{
		m_sleepTime += duration;
		if (m_sleepTime > START_SLEEP_TIME)
		{
			m_isAwake = false;
		}
	}
}

 

이렇게 setSleep()이 호출될 때마다 sleepTime이 누적되고, START_SLEEP_TIME을 넘어서면 Sleep 상태로 전환된다. Sleep 상태가 된 물체는 물리 연산을 전혀 하지 않고, 충돌의 주체로도 간주되지 않는다. (단, 다른 오브젝트가 이 오브젝트에 충돌해 들어오는 건 인정)

그렇다면 setSleep()은 언제 호출될까? 조건은 아래와 같다.

  1. 정지 접촉
  2. 임계값 이하의 선속도
  3. 임계값 이하의 각속도

이 세 가지 상태가 START_SLEEP_TIME 동안 유지되면 Sleep 상태가 되는 것이다.

 


2. Awake 상태

Awake 상태는 말 그대로 Sleep을 하지 않는 상태다. 물리 연산 대상이 되고, 충돌의 주체가 될 수 있다. 위에서 말한 setSleep() 조건을 하나라도 만족하지 못하면 setAwake()가 호출된다.

void Rigidbody::setAwake()
{
	m_sleepTime = 0.0f;
	m_isAwake = true;
}

 

호출되면 sleepTime은 0으로 초기화되고 Awake 상태가 된다.


3. 정지 접촉(Rest Contact)

정지 접촉이란, 두 물체의 접촉을 충돌이 아니라 단순히 붙어 있는 정지 상태로 보는 것이다. 조건은 다음과 같다.

  1. 충돌의 상대 선속도가 임계값 이하
  2. 충돌의 상대 각속도가 임계값 이하
  3. 충돌 방향이 중력방향과 유사해, 공중에 떠 있는 상태가 아닌 경우

 3번이 약간 헷갈릴 수 있는데, 이 조건이 없다면 한 줄로 쌓여 있는 상자들 중 맨 위에 있는 상자 옆에 정지 접촉을 이용하여 다른 상자를 붙일 수 있게 된다. 그러면 추가한 상자 아래엔 아무것도 없는데도 "옆의 상자와 정지 접촉을 유지한다"는 이유만으로 물리 연산에서 빠져서 중력도 안 받게 되는 결과가 발생한다. 그렇기 때문에 3번 조건을 둬서, 중력 방향으로 충분히 지지받는 상태인지 판별해야 한다.

 

 

 


Sleep과 Awake를 추가한 결과

 

 이렇게 Sleep과 Awake를 추가해주면, 초기에는 Sleep 상태라서 박스들이 서로 안정적으로 보인다. 그러나 공으로 박스들을 건드리는 순간 박스들이 요동치기 시작한다.

 처음엔 "Sleep 처리를 했는데도 박스가 이상하게 무너지는 걸 보면, 다른 데 문제가 있나?" 싶어서 이것저것 테스트해봤더니, velocity constraint solveposition constraint solve의 반복 처리를 잘못하고 있었다.


N번 solve 반복 시 올바른 속도/위치 업데이트 타이밍은?

충돌을 N번 solve로 반복 처리할 때, 속도나 위치를 어느 타이밍에 업데이트하는 게 맞을까?

  1. 하나의 충돌 내 접촉점 1개를 처리할 때마다
  2. 하나의 충돌을 처리할 때마다
  3. 전체 충돌을 한 번 순회 처리할 때마다

내 생각엔 2번이 올바른 방법이다.

  • 1번으로 처리해보니 같은 충돌 내의 다른 접촉점 처리에 영향을 줘서 부정확해졌고,
  • 3번으로 처리해보니 충돌들 간의 영향이 너무 늦게 반영돼서 부자연스러웠다.

 물리 엔진은 자연스럽게 수렴하는 게 핵심이기 때문에, 2번 타이밍마다 속도/위치를 업데이트해서 충돌 내 접촉점끼리는 같은 정보를 쓰고, 하나의 충돌 처리가 끝날 때마다 결과를 다른 충돌에도 반영되도록 만들어줘야 된다.


수정 후 결과

 

 속도, 위치 업데이트 타이밍을 바꿔주니까 확실히 충돌 처리가 자연스러워졌다.

 

 결국 Sleep 상태와 Awake 상태 도입 + Constraint Solve 반복 처리 시점 수정 이 두 가지로 문제가 해결됐다. 사실 Sleep 처리를 추가했는데도 문제가 계속 나타났을 때 정말 당황했고, 해결책을 찾기까지 삽질을 많이 하면서 많이 고생했다. 그래도 어떻게든 해결한 것을 보니, 무엇이든 열심히 하다 보면 어떻게든 해결이 되는게 맞는 것 같다.

 

 다음엔 좀 더 빠르게 문제 해결에 도달할 수 있도록, 물리 엔진의 내부 로직과 수렴 방식에 대해 더 깊이 공부해야겠다.

 

관련글 더보기