Tại sao nên sử dụng Time.deltaTime trong các chức năng Lerping?


12

Theo hiểu biết của tôi, hàm Lerp nội suy giữa hai giá trị ( ab) bằng cách sử dụng giá trị thứ ba ( t) giữa 01. Tại t = 0, giá trị a được trả về, tại t = 1, giá trị bđược trả về. Tại 0,5 giá trị giữa chừng abđược trả lại.

(Hình ảnh sau đây là một bước mượt mà, thường là nội suy bậc ba)

nhập mô tả hình ảnh ở đây

Tôi đã duyệt các diễn đàn và trên câu trả lời này, tôi tìm thấy dòng mã sau:transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime);

Tôi tự nghĩ, "thật là một kẻ ngốc, anh ta không có ý kiến ​​gì" nhưng vì nó có hơn 40 lần nâng cấp nên tôi đã thử và chắc chắn, nó đã hoạt động!

float t = Time.deltaTime;
transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, t);
Debug.Log(t);

Tôi có giá trị ngẫu nhiên giữa 0.010.02cho t. Không nên nội suy cho phù hợp? Tại sao các giá trị này ngăn xếp? Điều gì về lerp mà tôi không hiểu?


1
A thường là vị trí, thay đổi và lấy mẫu ở 1/60 (60 khung hình / giây) sẽ chỉ di chuyển đối tượng bằng cách nội suy 0,16 liên tục thu hẹp khoảng cách giữa A và B (do đó mẫu nhỏ hơn và nhỏ hơn mỗi lần).
Sidar

Bạn đã đăng nhập và đăng nhập với tt ... đó là các biến khác nhau.
dùng253751

Câu trả lời:


18

Xem thêm câu trả lời này .

Có hai cách phổ biến để sử dụng Lerp:

1. Pha trộn tuyến tính giữa bắt đầu và kết thúc

progress = Mathf.Clamp01(progress + speedPerTick);
current = Mathf.Lerp(start, end, progress);

Đây là phiên bản có lẽ bạn quen thuộc nhất.

2. Dễ dàng theo cấp số nhân đối với mục tiêu

current = Mathf.Lerp(current, target, sharpnessPerTick);

Lưu ý rằng trong phiên bản này, currentgiá trị xuất hiện ở cả đầu ra đầu vào. Nó thay thế startbiến, vì vậy chúng tôi luôn bắt đầu từ bất cứ nơi nào chúng tôi chuyển đến trên bản cập nhật cuối cùng. Đây là những gì mang lại cho phiên bản Lerpbộ nhớ này từ khung hình này sang khung hình tiếp theo. Từ điểm bắt đầu di chuyển này, sau đó chúng tôi sau đó di chuyển một phần của khoảng cách về phía targetđược chỉ định bởi một sharpnesstham số.

Thông số này không còn là "tốc độ" nữa, bởi vì chúng tôi tiếp cận mục tiêu theo cách giống như Zeno . Nếu sharpnessPerTicklà vậy 0.5, thì trong lần cập nhật đầu tiên, chúng ta sẽ đi được nửa đường đến mục tiêu của mình. Sau đó, trên bản cập nhật tiếp theo, chúng tôi sẽ di chuyển một nửa khoảng cách còn lại (vì vậy một phần tư khoảng cách ban đầu của chúng tôi). Sau đó, tiếp theo chúng ta sẽ di chuyển một nửa ...

Điều này mang lại "sự dễ dàng theo cấp số nhân" khi chuyển động nhanh khi cách xa mục tiêu và dần dần chậm lại khi nó tiếp cận một cách không có triệu chứng (mặc dù với số lượng chính xác vô hạn, nó sẽ không bao giờ đạt được nó trong bất kỳ số lượng cập nhật hữu hạn nào - vì mục đích của chúng tôi đủ gần). Thật tuyệt vời khi theo đuổi một giá trị mục tiêu di động hoặc làm mịn đầu vào nhiễu bằng cách sử dụng " trung bình di chuyển theo cấp số nhân ", thường sử dụng một sharpnessPerTicktham số rất nhỏ như 0.1hoặc nhỏ hơn.


