Vẽ nhiều gạch với OpenGL, cách hiện đại


35

Tôi đang làm việc trên một trò chơi PC dựa trên gạch nhỏ / sprite với một nhóm người và chúng tôi đang gặp vấn đề về hiệu suất. Lần cuối cùng tôi sử dụng OpenGL là vào khoảng năm 2004, vì vậy tôi đã tự dạy mình cách sử dụng hồ sơ cốt lõi và tôi cảm thấy hơi bối rối.

Tôi cần vẽ trong vùng lân cận 250-750 viên gạch 48x48 vào màn hình mỗi khung hình, cũng như có thể khoảng 50 họa tiết. Các ô chỉ thay đổi khi một cấp độ mới được tải và các họa tiết luôn thay đổi. Một số gạch được tạo thành từ bốn mảnh 24x24, và hầu hết (nhưng không phải tất cả) các họa tiết có cùng kích thước với gạch. Rất nhiều gạch và họa tiết sử dụng trộn alpha.

Ngay bây giờ tôi đang làm tất cả những điều này trong chế độ ngay lập tức, mà tôi biết là một ý tưởng tồi. Tất cả đều giống nhau, khi một thành viên trong nhóm của chúng tôi cố gắng chạy nó, anh ta có tốc độ khung hình rất tệ (~ 20-30 khung hình / giây), và điều tồi tệ hơn khi có nhiều gạch, đặc biệt là khi nhiều gạch đó là loại được cắt thành miếng. Tất cả điều này khiến tôi nghĩ rằng vấn đề là số lượng các cuộc gọi rút thăm được thực hiện.

Tôi đã nghĩ ra một vài giải pháp khả thi cho vấn đề này, nhưng tôi muốn điều hành chúng bởi một số người biết họ đang nói về cái gì nên tôi không lãng phí thời gian vào thứ gì đó ngu ngốc:

GẠCH

  1. Khi một mức được tải, vẽ tất cả các ô một lần vào bộ đệm khung được gắn với một kết cấu bấm còi lớn và chỉ cần vẽ một hình chữ nhật lớn với kết cấu đó trên mỗi khung.
  2. Đặt tất cả các ô vào một bộ đệm đỉnh tĩnh khi mức được tải và vẽ chúng theo cách đó. Tôi không biết liệu có cách nào để vẽ các đối tượng có kết cấu khác nhau bằng một lệnh gọi đến glDrawElements hay không, nếu đây thậm chí là điều tôi muốn làm. Có lẽ chỉ cần đặt tất cả các viên gạch vào một kết cấu khổng lồ lớn và sử dụng tọa độ kết cấu vui nhộn trong VBO?

XUÂN:

  1. Vẽ mỗi sprite với một lệnh gọi riêng đến glDrawElements. Điều này dường như liên quan đến rất nhiều chuyển đổi kết cấu, mà tôi đã nói là xấu. Là mảng kết cấu có thể hữu ích ở đây?
  2. Sử dụng một VBO năng động nào đó. Câu hỏi kết cấu tương tự như số 2 ở trên.
  3. Điểm spites? Điều này có lẽ là ngớ ngẩn.

Có bất kỳ ý tưởng trong số này là hợp lý? Có một triển khai tốt ở đâu đó tôi có thể xem qua?


Nếu gạch không di chuyển cũng không thay đổi và chúng trông giống như toàn bộ cấp độ, bạn nên sử dụng ý tưởng đầu tiên - bộ đệm khung. Nó sẽ hiệu quả nhất.
zacharmarz

Hãy thử sử dụng một tập bản đồ kết cấu để bạn không phải chuyển đổi kết cấu, nhưng giữ mọi thứ khác như cũ. Bây giờ tốc độ khung hình của họ như thế nào?
dùng253751

Câu trả lời:


25

