두 게임 상태를 보간하는 방법?


24

모든 객체 위치가 두 업데이트 상태 사이에서 보간되는 시스템을 만드는 가장 좋은 패턴은 무엇입니까?

업데이트는 항상 같은 빈도로 실행되지만 모든 FPS에서 렌더링 할 수 있기를 원합니다. 따라서 렌더링 빈도는 업데이트 빈도보다 낮거나 높은 초당 프레임 수에 관계없이 최대한 매끄 럽습니다.

현재 프레임에서 미래 프레임으로 보간하기 위해 1 프레임을 미래 보간으로 업데이트하고 싶습니다. 이 답변에는 다음과 같은 링크가 있습니다.

반 고정 또는 완전 고정 타임 스텝?

편집 : 보간에서 마지막 속도와 현재 속도를 어떻게 사용할 수 있습니까? 예를 들어 선형 보간 만 사용하면 위치간에 동일한 속도로 이동합니다. 두 점 사이의 위치를 ​​보간하는 방법이 필요하지만 보간을 위해 각 점의 속도를 고려하십시오. 파티클 효과와 같은 저속 시뮬레이션에 유용합니다.


2
진드기 논리 진드기? 그래서 업데이트 fps <렌더링 fps?
공산주의 오리

용어를 변경했습니다. 그러나 그렇습니다. 논리. 그리고 아니요, 렌더링에서 업데이트를 완전히 해제하고 싶기 때문에 게임이 120HZ 또는 22.8HZ에서 렌더링 될 수 있으며 사용자가 시스템 요구 사항을 충족하는 경우 업데이트는 여전히 동일한 속도로 실행됩니다.
AttackingHobo

렌더링하는 동안 모든 객체 위치를 그대로 유지해야하므로 렌더링이 까다로울 수 있습니다 (렌더링 프로세스 중에 변경하면 정의되지 않은 동작이 발생할 수 있음)
Ali1S232

보간은 이미 계산 된 2 개의 업데이트 프레임 사이의 시간에 상태를 계산합니다. 외삽에 관한이 질문이 아니고 마지막 업데이트 프레임 이후 의 시간 동안 상태를 계산 합니까? 다음 업데이트는 아직 계산되지 않았기 때문에.
Maik Semder

스레드 업데이트 / 렌더링이 하나 뿐인 경우 렌더링 위치 만 다시 업데이트 할 수 없다고 생각합니다. 위치를 GPU로 보낸 다음 다시 업데이트하면됩니다.
zacharmarz

답변:


22

업데이트 (논리 틱)와 드로우 (렌더 틱) 비율을 분리하려고합니다.

업데이트하면 전 세계의 모든 객체의 위치가 그려집니다.

여기서는 여러분이 요청한 것, 외삽 법, 그리고 또 다른 방법 인 보간법의 두 가지 다른 가능성을 다루겠습니다.

1.

외삽 은 다음 프레임에서 객체의 (예측 된) 위치를 계산 한 다음 현재 객체 위치와 객체가 다음 프레임에 위치 할 위치 사이를 보간하는 위치입니다.

이렇게하려면 그려 각 개체는이 연결되어 있어야 velocity하고 position. 객체가 다음 프레임에 위치 할 위치를 찾으려면 velocity * draw_timestep객체의 현재 위치에 추가 하여 다음 프레임의 예상 위치를 찾으십시오. draw_timestep이전 렌더 틱 (일명 이전 그리기 호출) 이후 경과 된 시간입니다.

이것을 그대로두면, 예측 된 위치가 다음 프레임의 실제 위치와 일치하지 않을 때 객체가 "깜박 거린다"는 것을 알 수 있습니다. 깜박임을 제거하려면 예측 위치 및 저장할 수 있습니다 LERP LERP 요소로 이전 업데이트 틱 이후 경과 된 시간을 사용하여 이전에 예측 위치 및 각 무승부 단계에서 새로운 예측 위치 사이를. 이렇게하면 빠르게 움직이는 객체가 갑자기 위치를 변경하는 경우 여전히 동작이 좋지 않으며 해당 특수한 경우를 처리 할 수 ​​있습니다. 이 단락에서 언급 한 모든 내용은 외삽 법을 사용 하지 않는 이유 입니다.

2.

