Làm thế nào để nội suy giữa hai trạng thái trò chơi?


24

Mẫu tốt nhất để tạo ra một hệ thống mà tất cả các vị trí đối tượng được nội suy giữa hai trạng thái cập nhật là gì?

Bản cập nhật sẽ luôn chạy ở cùng tần số, nhưng tôi muốn có thể kết xuất ở bất kỳ FPS nào. Vì vậy, kết xuất sẽ mượt nhất có thể, bất kể khung hình mỗi giây, cho dù thấp hơn hay cao hơn tần số cập nhật.

Tôi muốn cập nhật 1 khung hình vào nội suy tương lai từ khung hiện tại sang khung tương lai. Câu trả lời này có một liên kết nói về việc này:

Dấu thời gian bán cố định hoặc hoàn toàn cố định?

Chỉnh sửa: Làm thế nào tôi cũng có thể sử dụng tốc độ cuối cùng và hiện tại trong phép nội suy? Ví dụ, chỉ với phép nội suy tuyến tính, nó sẽ di chuyển với cùng tốc độ giữa các vị trí. Tôi cần một cách để nó nội suy vị trí giữa hai điểm, nhưng hãy xem xét tốc độ tại mỗi điểm cho phép nội suy. Nó sẽ hữu ích cho các mô phỏng tốc độ thấp như hiệu ứng hạt.


2
ve là logic ve? Vì vậy, cập nhật fps của bạn <render fps?
Vịt Cộng sản

Tôi đã thay đổi thuật ngữ. Nhưng vâng logic. Và không, tôi muốn hoàn toàn miễn phí kết xuất khỏi bản cập nhật, vì vậy trò chơi có thể kết xuất ở 120HZ hoặc 22.8HZ và việc cập nhật sẽ vẫn chạy cùng tốc độ, miễn là người dùng đáp ứng các yêu cầu hệ thống.
Tấn côngHobo

điều này có thể thực sự khó khăn vì trong khi kết xuất tất cả các vị trí đối tượng của bạn sẽ đứng yên (thay đổi chúng trong quá trình kết xuất có thể gây ra một số hành vi không xác định)
Ali1S232

Nội suy sẽ tính toán trạng thái tại một thời điểm giữa 2 khung cập nhật đã được tính toán. Đây không phải là câu hỏi về ngoại suy, tính toán trạng thái trong một thời gian sau khung cập nhật cuối cùng? Vì bản cập nhật tiếp theo thậm chí còn chưa được tính.
Maik

Tôi nghĩ rằng nếu anh ta chỉ có một luồng cập nhật / kết xuất, thì không thể cập nhật lại chỉ vị trí kết xuất. Bạn chỉ cần gửi vị trí cho GPU và sau đó cập nhật lại.
zacharmarz

Câu trả lời:


22

Bạn muốn tách riêng cập nhật (đánh dấu logic) và vẽ tỷ lệ (kết xuất đánh dấu).

Cập nhật của bạn sẽ tạo ra vị trí của tất cả các đối tượng trên thế giới sẽ được rút ra.

Tôi sẽ đề cập đến hai khả năng khác nhau ở đây, một khả năng bạn yêu cầu, ngoại suy và một phương pháp khác là nội suy.

1.

Phép ngoại suy là nơi chúng ta sẽ tính toán vị trí (dự đoán) của đối tượng ở khung tiếp theo, sau đó nội suy giữa vị trí đối tượng hiện tại và vị trí mà đối tượng sẽ ở khung tiếp theo.

Để làm điều này, mỗi đối tượng được vẽ phải có một liên kết velocityposition. Để tìm vị trí mà đối tượng sẽ ở khung tiếp theo, chúng ta chỉ cần thêm velocity * draw_timestepvào vị trí hiện tại của đối tượng, để tìm vị trí dự đoán của khung tiếp theo. draw_timesteplà lượng thời gian đã trôi qua kể từ lần đánh dấu kết xuất trước đó (còn gọi là lệnh rút thăm trước đó).