Cách nhanh nhất để kết xuất các ô là đóng gói dữ liệu đỉnh vào VBO tĩnh bằng các chỉ mục (như glDrawElements chỉ ra). Viết nó vào một hình ảnh khác là hoàn toàn không cần thiết và sẽ chỉ cần nhiều bộ nhớ hơn. Chuyển đổi kết cấu RẤT tốn kém, vì vậy bạn có thể muốn đóng gói tất cả các ô vào một cái gọi là Texture Atlas và cung cấp cho mỗi tam giác trong VBO các tọa độ kết cấu phù hợp. Dựa trên điều này, sẽ không có vấn đề gì khi kết xuất 1000, thậm chí 100000 ô, tùy thuộc vào phần cứng của bạn.

Sự khác biệt duy nhất giữa kết xuất Ngói và kết xuất Sprite có lẽ là các họa tiết là động. Vì vậy, để có hiệu suất tốt nhất nhưng dễ thực hiện, bạn chỉ cần đặt tọa độ cho các đỉnh sprite vào luồng vẽ VBO mỗi khung hình và vẽ bằng glDrawElements. Cũng đóng gói tất cả các kết cấu trong một Texture Atlas. Nếu các sprite của bạn hiếm khi di chuyển, bạn cũng có thể thử tạo VBO động và cập nhật nó khi sprite di chuyển, nhưng đó là quá mức cần thiết ở đây, vì bạn chỉ muốn kết xuất một số sprite.

Bạn có thể nhìn vào một nguyên mẫu nhỏ mà tôi đã tạo trong C ++ với OpenGL: Particulation

Tôi đoán khoảng 10000 điểm ảnh tôi đoán, với tốc độ khung hình / giây trung bình là 400 trên một máy thông thường (Quad Core @ 2,66GHz). Đó là giới hạn CPU, điều đó có nghĩa là card đồ họa có thể hiển thị nhiều hơn. Lưu ý rằng tôi không sử dụng Texture Atlase ở đây, vì tôi chỉ có một kết cấu duy nhất cho các hạt. Các hạt được kết xuất với GL_POINTS và các shader sẽ tính toán kích thước quad thực tế sau đó, nhưng tôi nghĩ đó cũng là Quad Renderer.

Ồ, và vâng, trừ khi bạn có hình vuông và sử dụng các shader để ánh xạ kết cấu, GL_POINTS khá ngớ ngẩn. ;)


Các họa sĩ thay đổi vị trí của họ và kết cấu họ đang sử dụng, và hầu hết trong số họ làm điều này mọi khung hình. Ngoài ra, các sprite và được tạo ra và phá hủy rất thường xuyên. Đây có phải là những điều mà một luồng VBO có thể xử lý?
Nic

2
Stream draw về cơ bản có nghĩa là: "Gửi dữ liệu này đến card đồ họa và loại bỏ nó sau khi vẽ". Vì vậy, bạn phải gửi lại dữ liệu cho từng khung và điều đó có nghĩa là không quan trọng bạn kết xuất bao nhiêu họa tiết, vị trí của chúng, tọa độ kết cấu hoặc màu gì. Nhưng việc gửi tất cả dữ liệu cùng một lúc và để GPU xử lý nó nhanh hơn RẤT NHIỀU so với chế độ ngay lập tức.
Marco

Tất cả điều này có ý nghĩa. Có đáng để sử dụng một bộ đệm chỉ mục điều này không? Các đỉnh duy nhất sẽ được lặp lại là hai góc từ mỗi hình chữ nhật, phải không? (Sự hiểu biết của tôi là các chỉ số là sự khác biệt giữa glDrawElements và glDrawArrays. Có đúng không?)
Nic