보간 은 마지막 두 업데이트의 상태를 저장하고 마지막 이전 업데이트 이후로 경과 한 현재 시간을 기반으로 두 업데이트 사이를 보간하는 위치입니다. 이 설정에서 각 객체는 관련되어 있어야합니다position 하고 previous_position. 이 경우, 우리의 그림은 현재 게임 상태보다 최악의 업데이트 틱을 나타내고, 현재 업데이트 틱과 정확히 같은 상태를 나타냅니다.


내 의견으로는, 구현하기가 더 쉽고, 현재 업데이트 된 상태 뒤에 1 초 (예 : 1/60 초)의 작은 부분을 그리는 것이 좋기 때문에 내가 설명한대로 보간을 원할 것입니다.


편집하다:

위의 방법으로 구현을 수행하기에 충분하지 않은 경우 여기에 설명 된 보간 방법을 수행하는 방법의 예가 있습니다. 외삽 법은 다루지 않겠습니다. 왜냐하면 여러분이 선호하는 실제 시나리오는 생각할 수 없기 때문입니다.

드로어 블 개체를 만들면 그리기에 필요한 속성 ( 그리기에 필요한 상태 정보)이 저장됩니다.

이 예에서는 위치와 회전을 저장합니다. 색상 또는 질감 좌표 위치와 같은 다른 속성을 저장하고 싶을 수도 있습니다 (예 : 질감이 스크롤되는 경우).

렌더 스레드가 데이터를 그리는 동안 데이터가 수정되는 것을 방지하려면 (예 : 렌더 스레드가 그리는 동안 한 객체의 위치가 변경되지만 다른 객체는 모두 아직 업데이트되지 않은 경우) 일부 유형의 이중 버퍼링을 구현해야합니다.

객체는 두 개의 사본을 저장합니다 previous_state. 나는 배열에 넣어와로 참조됩니다 previous_state[0]previous_state[1]. 마찬가지로 두 개의 사본이 필요합니다 current_state.

이중 버퍼의 어느 복사본이 사용되는지 추적하기 위해 state_index업데이트 및 그리기 스레드 모두에 사용할 수 있는 변수를 저장합니다 .

업데이트 스레드는 먼저 자체 데이터 (원하는 데이터 구조)를 사용하여 개체의 모든 속성을 계산합니다. 그리고, 사본 current_state[state_index]previous_state[state_index], 그림에 대한 새로운 데이터 관련, 복사 positionrotationcurrent_state[state_index]. 그런 다음 state_index = 1 - state_index현재 사용되는 이중 버퍼 사본을 뒤집습니다.

위 단락의 모든 내용은 잠금 장치를 사용하여 수행해야합니다. current_state . 업데이트 및 그리기 스레드는 모두이 잠금을 해제합니다. 상태 정보를 복사하는 동안에 만 잠금이 해제됩니다.

그런 다음 렌더 스레드에서 다음과 같이 위치 및 회전에 대한 선형 보간을 수행합니다.

current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)

elapsed마지막 업데이트 틱 이후 렌더 스레드에서 경과 한 시간은 어디 이며 update_tick_length고정 업데이트 속도가 틱당 소요되는 시간입니다 (예 : 20FPS 업데이트 update_tick_length = 0.05).

Lerp위 의 기능 이 무엇인지 모르는 경우 주제에 대한 위키 백과의 기사를 확인하십시오 : 선형 보간 . 그러나 lerping이 무엇인지 모른다면 보간 된 도면으로 분리 된 업데이트 / 도면을 구현할 준비가되지 않았을 것입니다.


1
+1 방향 / 회전 및 시간에 따라 변하는 다른 모든 상태, 예를 들어 파티클 시스템 등의 머티리얼 애니메이션에 대해서도 동일하게 수행해야합니다.
Maik Semder

1
좋은 지적 Maik, 방금 예로 위치를 사용했습니다. 외삽 법을 사용하려는 경우 외삽하려는 특성 (예 : 해당 특성의 시간에 따른 변화율)의 "속도"를 저장해야합니다. 결국, 나는 외삽 법이 내삽 법보다 나은 상황을 실제로 생각할 수 없으며, 질문자의 질문이 요청했기 때문에 그것을 포함 시켰습니다. 보간법을 사용합니다. 보간을 사용하면 보간 할 모든 속성의 현재 및 이전 업데이트 결과를 저장해야합니다.
Olhovsky

이것은 문제를 다시 말하고 보간과 외삽의 차이점입니다. 답이 아닙니다.