Nếu bạn để nó ở đây, bạn sẽ thấy các đối tượng "nhấp nháy" khi vị trí dự đoán của chúng không khớp với vị trí thực tế ở khung tiếp theo. Để loại bỏ nhấp nháy, bạn có thể lưu trữ vị trí dự đoán và lerp giữa vị trí dự đoán trước đó và vị trí dự đoán mới ở mỗi bước vẽ, sử dụng thời gian trôi qua kể từ lần đánh dấu cập nhật trước đó làm yếu tố lerp. Điều này vẫn sẽ dẫn đến hành vi kém khi các đối tượng chuyển động nhanh đột ngột thay đổi vị trí và bạn có thể muốn xử lý trường hợp đặc biệt đó. Tất cả những gì được nói trong đoạn này là lý do tại sao bạn không muốn sử dụng phép ngoại suy.

2.

Nội suy là nơi chúng tôi lưu trữ trạng thái của hai bản cập nhật mới nhất và nội suy giữa chúng dựa trên lượng thời gian hiện tại đã trôi qua kể từ bản cập nhật trước đó. Trong thiết lập này, mỗi đối tượng phải có một liên kết positionprevious_position. Trong trường hợp này, bản vẽ của chúng tôi sẽ đại diện cho một bản cập nhật tồi tệ nhất phía sau trò chơi hiện tại và tốt nhất là ở trạng thái chính xác như bản cập nhật hiện tại.


Theo tôi, có lẽ bạn muốn nội suy như tôi đã mô tả, vì hai bên dễ thực hiện hơn và rút ra một phần rất nhỏ của một giây (ví dụ 1/60 giây) phía sau trạng thái cập nhật hiện tại của bạn là ổn.


Chỉnh sửa:

Trong trường hợp ở trên không đủ để cho phép bạn thực hiện triển khai, đây là một ví dụ về cách thực hiện phương pháp nội suy mà tôi đã mô tả. Tôi sẽ không bao gồm ngoại suy, bởi vì tôi không thể nghĩ ra bất kỳ kịch bản trong thế giới thực nào mà bạn nên thích nó.

Khi bạn tạo một đối tượng có thể vẽ, nó sẽ lưu trữ các thuộc tính cần thiết để vẽ (tức là thông tin trạng thái cần thiết để vẽ nó).

Trong ví dụ này, chúng tôi sẽ lưu trữ vị trí và xoay. Bạn cũng có thể muốn lưu trữ các thuộc tính khác như vị trí tọa độ màu hoặc kết cấu (nghĩa là nếu một họa tiết cuộn).

Để ngăn dữ liệu bị sửa đổi trong khi luồng kết xuất đang vẽ nó, (tức là vị trí của một đối tượng bị thay đổi trong khi luồng kết xuất vẽ, nhưng tất cả các đối tượng khác chưa được cập nhật), chúng ta cần thực hiện một số loại bộ đệm đôi.

Một đối tượng lưu trữ hai bản sao của nó previous_state. Tôi sẽ đặt chúng trong một mảng và gọi chúng là previous_state[0]previous_state[1]. Nó tương tự cần hai bản sao của nó current_state.

Để theo dõi bản sao của bộ đệm đôi được sử dụng, chúng tôi lưu trữ một biến state_index, có sẵn cho cả luồng cập nhật và vẽ.

Trước tiên, luồng cập nhật sẽ tính toán tất cả các thuộc tính của một đối tượng bằng dữ liệu của chính nó (bất kỳ cấu trúc dữ liệu nào bạn muốn). Sau đó, nó sao chép current_state[state_index]đến previous_state[state_index], và sao chép dữ liệu mới phù hợp cho bản vẽ, positionrotationvào current_state[state_index]. Sau đó state_index = 1 - state_index, để lật bản sao hiện đang sử dụng của bộ đệm đôi.

