Physics Engine Hoạt Động Như Thế Nào?

Nói ngắn gọn physics engine là phần mềm mô phỏng các hiện tượng vật lý, tuy nhiên bài viết này chỉ giới hạn nói về cách hoạt động của physics engine và cách nó được tích hợp trong các game engine để phát triển các trò chơi điện tử đồ họa 2D, mặc dù trong bài viết này nói về 2D game physics engine nhưng các khái niệm cũng được áp dụng tương tự ở 3D game physics engine.

game overview
Figure 1

Tưởng tượng chúng ta đang phát triển một trò chơi bóng đá, có một số vấn đề về giả lập cho giống môi trường bên ngoài để game trông thật hơn/hay hơn mà chúng ta(physics engine) cần giải quyết như:

  • Khi cầu thủ sút bóng thì quả bóng phải di chuyển theo hướng sút
  • Khi bóng bay tới chạm vào một cầu thủ khác hoặc cầu gôn thì bóng phải bật ra theo 1 hướng khác và giảm tốc độ
  • Khi quả bóng di chuyển thì tốc độ sẽ giảm dần(vì có ma sát với mặt sân)
game vs physic
Figure 2

Chúng ta có thể coi tất cả(cầu thủ, trái banh, khung thành, trọng tài …) là Entity mỗi Enity thì đính kèm Body(có thông tin cân nặng, gia tốc …) và physics engine chỉ cần quan tâm các thông tin trong body để xử lý giả lập vật lý còn các thông tin khác như cầu thủ team nào, chạy nhanh bao nhiêu… thì đó thuộc về phần game logic.

physics engine overview
Figure 3

Physics engine chỉ nhận input từ game logic component(đầu vào có thể là tạo mới body, xóa body, add forces …) sau đó xử lý tính toán ra vận tốc, hướng đi, độ quay, tọa độ … của body, rồi game logic sẽ lấy những thông tin đã được physics engine xử lý để vẽ ra màn hình. Bản chất cũng chỉ là 1 vòng lặp vô tận nhận input từ game logic rồi xử lý và cứ lặp đi lặp lại như vậy. Về cơ bản physics engine được chi ra làm 4 stages:

  • Collision - phát hiện va chạm
  • Apply forces - nhận thông tin từ game logic component
  • Solve constraints - tính toán va chạm
  • Update positions - tính toán sau khi va chạm

Stage 1: Collision - Phát hiện va chạm

physics engine overview
Figure 4

Ở bước này được chia làm 2 phases nhỏ là board-phase và narrow phase. Board-phase mục tiêu là liệt kê những Body có khả năng va chạm rồi từ list có khả năng va chạm đó mới kiểm tra thật sự có va chạm không ở narrow-phase nhằm tối ưu performance.

Board phase

Bước này thường dùng các thuật toán đơn giản, nhanh như axis-aligned bounding box(AABB) để tính toán từng cặp body xem có đụng nhau hay không. Chú ý là thuật toán AABB chỉ áp dụng được cho 2 hình chữ nhật có cạnh song song với trục x và trục y. Hoặc nếu 2 body đó là hình tròn, nếu cả 2 là hình tròn thì chỉ cần kiểm tra khoảng cách từ 2 tâm mà nhỏ hơn tổng 2 bán kính 2 hình tròn thì 2 body đó colission với nhau.

broad phase
Figure 5

Vậy trường hợp body không phải hình tròn và cũng không phải hình chữ nhật luôn thì thế nào? Trường hợp này ta chỉ cần bao body trong 1 hình tròn lớn/ hoặc hình chữ nhật rồi kiểm tra, vậy mới nói phase này chỉ kiểm tra có khả năng collision. Để tăng performance có thể chia màn hình ra 4 grid, chỉ cần check các cặp body trong cùng 1 grid với nhau(màu đỏ) vì 2 body trong 2 grid khác nhau thì không thể xảy ra collision. Hoặc có thể chiếu tọa độ x min, max của 2 body lên trục Ox(màu xanh), nếu giao nhau thì có thể xảy ra collision

Narrow phase

narrow phase
Figure 6

Ở bước này thì thường dùng thuật toán Separating Axis Theorem(SAT) để phát hiện những body xảy ra va chạm. Nếu 2 body là va chạm thì cần phải lấy được thông tin normal vector, penetration(độ sâu), reference face và incident face để qua stage 3 tính toán được hướng và lực phản ra khi 2 body va chạm nhau. Tham khảo code