Nhưng bạn đã đúng, có một lỗi trong câu trả lời nâng cao mà bạn liên kết. Nó không sửa cho deltaTimeđúng cách. Đây là một lỗi rất phổ biến khi sử dụng phong cách này Lerp.

Kiểu đầu tiên Lerplà tuyến tính, vì vậy chúng ta có thể điều chỉnh tuyến tính tốc độ bằng cách nhân với deltaTime:

progress = Mathf.Clamp01(progress + speedPerSecond * Time.deltaTime);
// or progress = Mathf.Clamp01(progress + Time.deltaTime / durationSeconds);
current = Mathf.Lerp(start, end, progress);

Nhưng việc nới lỏng theo cấp số nhân của chúng tôi là phi tuyến tính , do đó, chỉ cần nhân sharpnesstham số của chúng tôi deltaTimesẽ không đưa ra sự điều chỉnh thời gian chính xác. Điều này sẽ hiển thị như một sự rung chuyển trong chuyển động nếu tốc độ khung hình của chúng ta dao động, hoặc thay đổi độ sắc nét nới lỏng nếu bạn đi từ 30 đến 60 một cách nhất quán.

Thay vào đó, chúng ta cần áp dụng một sự điều chỉnh theo cấp số nhân để dễ dàng theo cấp số nhân:

blend = 1f - Mathf.Pow(1f - sharpness, Time.deltaTime * referenceFramerate);
current = Mathf.Lerp(current, target, blend);

Đây referenceFrameratechỉ là một hằng số muốn 30giữ các đơn vị sharpnessgiống như chúng ta đã sử dụng trước khi sửa lỗi cho thời gian.


Có một lỗi khác có thể tranh cãi trong mã đó, đó là sử dụng Slerp- phép nội suy tuyến tính hình cầu rất hữu ích khi chúng ta muốn có tốc độ quay chính xác nhất quán trong toàn bộ chuyển động. Nhưng nếu chúng ta sẽ sử dụng một sự dễ dàng theo cấp số nhân phi tuyến tính, Lerpsẽ cho một kết quả gần như không thể phân biệt và nó rẻ hơn. ;) Đệ tứ lerp tốt hơn nhiều so với ma trận, vì vậy đây thường là sự thay thế an toàn.


1

Tôi nghĩ rằng khái niệm cốt lõi bị thiếu sẽ nằm trong kịch bản A này không cố định. A được cập nhật theo từng bước, tuy nhiên rất nhiều trong quá trình nội suy mà Time.deltaTime là.

Vì vậy, với A tiến gần hơn đến B với mỗi bước, tổng không gian của phép nội suy sẽ thay đổi theo mỗi lệnh gọi Lerp / Slerp. Không thực hiện phép toán thực tế, tôi nghi ngờ rằng hiệu ứng này không giống với biểu đồ Smoothstep của bạn, nhưng là một cách rẻ tiền để xấp xỉ một sự giảm tốc khi A tiến gần hơn đến B.

Ngoài ra, điều này thường được sử dụng vì B cũng có thể không tĩnh. Trường hợp điển hình có thể là một camera theo sau một người chơi. Bạn muốn tránh giật, có máy ảnh nhảy đến một vị trí hoặc xoay.


1

Bạn đã đúng, phương thức Quaternion Slerp(Quaternion a, Quaternion b, float t)nội suy giữa abtheo số lượng t. Nhưng xem giá trị đầu tiên, nó không phải là giá trị bắt đầu.

Ở đây giá trị đầu tiên được cung cấp cho phương thức là xoay đối tượng hiện tại transform.rotation. Vì vậy, đối với mỗi khung hình, nó sẽ nội suy giữa vòng quay hiện tại và vòng quay đích _lookRotationtheo số lượng Time.deltaTime.

Đó là lý do tại sao nó tạo ra một vòng quay trơn tru.


2
Bây giờ tôi cảm thấy như một thằng ngốc
AzulShiva

@AzulShiva Đừng lo lắng điều đó xảy ra với mọi người;)
Ludovic Feelz
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.