Tất cả mọi thứ trong đoạn trên phải được thực hiện với một khóa được lấy ra current_state. Các bản cập nhật và vẽ chủ đề đều đưa ra khóa này. Khóa chỉ được lấy ra trong thời gian sao chép thông tin trạng thái, nhanh.

Trong luồng kết xuất, sau đó bạn thực hiện phép nội suy tuyến tính trên vị trí và xoay như vậy:

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

Đâu elapsedlà lượng thời gian đã trôi qua trong luồng kết xuất, kể từ lần cập nhật cuối cùng và update_tick_lengthlà khoảng thời gian mà tốc độ cập nhật cố định của bạn mất trên mỗi lần đánh dấu (ví dụ: tại các bản cập nhật 20FPS, update_tick_length = 0.05).

Nếu bạn không biết Lerpchức năng trên là gì, thì hãy kiểm tra bài viết của wikipedia về chủ đề: Nội suy tuyến tính . Tuy nhiên, nếu bạn không biết lerping là gì, thì có lẽ bạn chưa sẵn sàng để thực hiện cập nhật / bản vẽ tách rời với bản vẽ được nội suy.


1
+1 tương tự phải được thực hiện cho các định hướng / phép quay và tất cả các trạng thái khác thay đổi theo thời gian, tức là giống như hoạt hình vật chất trong các hệ thống hạt, v.v.
Maik Semder

1
Điểm tốt Maik, tôi chỉ sử dụng vị trí làm ví dụ. Bạn cần lưu trữ "vận tốc" của bất kỳ tài sản nào bạn muốn ngoại suy (tức là tốc độ thay đổi theo thời gian của tài sản đó), nếu bạn muốn sử dụng phép ngoại suy. Cuối cùng, tôi thực sự không thể nghĩ đến một tình huống mà phép ngoại suy tốt hơn phép nội suy, tôi chỉ đưa nó vào vì câu hỏi của người hỏi yêu cầu nó. Tôi sử dụng phép nội suy. Với phép nội suy, chúng ta cần lưu trữ kết quả cập nhật hiện tại và trước đó của bất kỳ thuộc tính nào để nội suy, như bạn đã nói.
Olhovsky

Đây là một sự phục hồi của vấn đề và sự khác biệt giữa nội suy và ngoại suy; nó không phải là một câu trả lời

1
Trong ví dụ của tôi, tôi lưu trữ vị trí và xoay trong trạng thái. Bạn chỉ có thể lưu trữ vận tốc (hoặc tốc độ) ở trạng thái là tốt. Sau đó, bạn lerp giữa tốc độ theo cùng một cách chính xác ( Lerp(previous_speed, current_speed, elapsed/update_tick_length)). Bạn có thể làm điều này với bất kỳ số nào bạn muốn lưu trữ trong tiểu bang. Lerping chỉ cung cấp cho bạn một giá trị giữa hai giá trị, được đưa ra một yếu tố lerp.
Olhovsky

1
Để nội suy chuyển động góc, nên sử dụng slerp thay vì lerp. Dễ nhất sẽ là lưu trữ các tứ phân vị của cả hai trạng thái và trượt giữa chúng. Mặt khác, các quy tắc tương tự áp dụng cho tốc độ góc và gia tốc góc. Bạn có một trường hợp thử nghiệm cho hoạt hình xương?
Maik

-2

Vấn đề này đòi hỏi bạn phải suy nghĩ về định nghĩa bắt đầu và kết thúc khác nhau một chút. Các lập trình viên mới bắt đầu thường nghĩ về sự thay đổi vị trí trên mỗi khung và đó là một cách tốt để bắt đầu. Để đáp ứng của tôi, hãy xem xét một câu trả lời một chiều.