Stage 2: Apply Forces - Nhận thông tin từ game logic component

apply forces stage
Figure 7

Như đã nói từ đầu stage 2 này nhận input từ game logic component, vậy input này là gì? input này chính là forces. Ví dụ khi cầu thủ đá 1 trái banh thì trái banh sẽ nhận 1 lực(force). Ok vậy chúng ta đã biết có lực đẩy rồi làm sao chung ta tính được vận tốc trái banh khi bị đá trúng, điều này thì dựa nào định luật thứ 2 Newton huyền thoại F = ma

Định luật II: Vector gia tốc của một vật luôn cùng hướng với lực tác dụng lên vật. Độ lớn của vector gia tốc tỉ lệ thuận với độ lớn của vector lực và tỉ lệ nghịch với khối lượng của vật.
struct Body {
	float mass // khối lượng, đơn vị kg
	float friction
	Vec2 totalForces
	Vec2 position
	Vec2 acceleration
	Vec2 velocity
	...
}

for Body b : bodies {
	// Định luật 2 Newton
	b.acceleration = (1/b.mass) * b.totalForces 
	// deltaTime là khoảng cách thời gian giữa 2 frame
	b.velocity += b.acceleration * deltaTime 
}

Stage 3: Solve constraints - Tính toán va chạm

solve constraints
Figure 8

Tính được vận tốc, chúng ta cần tính thêm lực đẩy, theo định luật 3 Newton thì:

Định luật III: Khi một vật tác dụng lực lên vật thể thứ hai, vật thứ hai sẽ tác dụng một lực cùng độ lớn và ngược chiều về phía vật thứ nhất.
	Body *a = bodies[0]; // cầu thủ
	Body *b = bodies[1]; // trái banh

	float elasticity = std::min(a->restitution, b->restitution);
	Vec2 vrel = a->velocity - b->velocity;
	// normal là vector mà chúng ta tính được ở Board phase
	float impulseMagnitude = vrel.Dot(normal) * -(1 + elasticity) / (a->invMass + b->invMass);
	Vec2 jn = this->normal * impulseMagnitude;

	a->velocity += jn * 1/a.mass
	b->velocity += -jn * 1/a.mass

Ở đây mình minh họa dựa vào định luật 3 Newton mình tính ra được lực đẩy vào 2 body, thực tế tính toán phức tạp hơn nên mình cứ coi nó là magicbox. Các bạn có thể tham khảo thêm phần code này

Stage 4: Update positions - Tính toán sau khi va chạm

update positions
Figure 9

Khi đã tính toán vận tốc xong xuôi, cuối cùng chỉ là việc update vị trí của các body.

for Body b : bodies {
	// công thức quảng đường bằng thời gian x vận tốc :D
	b.position += deltaTime * b.velocity 
	b.totalForces = Vec2(0,0) // reset forces

Tổng kết

  • Physics Engine đặc biệt cho game thì không cần nhất thiết phải giống thật 100% ngoài đời. Đơn cử game phía trên chúng ta không có tính trọng lực vì mình đang giả định là game kiểu top-down nhìn từ trên xuống
  • Biến totalForces ở trong struct Body là tổng cộng tất cả các lực được tác dụng lên nó, ở game này mình chỉ có cộng lực cầu thủ đá vào trái banh, ngoài ra còn có các lực thông dụng khác như gravity(trọng lực), drag force(lực cản), friction force(lực ma sát), spring force(lực đàn hồi) hay nếu làm game liên quan đến hệ ngân hà(mặt trăng quay quanh trái đất) thì có thể thêm gravitational attraction force(lực hấp dẫn). Mỗi loại lực sẽ có công thức tính khác nhau, cuối cùng đều ra 1 Vec2 và mình cứ cộng hết vào biến totalForces
  • Theo định luật I của Newton Nếu một vật không chịu tác dụng của bất cứ lực nào hoặc chịu tác dụng của nhiều lực nhưng tổng hợp lực của các lực này bằng không thì vât giữ nguyên trạng thái chuyển động thẳng đều hoặc đứng yên, trong Physics engine cũng thể hiện nếu Entity có biến totalForces bằng 0 thì Entity(body) đó sẽ đứng yên.
  • Ở stage 2, 3, 4 mình hiện tại chỉ tính Linear momentum(động lượng tuyến tính) cho đơn giản, thực tế là mình cần phải tính thêm cả Angular momentum (Mô men động lượng). Tham khảo cách tính ở đây

comments