2008-10-03 26 views
47

Học kỳ này, tôi đã tham gia một khóa học về đồ họa máy tính tại trường đại học của tôi. Hiện tại, chúng tôi đang bắt đầu tham gia một số nội dung nâng cao hơn như bản đồ cao độ, tiêu chuẩn trung bình, tesselation, v.v.Một số phương pháp hay nhất cho mã hóa OpenGL (định hướng đối tượng w.r.t.) là gì?

Tôi đến từ nền hướng đối tượng, vì vậy tôi đang cố gắng đưa mọi thứ vào các lớp tái sử dụng. Tôi đã thành công trong việc tạo ra một lớp máy ảnh, vì nó phụ thuộc chủ yếu vào một cuộc gọi đến gluLookAt(), khá độc lập với phần còn lại của máy trạng thái OpenGL.

Tuy nhiên, tôi đang gặp một số vấn đề với các khía cạnh khác. Sử dụng các đối tượng để biểu diễn nguyên thủy chưa thực sự là một thành công đối với tôi. Điều này là do các cuộc gọi render thực sự phụ thuộc vào rất nhiều thứ bên ngoài, như kết cấu hiện đang bị ràng buộc vv. Nếu bạn đột nhiên muốn thay đổi từ một bề mặt bình thường sang một đỉnh bình thường cho một lớp cụ thể, nó gây ra một cơn đau đầu dữ dội.

Tôi bắt đầu tự hỏi liệu các nguyên tắc OO có được áp dụng trong mã hóa OpenGL hay không. Ít nhất, tôi nghĩ rằng tôi nên làm cho lớp học của tôi ít chi tiết hơn.

Quan điểm của cộng đồng tràn ngăn xếp về điều này là gì? Các phương pháp hay nhất của bạn cho mã hóa OpenGL là gì?

Trả lời

66

Cách tiếp cận thực tế nhất có vẻ là bỏ qua hầu hết chức năng OpenGL không áp dụng trực tiếp (hoặc chậm hoặc không được tăng tốc phần cứng hoặc không còn phù hợp với phần cứng nữa).

OOP hay không, để làm cho một số cảnh đó là những loại khác nhau và các tổ chức mà bạn thường có:

Geometry (mắt lưới). Thông thường, đây là một mảng các đỉnh và một loạt các chỉ mục (tức là ba chỉ số cho mỗi tam giác, còn được gọi là "danh sách tam giác"). Một đỉnh có thể ở một số định dạng tùy ý (ví dụ: chỉ có vị trí float3; vị trí float3 + float3 bình thường; vị trí float3 + float3 bình thường + float2 texcoord; v.v ...). Vì vậy, để xác định một miếng hình học bạn cần:

  • định nghĩa nó là định dạng đỉnh (có thể là một bitmask, một enum từ một danh sách các định dạng; ...),
  • có mảng các đỉnh, với các thành phần của họ xen kẽ ("mảng xen kẽ")
  • có mảng tam giác.

Nếu bạn ở trong vùng đất OOP, bạn có thể gọi lớp này là Lưới.

Vật liệu - những thứ xác định cách một phần hình học được hiển thị. Trong một trường hợp đơn giản nhất, điều này có thể là một màu của đối tượng, ví dụ. Hoặc có nên áp dụng ánh sáng hay không. Hoặc liệu đối tượng có nên được trộn alpha hay không. Hoặc một kết cấu (hoặc một danh sách các kết cấu) để sử dụng. Hoặc một vertex/fragment shader để sử dụng. Và như vậy, khả năng là vô tận. Bắt đầu bằng cách đặt những thứ bạn cần thành tài liệu. Trong đất OOP mà lớp học có thể được gọi (bất ngờ!) A Chất liệu.

Cảnh - bạn có phần hình học, bộ sưu tập tài liệu, thời gian để xác định nội dung trong cảnh. Trong một trường hợp đơn giản, mỗi đối tượng trong cảnh có thể được xác định bởi: - Hình học mà nó sử dụng (con trỏ tới Lưới), - Cách hiển thị nó (trỏ đến Material), - Vị trí của nó. Đây có thể là ma trận chuyển đổi 4x4 hoặc ma trận chuyển đổi 4x3 hoặc véc tơ (vị trí), quaternion (định hướng) và vec-tơ khác (tỷ lệ). Hãy gọi đây là số Nút trong vùng đất OOP.

