Làm thế nào để thực hiện vật lý độc lập tốc độ khung hình chính xác?


7

Vì vậy, tôi đã làm việc trên một dự án trong một thời gian và gần đây đã vấp phải một vấn đề:

Cách tiếp cận phổ biến đối với vật lý độc lập tốc độ khung hình là sử dụng khoảng thời gian cập nhật cố định (ví dụ: Unity) hoặc chỉ nhân mỗi thay đổi vật lý với delta. Vì vậy, nó sẽ trông giống như thế này:

update(int delta)
{
   ...
   positionX += velocityX * delta;
   ...
} 

Và khi tăng tốc được thêm vào, sự thay đổi vận tốc được xử lý tương tự:

update(int delta)
{
   ...
   velocityX += accelerationX * delta;
   ...
}

Điều này hoạt động tốt cho hầu hết các trường hợp sử dụng. Trên thực tế rất tốt, tôi đã không nhận thấy bất cứ điều gì đã tắt trong nhiều tháng. Nhưng có một vấn đề với logic này, cụ thể là vị trí sẽ hơi lệch đối với tốc độ khung hình hoàn toàn khác nhau. Vấn đề này không đáng kể khi làm việc với các thay đổi tốc độ khung hình nhỏ, nhưng hoàn toàn đáng chú ý một khi thời gian delta thay đổi bởi số lượng lớn hơn. Giả sử, tốc độ FPS từ 30 đến 600. Bây giờ, trong một trò chơi bình thường, FPS chỉ bị khóa và ngay cả khi các vị trí bị tắt trong một vài khung hình như 5% thì không thành vấn đề vì người ta không chú ý đến nó. Nhưng tôi tự nhân bội số delta với một yếu tố để hoàn thành một hiệu ứng nhất định và lỗi này có phần đáng chú ý và phá vỡ trò chơi .

Một ví dụ nhỏ để làm rõ những gì tôi muốn nói:

Có một đối tượng trò chơi với vị trí bắt đầu posX = 0 và x-Velocity là 10 và gia tốc ngang là 0,1.

Sau hai khung hình, với 50FPS, điều này sẽ dẫn đến (với một delta trung bình)

velocityX += acceleration * delta; 
posX += velocityX * delta;

velocityX += 0.1 * 20; //velocityX = 12
posX += 12 * 20 //posX = 240

//next frame

velocityX += 0.1 * 20; //velocityX = 14
posX += 14 * 20 //posX = 520 (240 + 280)

Sau một khung hình với 25FPS, bằng với khoảng thời gian giống như hai khung hình với 50FPS, điều này sẽ giúp chúng tôi:

velocityX += 0.1 * 40; //velocityX = 14
posX += 14 * 40; //posX = 560

Vì vậy, hai tình huống có cùng khoảng thời gian trôi qua, chúng tôi nhận được kết quả khác nhau (520 cho 50FPS và 560 cho 25FPS).

Bây giờ, vấn đề rõ ràng là có thêm một khung hình (trong ví dụ này, nó có thể là bất kỳ số lượng khung hình nào nhưng kết quả vẫn khác nhau trong cùng một khoảng thời gian) trong đó gia tốc được áp dụng và do đó bạn sẽ có được nhiều FPS hơn.

Ổn định đồng bằng không phải là một lựa chọn vì tôi cần các đồng bằng khác nhau cho hiệu ứng đã nói, vì vậy tôi cần vật lý để hoàn toàn độc lập ở đó. Có ai từng gặp một tình huống tương tự và có giải pháp nào cho nó không?



@msell Đây chắc chắn không phải là một bản sao. Đó là về các chủ đề khác nhau. Câu hỏi bạn liên kết là về các bước thời gian cố định và thay đổi, trong khi đây là về cách giải quyết các vấn đề với các thời điểm delta khác nhau
flotothemoon

1
Câu hỏi và câu trả lời được liên kết giải thích rằng sử dụng bước thời gian cố định thực sự là cách để giải quyết vấn đề của bạn.
msell

@msell Bạn đã thực sự đọc câu hỏi của tôi? Không có nó không phải là. Tôi thực sự muốn sử dụng các đồng bằng khác nhau, câu hỏi là làm thế nào để thực hiện nó đúng cách.
flotothemoon

Câu trả lời:


3

Ổn định đồng bằng không phải là một lựa chọn vì tôi cần các đồng bằng khác nhau cho hiệu ứng đã nói, vì vậy tôi cần vật lý để hoàn toàn độc lập ở đó.

Ổn định thời gian delta là lựa chọn duy nhất của bạn. Nếu chúng ta có thể thay đổi bước thời gian của tích hợp số và được đảm bảo để có kết quả tương tự, chúng ta có thể khai thác điều này để có được các phép tính thực sự chính xác mà không cần bất kỳ nỗ lực tính toán nào. Thật không may, hôm nay không có bữa trưa miễn phí. Nếu bạn thay đổi độ chính xác của mô phỏng, kết quả có thể thay đổi.