1
Nếu không có các chỉ số, bạn không thể sử dụng GL_TRIANGLES, điều này thường rất tệ, vì phương pháp vẽ này là phương pháp có hiệu suất tốt nhất. Ngoài ra, việc triển khai GL_QUADS không được chấp nhận trong OpenGL 3.0 (nguồn: stackoverflow.com/questions/6644099/ trộm ). Hình tam giác là lưới bản địa của bất kỳ card đồ họa. Vì vậy, bạn "sử dụng" thêm 2 * 6 byte để lưu 2 lần thực hiện shader đỉnh và vertex_size * 2 byte. Vì vậy, bạn thường có thể nói rằng đó là LUÔN tốt hơn.
Marco

2
Liên kết đến Particulation đã chết ... Bạn có thể cung cấp một cái mới không?
SWdV

4

Ngay cả với số lần rút thăm này, bạn không nên thấy loại giảm hiệu suất đó - chế độ ngay lập tức có thể chậm nhưng không phải vậy chậm (để tham khảo, Quake thậm chí thân yêu tuổi có thể quản lý hàng ngàn cuộc gọi ngay lập tức chế độ cho mỗi khung hình mà không rơi xuống rất tệ).

Tôi nghi ngờ rằng có một cái gì đó thú vị hơn đang diễn ra ở đây. Điều đầu tiên bạn cần làm là đầu tư một chút thời gian vào việc định hình chương trình của mình, nếu không, bạn sẽ gặp rủi ro rất lớn trong việc nghiên cứu lại dựa trên một giả định có thể dẫn đến tăng hiệu suất bằng không. Vì vậy, hãy chạy nó qua một cái gì đó cơ bản như GLIntercept và xem thời gian của bạn đang đi đâu. Dựa trên kết quả của điều đó, bạn sẽ có thể giải quyết vấn đề với một số thông tin thực tế về (các) nút cổ chai chính của bạn là gì.


Tôi đã thực hiện một số hồ sơ, mặc dù điều đó thật bất tiện vì các vấn đề về hiệu năng không xảy ra trên cùng một máy với sự phát triển. Tôi hơi nghi ngờ rằng vấn đề là ở nơi khác vì các vấn đề chắc chắn gia tăng theo số lượng gạch và gạch theo nghĩa đen không làm gì khác ngoài việc được rút ra.
Nic

Làm thế nào về thay đổi nhà nước sau đó? Bạn đang nhóm các gạch mờ của bạn theo nhà nước?
Maximus Minimus

Đó là một khả năng. Điều này chắc chắn xứng đáng được chú ý nhiều hơn về phía tôi.
Nic

2

Được rồi, vì câu trả lời cuối cùng của tôi đã ra khỏi tay đây là một câu hỏi mới có thể hữu ích hơn.


Giới thiệu về hiệu suất 2D

Đầu tiên, một số lời khuyên chung: 2D không đòi hỏi phần cứng hiện tại, thậm chí phần lớn mã không được tối ưu hóa sẽ hoạt động. Tuy nhiên, điều đó không có nghĩa là bạn nên Chế độ trung gian, ít nhất hãy đảm bảo rằng bạn không thay đổi trạng thái khi không cần thiết (ví dụ: không liên kết một kết cấu mới với glBindTexture khi kết cấu tương tự đã bị ràng buộc, nếu kiểm tra trên CPU là hàng tấn nhanh hơn cuộc gọi glBindTexture) và không sử dụng thứ gì đó hoàn toàn sai và ngu ngốc như glVertex (ngay cả glDrawArrays cũng sẽ nhanh hơn và không khó sử dụng hơn, mặc dù vậy nó không "hiện đại" lắm). Với hai quy tắc rất đơn giản đó, thời gian khung hình ít nhất phải là 10ms (100 khung hình / giây). Bây giờ để có được tốc độ nhanh hơn nữa, bước logic tiếp theo là tạo khối, ví dụ: kết hợp nhiều cuộc gọi rút thăm thành một, vì điều này bạn nên xem xét triển khai kết cấu, do đó bạn có thể giảm thiểu số lượng liên kết kết cấu và do đó tăng số lượng hình chữ nhật bạn có thể vẽ với một cuộc gọi đến một số lượng lớn. Nếu bây giờ bạn không xuống khoảng 2ms (500fps) thì bạn đang làm gì đó sai :)