Máy ảnh. Vâng, một máy ảnh không có gì hơn "nơi nó được đặt" (một lần nữa, một 4x4 hoặc ma trận 4x3, hoặc một vị trí và định hướng), cộng với một số thông số chiếu (lĩnh vực xem, tỷ lệ khía cạnh, ...).

Vì vậy, về cơ bản đó là nó! Bạn có một cảnh là một loạt các Nút tham chiếu đến các Lưới và Vật liệu, và bạn có một Máy ảnh xác định vị trí của người xem.

Hiện tại, nơi thực hiện cuộc gọi OpenGL thực tế chỉ là một câu hỏi thiết kế. Tôi muốn nói, không đặt các cuộc gọi OpenGL vào các lớp Node hoặc Mesh hoặc Material. Thay vào đó, hãy tạo một cái gì đó như OpenGLRenderer có thể duyệt qua cảnh và phát hành tất cả các cuộc gọi. Hoặc, thậm chí tốt hơn, làm cho một cái gì đó đi qua cảnh độc lập của OpenGL, và đưa các cuộc gọi cấp thấp hơn vào lớp phụ thuộc OpenGL.

Vì vậy, tất cả những điều trên là khá nhiều nền tảng độc lập. Đi theo cách này, bạn sẽ thấy rằng glRotate, glTranslate, gluLookAt và bạn bè khá vô dụng. Bạn đã có tất cả các ma trận rồi, chỉ cần chuyển chúng sang OpenGL. Đây là cách mà hầu hết mã thực tế thực tế trong các trò chơi/ứng dụng thực vẫn hoạt động.

Tất nhiên, điều trên có thể phức tạp do yêu cầu phức tạp hơn. Đặc biệt, vật liệu có thể khá phức tạp. Các mắt lưới thường cần hỗ trợ nhiều định dạng đỉnh khác nhau (ví dụ như các tiêu chuẩn đóng gói cho hiệu quả). Scene Nodes có thể cần phải được tổ chức trong một hệ thống phân cấp (điều này có thể được dễ dàng - chỉ cần thêm con trỏ/con trỏ đến nút). Lưới mắt và hình ảnh động nói chung thêm độ phức tạp. Và cứ thế.

Nhưng ý tưởng chính là đơn giản: có Hình học, có Vật liệu, có các đối tượng trong cảnh. Sau đó, một số đoạn mã nhỏ có thể hiển thị chúng.

Trong trường hợp OpenGL, việc thiết lập mắt lưới sẽ rất có khả năng tạo/kích hoạt/sửa đổi các đối tượng VBO. Trước khi bất kỳ nút nào được hiển thị, các ma trận sẽ cần được thiết lập. Và thiết lập Material sẽ chạm hầu hết trạng thái OpenGL còn lại (trộn, texturing, ánh sáng, combiners, shaders, ...).

+0

Ý tưởng của bạn cho lớp lưới có vẻ hiển nhiên đối với tôi ngay bây giờ :) Những gì tôi đã cố gắng làm là sử dụng các đối tượng cho nguyên thủy như hình tam giác. Sử dụng các đối tượng để quản lý mắt lưới có ý nghĩa hơn nhiều, vì chúng có xu hướng khá tự đủ, đúng không? – fluffels

+0

Ngoài ra, cảm ơn rất nhiều cho cái nhìn sâu sắc vào các công cụ độc lập nền tảng và làm cho cây! Điều đó giúp ích rất nhiều! – fluffels

+0

Tôi nghĩ đây là một câu trả lời tuyệt vời! ... Bất cứ ai bắt đầu viết một động cơ 3d nên dựa trên những nguyên tắc này. (Hoặc thậm chí tốt hơn: sử dụng động cơ làm tất cả điều này cho bạn và tiết kiệm thời gian có giá trị) – fho

0

