Đường ống kết xuất động cơ: Làm cho shader chung


10

Tôi đang cố gắng tạo một công cụ trò chơi 2D bằng OpenGL ES 2.0 (iOS bây giờ). Tôi đã viết lớp Ứng dụng trong Mục tiêu C và một RendererGLES20 riêng chứa trong C ++. Không có cuộc gọi cụ thể GL được thực hiện bên ngoài trình kết xuất. Nó đang hoạt động hoàn hảo.

Nhưng tôi có một số vấn đề thiết kế khi sử dụng shader. Mỗi shader có các thuộc tính và đồng phục riêng cần được đặt ngay trước lệnh gọi rút thăm chính (glDrawArrays trong trường hợp này). Ví dụ, để vẽ một số hình học tôi sẽ làm:

void RendererGLES20::render(Model * model)
{
    // Set a bunch of uniforms
    glUniformMatrix4fv(.......);
    // Enable specific attributes, can be many
    glEnableVertexAttribArray(......);
    // Set a bunch of vertex attribute pointers:
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);

    // Now actually Draw the geometry
    glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);

    // After drawing, disable any vertex attributes:
    glDisableVertexAttribArray(.......);
}

Như bạn có thể thấy mã này là vô cùng cứng nhắc. Nếu tôi sử dụng một shader khác, giả sử hiệu ứng gợn, tôi sẽ cần phải truyền thêm đồng phục, attribs đỉnh, v.v. Nói cách khác, tôi sẽ phải thay đổi mã nguồn kết xuất RendererGLES20 chỉ để kết hợp với shader mới.

Có cách nào để làm cho đối tượng shader hoàn toàn chung chung không? Giống như Điều gì xảy ra nếu tôi chỉ muốn thay đổi đối tượng đổ bóng và không lo lắng về việc biên dịch lại nguồn trò chơi? Bất kỳ cách nào để làm cho trình kết xuất không biết về đồng phục và thuộc tính, v.v.? Mặc dù chúng ta cần truyền dữ liệu cho đồng phục, đâu là nơi tốt nhất để làm điều đó? Lớp người mẫu? Là lớp mô hình nhận thức về đồng phục và thuộc tính cụ thể của shader?

Chương trình sau đây Lớp diễn viên:

class Actor : public ISceneNode
{
    ModelController * model;
    AIController * AI;
};

Lớp trình điều khiển mô hình: lớp ModelContoder {class IShader * shader; int textureId; vec4 tint; phao alpha; cấu trúc Vertex * vertexArray; };

Lớp shader chỉ chứa đối tượng shader, biên dịch và liên kết các thường trình con, v.v.

Trong lớp Game Logic tôi thực sự kết xuất đối tượng:

void GameLogic::update(float dt)
{
    IRenderer * renderer = g_application->GetRenderer();

    Actor * a = GetActor(id);
    renderer->render(a->model);
}

Xin lưu ý rằng mặc dù Diễn viên mở rộng ISceneNode, tôi vẫn chưa bắt đầu triển khai SceneGraph. Tôi sẽ làm điều đó ngay khi tôi giải quyết vấn đề này.

Bất kỳ ý tưởng làm thế nào để cải thiện điều này? Mẫu thiết kế liên quan vv?

Cảm ơn bạn đã đọc câu hỏi.



Tôi không chắc đó có phải là một bản sao chính xác hay không, nhưng nó đúng với những gì bạn đang cố gắng thực hiện, tôi nghĩ vậy.
Sean Middleditch

Câu trả lời:


12

Bạn có thể làm cho hệ thống đổ bóng của mình được điều khiển dữ liệu nhiều hơn, do đó bạn không có quá nhiều mã dành riêng cho trình tạo bóng cho các định dạng đồng phục và đỉnh, mà thay vào đó, hãy đặt chúng theo lập trình dựa trên siêu dữ liệu được gắn vào trình đổ bóng.

Đầu tiên từ chối trách nhiệm: một hệ thống điều khiển dữ liệu có thể giúp dễ dàng thêm các trình đổ bóng mới, nhưng mặt khác, nó đi kèm với chi phí về sự phức tạp gia tăng của hệ thống, khiến cho việc bảo trì và gỡ lỗi khó khăn hơn. Vì vậy, nên suy nghĩ cẩn thận về việc điều khiển dữ liệu sẽ tốt cho bạn bao nhiêu (đối với một dự án nhỏ, câu trả lời có thể là "không"), và đừng cố gắng xây dựng một hệ thống quá khái quát.

Được rồi, trước tiên hãy nói về các định dạng đỉnh (thuộc tính). Bạn có thể tạo một mô tả dữ liệu bằng cách tạo một cấu trúc có chứa dữ liệu để truyền đến glVertexAttribPointer- chỉ mục, loại, kích thước, v.v. của một thuộc tính duy nhất - và có một mảng các cấu trúc đó để biểu thị toàn bộ định dạng đỉnh. Đưa ra thông tin này, bạn có thể lập trình thiết lập tất cả trạng thái GL liên quan đến các thuộc tính đỉnh.

Trường hợp dữ liệu để điền mô tả này đến từ đâu? Về mặt khái niệm, tôi nghĩ cách sạch nhất là để nó thuộc về shader. Khi bạn xây dựng dữ liệu đỉnh cho lưới, bạn sẽ tìm kiếm shader nào được sử dụng trên lưới, tìm định dạng đỉnh theo yêu cầu của shader đó và xây dựng bộ đệm đỉnh tương ứng. Bạn chỉ cần một số cách để mỗi shader xác định định dạng đỉnh mà nó mong đợi.

Có nhiều cách khác nhau để làm điều này; chẳng hạn, bạn có thể có một bộ tên tiêu chuẩn cho các thuộc tính trong shader ("attrPocation", "attrN normal", v.v.) cùng với một số quy tắc được mã hóa cứng như "vị trí là 3 float". Sau đó, bạn sử dụng glGetAttribLocationhoặc tương tự để truy vấn thuộc tính nào mà trình đổ bóng sử dụng và áp dụng các quy tắc để xây dựng định dạng đỉnh. Một cách khác là có một đoạn mã XML xác định định dạng, được nhúng trong một nhận xét trong nguồn đổ bóng và được trích xuất bởi các công cụ của bạn hoặc một cái gì đó dọc theo các dòng đó.

Đối với đồng phục, nếu bạn có thể sử dụng OpenGL 3.1 trở lên thì nên sử dụng các đối tượng bộ đệm thống nhất (tương đương OpenGL với bộ đệm không đổi của D3D). Than ôi, GL ES 2.0 không có những cái đó, vì vậy đồng phục phải được xử lý riêng. Một cách để làm điều đó là tạo ra một cấu trúc có chứa vị trí thống nhất cho từng tham số bạn muốn đặt - ma trận camera, công suất đầu cơ, ma trận thế giới, v.v. Cách tiếp cận này phụ thuộc vào việc có một bộ thông số tiêu chuẩn được chia sẻ trên tất cả các shader. Không phải mọi shader đều phải sử dụng mọi tham số đơn lẻ, nhưng tất cả các tham số sẽ phải nằm trong cấu trúc này.

Mỗi shader sẽ có một thể hiện của cấu trúc này và khi bạn tải một shader bạn sẽ truy vấn nó cho các vị trí của tất cả các tham số, sử dụng glGetUniformLocationvà các tên được tiêu chuẩn hóa. Sau đó, bất cứ khi nào bạn cần đặt đồng phục từ mã, bạn có thể kiểm tra xem nó có trong trình đổ bóng đó hay không, và chỉ cần tìm vị trí của nó và đặt nó.

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.