Bản đồ lát

Thực hiện mã bản vẽ cho bản đồ ô vuông là tìm sự cân bằng giữa tính linh hoạt và tốc độ. Bạn có thể sử dụng VBO tĩnh nhưng điều đó sẽ không hoạt động với các ô hoạt hình hoặc bạn chỉ có thể tạo dữ liệu đỉnh mỗi khung và áp dụng các quy tắc tôi đã giải thích ở trên, điều đó rất linh hoạt nhưng cho đến nay không nhanh như vậy.

Trong câu trả lời trước của tôi, tôi đã giới thiệu một mô hình khác trong đó trình tạo bóng mảnh đảm nhiệm toàn bộ kết cấu, nhưng nó đã chỉ ra rằng nó yêu cầu tra cứu kết cấu phụ thuộc và do đó có thể không nhanh như các phương pháp khác. (Ý tưởng về cơ bản là bạn chỉ tải lên các chỉ báo ô và trong trình đổ bóng mảnh, bạn tính toán tọa độ kết cấu, nghĩa là bạn có thể vẽ toàn bộ bản đồ chỉ bằng một hình chữ nhật)


Sprites

Sprites đòi hỏi rất nhiều tính linh hoạt, khiến cho việc tối ưu hóa rất khó khăn, ngoài những điều được thảo luận trong phần "Giới thiệu về Hiệu suất 2D". Và trừ khi bạn muốn mười ngàn sprite trên màn hình cùng một lúc, nó có thể không đáng nỗ lực.


1
Và ngay cả khi bạn có mười nghìn sprite, phần cứng hiện đại nên chạy nó với tốc độ khá :)
Marco

@ API-Beast còn chờ gì nữa? Làm thế nào để bạn tính toán Texture UV trong shader mảnh? Bạn có định gửi UV đến cho shader mảnh không?
HgMerk 04/07/2015

0

Nếu thất bại ...

Thiết lập một phương pháp vẽ lật. Chỉ cập nhật mọi sprite khác tại một thời điểm. Mặc dù, ngay cả với VisualBasic6 và các phương thức bit-blit đơn giản, bạn có thể chủ động vẽ hàng ngàn họa tiết trên mỗi khung. Có lẽ bạn nên xem xét các phương pháp đó, vì phương pháp trực tiếp của bạn chỉ vẽ các họa tiết dường như đang thất bại. (Nghe có vẻ giống như bạn đang sử dụng "phương thức kết xuất", nhưng cố gắng sử dụng nó như "phương pháp chơi trò chơi". Kết xuất là về sự rõ ràng, không phải tốc độ.)

Rất có thể, bạn liên tục vẽ lại toàn bộ màn hình, lặp đi lặp lại. Thay vì chỉ vẽ lại các khu vực thay đổi. Đó là RẤT NHIỀU chi phí. Khái niệm này là đơn giản, nhưng không dễ hiểu.

Sử dụng một bộ đệm cho nền tĩnh. Điều này không bao giờ được hiển thị chính nó, trừ khi không có họa tiết trên màn hình. Điều này liên tục được sử dụng để "hoàn nguyên" trong đó một sprite đã được rút ra, để hủy bỏ sprite trong cuộc gọi tiếp theo. Bạn cũng cần một bộ đệm để "vẽ", không phải là màn hình. Bạn vẽ ở đó, sau đó, một khi tất cả được vẽ, bạn lật nó lên màn hình, một lần. Đó phải là một cuộc gọi màn hình cho tất cả các họa tiết của bạn. (Trái ngược với việc vẽ từng sprite trên màn hình, từng cái một hoặc cố gắng thực hiện tất cả cùng một lúc, điều này sẽ khiến việc hòa trộn alpha của bạn thất bại.) ". Mỗi cuộc gọi rút thăm sẽ chờ tín hiệu trả về, trước khi nó cố gắng rút lại. (Không phải là v-sync, một đánh dấu phần cứng thực tế, chậm hơn rất nhiều so với thời gian chờ mà RAM có.)