Nếu bạn ổn với kết quả thay đổi một chút, bạn có thể chọn phương thức tích hợp khác. Tích hợp Verlet thực hiện tốt hơn rất nhiều so với Euler trong ví dụ của bạn: vị trí sau 40 ms sẽ được ước tính là 480 với t = 40 ms và tại 480 với t = 20 ms, nhưng chúng cũng sẽ thay đổi nếu bạn đưa ra gia tốc không đổi.

Tôi không hoàn toàn chắc chắn về hiệu ứng mà bạn muốn theo đuổi bằng cách thay đổi bước thời gian, nhưng nếu bạn đang muốn tạo hiệu ứng chuyển động chậm chỉ ảnh hưởng đến phát lại, bạn cần tách rời mô phỏng từ tốc độ khung hình. Một ví dụ: mô phỏng của bạn thường hoạt động với t 10 ms. Cứ sau mười mili giây, bạn tiến lên vật lý bằng một tích tắc. Trò chơi chạy ở tốc độ 25 khung hình / giây; bạn vẽ lại màn hình mỗi tích tắc thứ tư. Nếu bạn muốn giảm tốc độ xuống một nửa tốc độ bình thường, thay vào đó cứ sau 20 mili giây, bạn lại tăng tốc vật lý, nhưng bạn giữ tốc độ 10 ms (vì đó là thời gian đã trôi qua trong trò chơi). Để giữ tốc độ khung hình liên tục, bây giờ bạn phải vẽ lại màn hình cứ sau hai tích tắc.

Bất kể tốc độ phát lại, kết quả sẽ giống nhau, nhưng phương pháp này có nhược điểm khác. Bạn không thể vẽ một khung mới cho đến khi ít nhất đã trôi qua trong trò chơi và vì giá trị đó là cố định, nên có giới hạn về mức độ bạn có thể đi chậm mà không bị rớt khung hình. Bạn có thể đẩy giới hạn thấp hơn này bằng cách chọn giá trị nhỏ hơn cho Δt, nhưng bạn sẽ lãng phí tài nguyên cho độ chính xác mà bạn không cần ở tốc độ bình thường. Cuối cùng, trò chơi của bạn có thể không thể chạy ở tốc độ bình thường, nếu một tích tắc vẫn đang được tính khi lần tiếp theo dự kiến ​​bắt đầu.


"Tích hợp Verlet" mà bạn đề cập nghe có vẻ khá tốt những gì tôi muốn đạt được, nó không phải chính xác 100%, 99% hoặc hơn là tốt. Bạn có thể giải thích ý của bạn với điều đó không và làm thế nào để áp dụng nó vào công thức của tôi?
flotothemoon

2
@ 1337: Euler giả định rằng trong khoảng thời gian của quá khứ, vật thể đã chuyển động với vận tốc mới. Verlet giả định rằng trong t cuối cùng, vận tốc đã dần thay đổi thành giá trị mới và lấy vận tốc trung bình làm trung điểm giữa vận tốc mới và vận tốc trước đó. Ví dụ thực hiện .
Marcks Thomas

Cảm ơn về liên kết, tôi đã đọc bài viết đó, nhưng tôi vẫn không hiểu đầy đủ về cách thức hoạt động của nó. Điều gì sẽ được thay thế cho mỗi giá trị? nhầm lẫn - Và ý nghĩa của Derive \ vec {a} (t + \ Delta t) từ tiềm năng tương tác bằng cách sử dụng \ vec {x} (t ​​+ \ Delta t) (Bước 2). (Tôi biết điều này thực sự nghe giống như một yêu cầu viết mã của tôi, nhưng tôi thực sự không biết bắt đầu từ đâu tại đây)
flotothemoon 13/2/2015

@ 1337: Tiềm năng tương tác không liên quan ở đây. Tính gia tốc a (t + t) như bình thường. Nếu gia tốc phụ thuộc vào vị trí hiện tại, sử dụng x (t + t) cho vị trí đó thay vì x (t) . Ví dụ: nếu đối tượng va chạm giữa tt + Δt , trước tiên bạn cập nhật vị trí với vận tốc trước khi va chạm, nhưng sau đó bạn cập nhật vận tốc với gia tốc sau va chạm.
Marcks Thomas

Cảm ơn, tôi đã làm cho nó hoạt động chính xác ngay bây giờ! Tuyệt vời! Nhân tiện, đây là một liên kết đến một lời giải thích khác về cách thực hiện điều này: lolengine.net/blog/2011/12/14/under Hiểu
motion

4

Bạn có thể khắc phục sự cố này bằng cách lấy trung bình vận tốc ban đầu và vận tốc cuối cùng:

velocityOld = velocityX
velocityX += acceleration * delta; 
posX += (velocityX + velocityOld)/2 * delta;

Trong ví dụ cụ thể này, nó sẽ loại bỏ hoàn toàn sự phụ thuộc vào delta. Nói chung giải pháp này sẽ làm giảm ảnh hưởng của delta.


