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


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], 그림에 대한 새로운 데이터 관련, 복사 position및 rotation에 current_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.