Tôi tưởng tượng đó là một phần lý do bạn chỉ thấy vấn đề này trên một máy tính. Hoặc, nó đang quay trở lại kết xuất phần mềm của ALPHA-BLEND, mà tất cả các thẻ không hỗ trợ. Bạn có kiểm tra xem tính năng đó có được hỗ trợ phần cứng không, trước khi bạn thử sử dụng nó? Bạn có dự phòng (chế độ không pha trộn alpha), nếu họ không có nó? Rõ ràng, bạn không có mã giới hạn (số lượng thứ được trộn), vì tôi cho rằng điều đó sẽ làm giảm nội dung trò chơi của bạn. .

Cuối cùng, tôi sẽ đề nghị giới hạn những gì bạn đang trộn alpha, chỉ những thứ cần nó. Nếu mọi thứ cần nó ... Bạn không có lựa chọn nào khác ngoài việc yêu cầu người dùng của bạn có yêu cầu phần cứng tốt hơn hoặc làm giảm trò chơi để có hiệu suất mong muốn.


-1

Tạo một bảng sprite cho các đối tượng và một tập hợp các ô cho địa hình như bạn trong trò chơi 2D khác, không cần phải chuyển đổi kết cấu.

Gạch kết xuất có thể là một nỗi đau bởi vì mỗi cặp tam giác cần tọa độ kết cấu riêng của họ. Có một giải pháp cho vấn đề này, tuy nhiên, nó được gọi là kết xuất theo bản năng .

Miễn là bạn có thể sắp xếp dữ liệu của mình theo cách sao cho, ví dụ, bạn có thể có một danh sách các ô cỏ và vị trí của chúng, bạn có thể kết xuất mọi ô cỏ với một lệnh gọi rút thăm duy nhất, tất cả những gì bạn phải làm là cung cấp một mảng của mô hình để ma trận thế giới cho mỗi gạch. Sắp xếp dữ liệu của bạn theo cách này không phải là một vấn đề với ngay cả biểu đồ cảnh đơn giản nhất.


-1: Instance là một ý tưởng tồi tệ hơn giải pháp shader thuần túy của Mr. Beast. Instance hoạt động tốt nhất cho hiệu suất khi hiển thị các đối tượng có độ phức tạp vừa phải (~ 100 hình tam giác hoặc hơn). Mỗi ô tam giác cần tọa độ kết cấu không phải là một vấn đề. Bạn chỉ cần tạo một lưới với một loạt các hình tứ giác lỏng lẻo để tạo thành một tilemap.
Nicol Bolas

1
@NicolBolas không sao, tôi sẽ để lại câu trả lời cho mục đích học tập
dreta

1
Để rõ ràng, Nicol Bolas, đề xuất của bạn về cách đối phó với tất cả điều này là gì? Dòng của Marco vẽ điều gì? Có nơi nào tôi có thể thấy một thực hiện này?
Nic

@Nic: Truyền đến các đối tượng bộ đệm không phải là mã đặc biệt phức tạp. Nhưng thực sự, nếu bạn chỉ nói về 50 spites, thì không có gì . Điều lạ là chính bản vẽ địa hình của bạn đã gây ra vấn đề về hiệu suất, do đó, chuyển sang bộ đệm tĩnh cho điều đó có lẽ là đủ tốt.
Nicol Bolas

Trên thực tế, nếu việc vận hành hoạt động như chúng ta nghĩ, nó sẽ là giải pháp tốt nhất - nhưng vì không, nên nướng tất cả các trường hợp vào một vbo tĩnh duy nhất là cách tốt nhất.
Jari Komppa
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.