Tôi thường có hàm drawOpenGl(), mỗi lớp có thể được hiển thị, có chứa các cuộc gọi opengl. Hàm đó được gọi từ renderloop. Lớp này chứa mọi thông tin cần thiết cho các cuộc gọi hàm opengl của nó, ví dụ như. về vị trí và định hướng để nó có thể thực hiện chuyển đổi riêng.

Khi các đối tượng phụ thuộc vào nhau, ví dụ: chúng tạo thành một phần của một đối tượng lớn hơn, sau đó soạn các lớp đó trong một lớp khác đại diện cho đối tượng đó. Trong đó có hàm drawOpenGL() của riêng nó gọi tất cả các hàm drawOpenGL() của các con của nó, vì vậy bạn có thể có các cuộc gọi định vị/định hướng xung quanh bằng cách sử dụng push- và popmatrix.

Đã lâu rồi, nhưng tôi đoán có thể có điều tương tự là có thể với họa tiết.

Nếu bạn muốn chuyển đổi giữa các chỉ tiêu bề mặt hoặc tiêu chuẩn đỉnh, thì hãy để đối tượng nhớ cái này hoặc cái kia và có 2 chức năng riêng cho mỗi dịp drawOpenGL() gọi khi cần. Chắc chắn có các giải pháp thanh lịch hơn (ví dụ: sử dụng mẫu thiết kế chiến lược hoặc thứ gì đó), nhưng cách này có thể hoạt động theo như tôi hiểu vấn đề của bạn

+0

Đây là phương pháp tôi hiện đang sử dụng (có hai chức năng riêng tư). Nó không hoàn toàn phù hợp với tôi, vì mỗi lớp tam giác phụ thuộc vào 6 tam giác khác để lấy trung bình bình thường. Bạn cũng khuyên bạn nên mô hình hóa một lưới thay vì nguyên thủy như lớp cơ bản của tôi? – fluffels

+0

sàng cho câu trả lời muộn. có, im suy nghĩ: một lớp lưới bao gồm một danh sách các lớp tam giác và có thể là chức năng cho các chuẩn mực avarage; lớp tam giác có thể tạo ra bình thường của riêng nó. Lưới có thể vẽ hình tam giác. Một nguyên thủy có thể là một lưới với một hình thức cụ thể. –

2

Kỹ thuật tiêu chuẩn là cách nhiệt hiệu ứng của đối tượng trên trạng thái hiển thị của nhau bằng cách thực hiện tất cả thay đổi từ một số trạng thái OpenGL mặc định trong phạm vi glPushAttrib/glPopAttrib. Trong C++ định nghĩa một lớp với constructor chứa

glPushAttrib(GL_ALL_ATTRIB_BITS); 
    glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); 

và destructor chứa

glPopClientAttrib(); 
    glPopAttrib(); 

và sử dụng lớp RAII kiểu quấn bất kỳ mã mà messes với trạng thái OpenGL. Với điều kiện bạn làm theo mẫu, mỗi phương thức render của đối tượng nhận được một "slate sạch" và không cần phải lo lắng về việc thúc đẩy mỗi bit có thể sửa đổi trạng thái OpenGL là những gì nó cần.

Là một tối ưu hóa, thông thường bạn sẽ đặt trạng thái OpenGL khi khởi động ứng dụng vào một số trạng thái gần nhất có thể với những gì mọi thứ muốn; điều này giảm thiểu số lượng cuộc gọi cần thực hiện trong phạm vi được đẩy.

Tin xấu là đây không phải là cuộc gọi giá rẻ. Tôi đã không bao giờ thực sự điều tra bao nhiêu mỗi giây bạn có thể nhận được đi với; chắc chắn đủ để có ích trong những cảnh phức tạp. Điều chính là cố gắng và tận dụng tối đa các tiểu bang khi bạn đã đặt chúng. Nếu bạn có một đội quân Orc để render, với các shader khác nhau, texture, vv cho áo giáp và da, đừng lặp lại tất cả các orc render giáp/da/giáp/da/...; hãy chắc chắn rằng bạn thiết lập trạng thái cho áo giáp một lần và làm cho tất cả giáp của Orc, sau đó thiết lập để làm cho tất cả các da.