Tôi đã tìm kiếm câu trả lời này - trong khi những người khác mô tả vấn đề tích hợp chính xác một cách chi tiết đầy đủ, đây có lẽ sẽ là cách khắc phục tốt nhất cho OP và hầu hết khách truy cập trong tương lai. - Vì đây đơn giản là công thức chính xác để tính vị trí của một vật có gia tốc không đổi sau một thời gian nhất định. - Bạn có thể thêm rằng đây không phải là một "mánh khóe" nhưng đây thực sự là cách bạn học nó trong lớp vật lý, khi tính toán vị trí của một vật thể rơi tự do.
Falco

2

Chào mừng đến với thế giới tuyệt vời của tích phân.

Speed = distance / time
Acceleration = distance / (time * time)

Vì vậy, để có được câu trả lời chính xác, bạn cần phải tính tích phân của tốc độ và sau đó là tích phân của vị trí của bạn. Các toán có thể được khá khó khăn , và trong nhiều trường hợp nó sẽ là quá mức cần thiết và chỉ làm cho mọi việc phức tạp hơn. Bạn có thể sử dụng điều này để tính toán các vị trí, nhưng phát hiện va chạm có nghĩa là tính toán thời gian một vật thể đạt đến một vị trí nhất định và đối với hai đối tượng chuyển động thời gian đến của các đường dẫn của chúng. Chà, tính toán thậm chí còn nhiều hơn để tính toán, và với mức tăng rất ít (nếu có chút nào vì Float có thể không đủ chính xác cho nó).

Đây là lý do tại sao bạn thường sử dụng dấu thời gian cố định cho các tính toán vật lý, ngay cả khi bạn có tốc độ khung hình thay đổi, chỉ cần theo dõi thời gian trôi qua và sau đó chạy tính toán vật lý của bạn theo dấu thời gian cố định:

for (timeElapsed += delta; timeElapsed > timeStep; timeElapsed -= timeStep) {
  velocityX += acceleration * timeStep; 
  posX += velocityX * timeStep;
}

Điều này có thể được đặt trong vòng cập nhật của bạn để giữ cho vật lý chạy theo dấu thời gian cố định bất kể dấu thời gian của vòng cập nhật của bạn chạy là gì, dĩ nhiên giá trị của timeElapsed sẽ phải được giữ theo thời gian. Bất cứ khi nào bạn muốn sử dụng dấu thời gian cố định, có một số điều cần lưu ý, Shawn Hargraeves có một bài đăng blog rất thú vị về điều này, nó đề cập đến các chi tiết cụ thể của XNA nhưng các khái niệm áp dụng cho bất kỳ dấu thời gian cố định nào.


0

Tính toán vật lý với đồng bằng nói chung không phải là một ý tưởng hay, không chỉ vì lý do bạn đưa ra, mà còn vì bạn nhận được kết quả hơi khác nhau mỗi khi bạn chạy ứng dụng ngay cả với tốc độ khung hình tương tự, có thể đã phá vỡ trò chơi. Hoặc, nếu tốc độ khung hình giảm quá thấp, các đối tượng chuyển động nhanh hoàn toàn có thể cắt qua nhau. (Giả sử bạn không sử dụng phát hiện va chạm liên tục.) Vì vậy, cách giải quyết thông thường (hoặc ít nhất là của tôi) là có hai "sự kiện đánh dấu" khác nhau cho thế giới của bạn: Đánh dấu cố định và đánh dấu khung. Đánh dấu cố định là nơi bạn làm tất cả các công cụ vật lý và mọi thứ cần phải luôn hoạt động giống hệt nhau. Hàm đó chỉ tăng trạng thái mô phỏng theo một lượng cố định, mỗi khi bạn gọi nó. Bạn chỉ cần đo thời gian trôi qua và gọi hàm là số lần thích hợp. Tất nhiên bạn nên nhớ thời gian trôi qua nhiều khung hình để bạn không "mất" thời gian. Trong chức năng / sự kiện đánh dấu khung hình, bạn làm những việc phải hoàn hảo về thời gian, nhưng ở đâu không quan trọng nếu kết quả có thể lặp lại, như máy ảnh. (Nếu bạn di chuyển máy ảnh trong chức năng / sự kiện đánh dấu cố định, bạn sẽ bị giật nhẹ nhưng đáng chú ý, vì vị trí của nó sẽ không luôn luôn phù hợp hoàn hảo với thời gian thực sự trôi qua.)

Một vấn đề của điều này là nếu bạn bị ràng buộc CPU đến mức bạn không thể chạy đủ số lần lặp cố định, trò chơi sẽ hoàn toàn tự thoát ra, bởi vì mọi khung hình đều mất nhiều thời gian hơn và bạn phải chạy nhiều hơn lặp lại từng khung. Nhưng bạn không thể thực sự hiểu được điều đó, bạn chỉ cần thực hiện một số bước mô phỏng nhất định để có kết quả chính xác.

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.