Giả sử bạn có một con khỉ ở vị trí x. Bây giờ bạn cũng có một "addX" mà bạn thêm vào vị trí của khỉ trên mỗi khung dựa trên bàn phím hoặc một số điều khiển khác. Điều này sẽ hoạt động miễn là bạn có tốc độ khung hình được đảm bảo. Giả sử x của bạn là 100 và addX của bạn là 10. Sau 10 khung hình, x + = addX của bạn sẽ tích lũy thành 200.

Bây giờ, thay vì addX, khi bạn có tốc độ khung hình thay đổi, bạn nên suy nghĩ về tốc độ và gia tốc. Tôi sẽ hướng dẫn bạn qua tất cả các số học này nhưng là siêu đơn giản. Những gì chúng tôi muốn biết là bạn muốn đi bao xa mỗi mili giây (1/1000 giây)

Nếu bạn đang quay trong 30 FPS, thì VelX của bạn sẽ là 1/3 giây (10 khung hình từ ví dụ cuối cùng ở 30 FPS) và bạn biết rằng bạn muốn di chuyển 100 'x' trong thời gian đó, vì vậy hãy đặt velX của bạn thành 100 khoảng cách / 10 FPS hoặc 10 khoảng cách trên mỗi khung hình. Trong một phần nghìn giây, tính ra là 1 khoảng cách x trên 3,3 mili giây hoặc 0,3 'x' mỗi mili giây.

Bây giờ, mỗi khi bạn cập nhật, tất cả những gì bạn cần làm là tìm ra thời gian đã trôi qua. Cho dù 33 ms đã trôi qua (1/30 giây) hay bất cứ điều gì, bạn chỉ cần nhân khoảng cách 0,3 với số mili giây trôi qua. Điều này có nghĩa là bạn cần một bộ đếm thời gian cung cấp cho bạn độ chính xác ms (mili giây) nhưng hầu hết các bộ định thời cung cấp cho bạn điều này. Đơn giản chỉ cần làm một cái gì đó như thế này:

var startedTime = getTimeInMillisecond ()

... một lát sau ...

var time = getTimeInMillisecond ()

var elapsedTime = time-startedTime

startedTime = thời gian

... bây giờ sử dụng thời gian trôi qua này để tính toán tất cả các khoảng cách của bạn.


1
Anh ta không có tỷ lệ cập nhật thay đổi. Ông có một tỷ lệ cập nhật cố định. Thành thật mà nói, tôi thực sự không biết điểm nào bạn đang cố gắng thực hiện ở đây: /
Olhovsky

1
??? -1. Đó là toàn bộ vấn đề, tôi đang có một tỷ lệ cập nhật được đảm bảo, nhưng tốc độ kết xuất thay đổi và tôi muốn nó được thông suốt mà không bị nói lắp.
Tấn

Tốc độ cập nhật biến không hoạt động tốt với các trò chơi được nối mạng, trò chơi cạnh tranh, hệ thống phát lại hoặc bất cứ điều gì khác liên quan đến trò chơi có tính quyết định.
Tấn

1
Cập nhật cố định cũng cho phép dễ dàng tích hợp giả ma sát. Ví dụ: nếu bạn muốn nhân tốc độ của mình với 0,9 mỗi khung hình, làm thế nào để bạn tính được bao nhiêu để nhân lên nếu bạn có khung hình nhanh hay chậm? Cập nhật cố định đôi khi được ưu tiên rất nhiều - hầu như tất cả các mô phỏng vật lý đều sử dụng tốc độ cập nhật cố định.
Olhovsky

2
Nếu tôi sử dụng tốc độ khung hình thay đổi và thiết lập trạng thái ban đầu phức tạp với nhiều đối tượng nảy ra khỏi nhau, không có gì đảm bảo rằng nó sẽ mô phỏng giống hệt nhau. Trong thực tế, rất có thể nó sẽ mô phỏng hơi khác nhau mỗi lần, với những khác biệt nhỏ khi bắt đầu, gộp trong một thời gian ngắn thành các trạng thái hoàn toàn khác nhau giữa mỗi lần chạy mô phỏng.
Tấn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.