+1

Thật lạ lùng khi khởi tạo một đối tượng, gọi hàm render() của nó và sau đó phá hủy nó, chỉ để cách ly trạng thái. Tôi có hiểu đúng không? – fluffels

+0

Xin lỗi, tôi không giải thích rõ điều này. Đối tượng làm push/pop trên trạng thái GL chỉ là một trợ giúp tiện lợi ... không có gì để làm với các đối tượng bạn đang vẽ. Mã sẽ trông giống như sau: có thể hiển thị * điều = mới ...; { gl_pushed_scope p; thing-> render(); } – timday

3

Object biến đổi

Tránh tùy thuộc vào OpenGL để làm biến đổi của bạn. Thông thường, hướng dẫn dạy bạn cách chơi với ngăn xếp ma trận chuyển đổi. Tôi sẽ không khuyên bạn nên sử dụng phương pháp này vì bạn có thể cần một số ma trận sau đó sẽ chỉ có thể truy cập thông qua ngăn xếp này, và sử dụng nó là rất dài kể từ khi xe buýt GPU được thiết kế để được nhanh chóng từ CPU đến GPU nhưng không phải là cách khác.

đối tượng Thạc sĩ

cảnh 3D thường được coi như là một cây của các đối tượng để biết đối tượng phụ thuộc. Có một cuộc tranh luận về những gì nên ở gốc của cây này, một danh sách các đối tượng hoặc một đối tượng chủ.

Tôi khuyên bạn nên sử dụng đối tượng chính. Mặc dù nó không có một biểu diễn đồ họa, nó sẽ đơn giản hơn vì bạn sẽ có thể sử dụng đệ quy hiệu quả hơn.

quản lý cảnh tách và renderer

Tôi không đồng ý với @ejac mà bạn nên có một phương pháp trên từng đối tượng làm các cuộc gọi OpenGL. Việc có một lớp Trình kết xuất riêng biệt duyệt cảnh của bạn và thực hiện tất cả các cuộc gọi OpenGL sẽ giúp bạn tách rời khung cảnh logic và mã OpenGL của mình.

Điều này làm tăng thêm một số khó khăn về thiết kế nhưng sẽ giúp bạn linh hoạt hơn nếu bạn phải thay đổi từ OpenGL thành DirectX hoặc bất kỳ API nào khác có liên quan.

+0

Từ những gì tôi đã thấy, tôi chắc chắn đồng ý với điểm cuối cùng của bạn. Tôi sẽ phải thử mô hình thứ hai, nhưng công việc của tôi không đủ phức tạp để đảm bảo sự độc lập nền tảng (nó chỉ là một vài bài tập thực hành vứt đi). – fluffels

1

nếu bạn muốn cuộn các câu trả lời ở trên của bạn hoạt động tốt. Rất nhiều nguyên tắc được đề cập được thực hiện trong hầu hết các công cụ đồ họa nguồn mở. Scenegraphs là một phương pháp để di chuyển ra khỏi bản vẽ chế độ trực tiếp.

OpenScenegraph là một ứng dụng nguồn mở cung cấp cho bạn thư viện lớn (có thể quá lớn) của các công cụ để thực hiện đồ họa OO 3D, có rất nhiều thứ khác ngoài đó.

+0

Cảm ơn bạn đã tham khảo, tôi chắc chắn sẽ kiểm tra lại sau. Thật không may, các giảng viên của chúng tôi là một draconian nhỏ và chúng tôi không thể sử dụng các công cụ của bên thứ 3 ... :( – fluffels

+0

Đây là cách tiếp cận mà Đồ họa máy tính tương tác của Edward Angel hướng tới lập trình đồ họa OOP. Câu trả lời đúng là – fluffels

+0

Cảnh báo có những nhược điểm của chúng, điều này rất được khuyến khích đọc http://home.comcast.net/~tom_forsyth/blog.wiki.html#%5B%5BScene%20Graphs%20-%20just%20say % 20no% 5D% 5D – kert

Các vấn đề liên quan