1
내 예에서는 상태와 위치에 회전을 저장했습니다. 상태에 속도 (또는 속도) 만 저장할 수 있습니다. 그런 다음 정확히 같은 방식으로 속도를 뛰어 넘습니다 ( Lerp(previous_speed, current_speed, elapsed/update_tick_length)). 상태에 저장하려는 숫자로이 작업을 수행 할 수 있습니다. Lerping은 lerp factor가 주어지면 두 값 사이의 값을 제공합니다.
Olhovsky

1
각도 움직임의 보간을 위해서는 lerp 대신 slerp 를 사용하는 것이 좋습니다 . 가장 쉬운 방법은 두 국가의 쿼터니언을 저장하고 그 사이에서 잠을자는 것입니다. 그렇지 않으면 각속도와 각 가속에 동일한 규칙이 적용됩니다. 스켈 레탈 애니메이션에 대한 테스트 사례가 있습니까?
Maik Semder

-2

이 문제는 시작과 끝의 정의에 대해 약간 다르게 생각해야합니다. 초보 프로그래머는 종종 프레임 당 위치의 변화를 생각하며 이는 처음에가는 좋은 방법입니다. 내 대답을 위해 1 차원 답변을 고려해 봅시다.

x 위치에 원숭이가 있다고 가정 해 봅시다. 이제 키보드 나 다른 컨트롤을 기반으로 프레임 당 원숭이의 위치에 추가 할 수있는 "addX"도 있습니다. 프레임 속도가 보장되는 한 작동합니다. x가 100이고 addX가 10이라고 가정 해 봅시다. 10 프레임 후에 x + = addX는 200으로 누적되어야합니다.

이제 addX 대신 가변 프레임 속도가있을 때 속도와 가속도를 고려해야합니다. 이 모든 산술 과정을 안내하지만 매우 간단합니다. 우리가 알고 싶은 것은 밀리 초 (1/1000 초) 당 얼마나 멀리 여행하고 싶은가입니다.

30FPS로 촬영하는 경우 velX는 1/3의 1/3 (마지막 예제에서 30FPS의 10 프레임)이어야하며 그 시간에 100 'x'를 이동하고 싶을 때 velX를 프레임 당 100 거리 / 10 FPS 또는 10 거리. 밀리 초 단위로, 3.3 밀리 초당 1 개의 거리 x 또는 밀리 초당 0.3 'x'로 작동합니다.

이제 업데이트 할 때마다 경과 시간을 파악하기 만하면됩니다. 33ms가 지 났는지 (1/30 초), 또는 거리에 0.3을 곱한 밀리 초 수를 곱하면됩니다. 즉, ms (밀리 초) 정확도를 제공하는 타이머가 필요하지만 대부분의 타이머가이를 제공합니다. 간단히 다음과 같이하십시오.

var beginTime = getTimeInMillisecond ()

... 나중에 ...

var time = getTimeInMillisecond ()

var elapsedTime = time-beginTime

beginTime = 시간

... 이제이 elapsedTime을 사용하여 모든 거리를 계산하십시오.


1
그는 업데이트 속도가 다양하지 않습니다. 그는 업데이트 속도가 고정되어 있습니다. 솔직히 말해서, 당신이 여기서 어떤 점을 만들려고하는지 모르겠습니다 : /
Olhovsky

1
??? -1. 그것은 전체 요점이며, 보장 된 업데이트 속도가 있지만 가변 렌더링 속도가 있으며 끊김없이 매끄럽게 만들고 싶습니다.
AttackingHobo

가변 업데이트 속도는 네트워크 게임, 경쟁 게임, 리플레이 시스템 또는 결정적인 게임 플레이에 의존하는 다른 것에서는 제대로 작동하지 않습니다.
AttackingHobo

1
고정 업데이트는 의사 마찰을 쉽게 통합 할 수 있습니다. 예를 들어 속도에 각 프레임에 0.9를 곱하려면 프레임이 빠르거나 느린 경우 얼마나 곱할 것인지 어떻게 알 수 있습니까? 고정 업데이트는 때때로 선호됩니다. 사실상 모든 물리 시뮬레이션은 고정 업데이트 속도를 사용합니다.
Olhovsky

2
가변 프레임 속도를 사용하고 많은 객체가 서로 튀는 복잡한 초기 상태를 설정하면 정확히 동일한 시뮬레이션을 보장하지 않습니다. 실제로 처음에는 약간의 차이가있을 때마다 약간 씩 다르게 시뮬레이션되어 각 시뮬레이션 실행간에 짧은 시간 동안 완전히 다른 상태로 합성됩니다.
AttackingHobo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.