Lớp trừu tượng tốt hơn cho quản lý dữ liệu đỉnh D3D9 và OpenGL là gì?


8

Mã kết xuất của tôi luôn là OpenGL. Bây giờ tôi cần hỗ trợ một nền tảng không có OpenGL, vì vậy tôi phải thêm một lớp trừu tượng bao bọc OpenGL và Direct3D 9. Tôi sẽ hỗ trợ Direct3D 11 sau.

TL; DR: sự khác biệt giữa OpenGL và Direct3D gây ra sự dư thừa cho lập trình viên và bố cục dữ liệu cảm thấy không ổn định.

Hiện tại, API của tôi hoạt động giống như thế này. Đây là cách tạo bóng đổ:

Shader *shader = Shader::Create(
    " ... GLSL vertex shader ... ", " ... GLSL pixel shader ... ",
    " ... HLSL vertex shader ... ", " ... HLSL pixel shader ... ");
ShaderAttrib a1 = shader->GetAttribLocation("Point", VertexUsage::Position, 0);
ShaderAttrib a2 = shader->GetAttribLocation("TexCoord", VertexUsage::TexCoord, 0);
ShaderAttrib a3 = shader->GetAttribLocation("Data", VertexUsage::TexCoord, 1);
ShaderUniform u1 = shader->GetUniformLocation("WorldMatrix");
ShaderUniform u2 = shader->GetUniformLocation("Zoom");

Đã có một vấn đề ở đây: một khi trình tạo bóng Direct3D được biên dịch, không có cách nào để truy vấn một thuộc tính đầu vào bằng tên của nó; Rõ ràng chỉ có ngữ nghĩa là có ý nghĩa. Đây là lý do tại sao GetAttribLocationcó những đối số bổ sung, được ẩn trong ShaderAttrib.

Bây giờ đây là cách tôi tạo một khai báo đỉnh và hai bộ đệm đỉnh:

VertexDeclaration *decl = VertexDeclaration::Create(
        VertexStream<vec3,vec2>(VertexUsage::Position, 0,
                                VertexUsage::TexCoord, 0),
        VertexStream<vec4>(VertexUsage::TexCoord, 1));

VertexBuffer *vb1 = new VertexBuffer(NUM * (sizeof(vec3) + sizeof(vec2));
VertexBuffer *vb2 = new VertexBuffer(NUM * sizeof(vec4));

Một vấn đề khác: thông tin VertexUsage::Position, 0hoàn toàn vô dụng đối với phụ trợ OpenGL / GLSL vì nó không quan tâm đến ngữ nghĩa.

Khi bộ đệm đỉnh đã được điền hoặc trỏ vào dữ liệu, đây là mã kết xuất:

shader->Bind();
shader->SetUniform(u1, GetWorldMatrix());
shader->SetUniform(u2, blah);
decl->Bind();
decl->SetStream(vb1, a1, a2);
decl->SetStream(vb2, a3);
decl->DrawPrimitives(VertexPrimitive::Triangle, NUM / 3);
decl->Unbind();
shader->Unbind();

Bạn thấy đó decllà một chút nhiều hơn chỉ là một khai báo đỉnh giống như D3D, nó cũng rất quan tâm đến việc kết xuất. Điều này có ý nghĩa gì không? Điều gì sẽ là một thiết kế sạch hơn? Hay một nguồn cảm hứng tốt?


Phiên bản OpenGL nào bạn đang nhắm mục tiêu?
Nicol Bolas

@NicolBolas hiện tại tôi sử dụng OpenGL 2.1 và OpenGL ES 2.0 và tôi dự định hỗ trợ OpenGL 3.3 hoặc 4.0, nhưng tôi chưa quyết định liệu tôi sẽ bỏ hỗ trợ cho các phiên bản trước hay không. Vấn đề hiện tại của tôi là tôi cũng sử dụng một tập hợp con OpenGL cũ trên PS3, nó không tối ưu nhưng khá tiện lợi
sam hocevar

Có lẽ bạn đã biết về điều này, nhưng hãy xem nguồn của Ogre để xem họ đã làm như thế nào ogre3d.org
Aralox

4
@Aralox: OGRE là một mớ hỗn độn của Singleton và tôi sẽ không bao giờ khuyên ai nên làm theo thiết kế của họ.
DeadMG

Câu trả lời:


8

Về cơ bản, bạn đang gặp phải tình huống khiến NVIDIA Cg trở thành một phần mềm hấp dẫn như vậy (ngoài thực tế là nó không hỗ trợ GL | ES, mà bạn nói bạn đang sử dụng).

Cũng lưu ý rằng bạn thực sự không nên sử dụng glGetAttribLocation. Chức năng đó là juju tồi từ những ngày đầu của GLSL trước khi những người phụ trách GL thực sự bắt đầu tìm hiểu làm thế nào một ngôn ngữ tạo bóng tốt nên hoạt động. Nó không bị phản đối vì nó thường được sử dụng, nhưng nói chung, thích glBindAttibLocation hoặc phần mở rộng vị trí thuộc tính rõ ràng (cốt lõi trong GL 3.3+).

Đối phó với sự khác biệt trong các ngôn ngữ đổ bóng là phần khó nhất trong phần mềm chuyển giữa GL và D3D. Các vấn đề API mà bạn gặp phải liên quan đến định nghĩa bố cục đỉnh cũng có thể được xem như là một vấn đề về ngôn ngữ đổ bóng, vì các phiên bản GLSL trước 3.30 không hỗ trợ vị trí thuộc tính rõ ràng (tương tự về ngữ nghĩa thuộc tính trong các phiên bản HLSL) và các phiên bản GLSL trước đây 4.10 iirc không hỗ trợ các ràng buộc thống nhất rõ ràng.

Cách tiếp cận "tốt nhất" là có một thư viện ngôn ngữ và định dạng dữ liệu tô bóng mức cao, đóng gói các gói đổ bóng của bạn. KHÔNG chỉ đơn giản cung cấp một bó GLSL / HLSL thô cho một lớp Shader mỏng và mong muốn có thể đưa ra bất kỳ loại API lành mạnh nào.

Thay vào đó, đặt shader của bạn ra thành một tập tin. Gói chúng trong một chút dữ liệu meta. Bạn có thể sử dụng XML và viết các gói shader như:

<shader name="bloom">
  <profile type="glsl" version="1.30">
    <source type="vertex"><![CDATA[
      glsl vertex shader code goes here
    ]]></source>
    <source type="fragment"><![CDATA[
      glsl fragment shader code goes here
    ]]></source>
  </profile>
  <profile type="hlsl" version="sm3">
    <source type="fx"><![CDATA[
      hlsl effects code goes here
      you could also split up the source elements for hlsl
    ]]></source>
  </profile>
</shader>

Viết một trình phân tích cú pháp tối thiểu cho việc đó là chuyện nhỏ (chỉ cần sử dụng TinyXML chẳng hạn). Hãy để thư viện shader của bạn tải lên gói đó, chọn cấu hình phù hợp cho trình kết xuất mục tiêu hiện tại của bạn và biên dịch các shader.

Cũng lưu ý rằng nếu bạn thích, bạn có thể giữ nguồn bên ngoài theo định nghĩa shader, nhưng vẫn có tệp. Chỉ cần đặt tên tệp thay vì nguồn vào các thành phần nguồn. Điều này có thể có lợi nếu bạn có kế hoạch biên dịch trước các shader.

Phần khó bây giờ tất nhiên là xử lý GLSL và những thiếu sót của nó. Vấn đề là bạn cần liên kết các vị trí thuộc tính với một cái gì đó giống với ngữ nghĩa của HLSL. Điều này có thể được thực hiện bằng cách xác định các ngữ nghĩa đó trong API của bạn và sau đó sử dụng glBindAttribLocation trước khi liên kết cấu hình GLSL. Khung gói shader của bạn có thể xử lý việc này một cách rõ ràng, hoàn toàn không cần API đồ họa của bạn để lộ chi tiết.

Bạn có thể làm điều đó bằng cách mở rộng định dạng XML ở trên với một số thành phần mới trong cấu hình GLSL để chỉ định rõ ràng các vị trí thuộc tính, ví dụ:

<shader name="bloom">
  <profile type="glsl" version="1.30">
    <attrib name="inPosition" semantic="POSITION"/>
    <attrib name="inColor" semantic="COLOR0"/>
    <source type="vertex"><![CDATA[
      #version 150
      in vec4 inPosition;
      in vec4 inColor;

      out vec4 vColor;

      void main() {
        vColor = inColor;
        gl_Position = position;
      }
    ]]></source>
  </profile>
</shader>

Mã gói shader của bạn sẽ đọc tất cả các phần tử attrib trong XML, lấy tên và ngữ nghĩa từ chúng, tra cứu chỉ mục thuộc tính được xác định trước cho từng ngữ nghĩa và sau đó tự động gọi glBindAttribLocation cho bạn khi liên kết trình đổ bóng.

Kết quả cuối cùng là API của bạn bây giờ có thể cảm thấy đẹp hơn nhiều so với mã GL cũ của bạn có thể đã từng nhìn và thậm chí sạch hơn một chút so với D3D11 sẽ cho phép:

// simple example, easily improved
VertexLayout layout = api->createLayout();
layout.bind(gfx::POSITION, buffer0, gfx::FLOATx4, sizeof(Vertex), offsetof(Vertex, position));
layout.bind(gfx::COLOR0, buffer0, gfx::UBYTEx4, sizeof(Vertex), offsetof(Vertex, color));

Cũng lưu ý rằng bạn không thực sự cần định dạng gói shader. Nếu bạn muốn đơn giản hóa mọi thứ, bạn chỉ cần có một loại hàm loadShader (const char * name) tự động lấy các tệp name.vs và name.fs GLSL trong chế độ GL và biên dịch và liên kết chúng. Tuy nhiên, bạn hoàn toàn sẽ muốn siêu dữ liệu thuộc tính đó. Trong trường hợp đơn giản, bạn có thể tăng mã GLSL của mình bằng các nhận xét đặc biệt dễ phân tích, như:

#version 150

/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;

out vec4 vColor

void main() {
  vColor = inColor;
  gl_Position = inPosition;
}

Bạn có thể thấy lạ mắt như bạn thấy thoải mái khi phân tích nhận xét. Hơn một vài công cụ chuyên nghiệp sẽ đi xa đến mức tạo ra các phần mở rộng ngôn ngữ nhỏ mà chúng phân tích và sửa đổi, thậm chí, chẳng hạn như chỉ thêm hoàn toàn các khai báo ngữ nghĩa theo kiểu HLSL. Nếu kiến ​​thức về phân tích cú pháp của bạn mạnh mẽ, bạn sẽ có thể tin cậy tìm thấy các khai báo mở rộng đó, trích xuất thông tin bổ sung và sau đó thay thế văn bản bằng mã tương thích GLSL.

Bất kể bạn làm điều đó như thế nào, phiên bản ngắn là để tăng GLSL của bạn với thông tin ngữ nghĩa thuộc tính bị thiếu và có thỏa thuận trừu tượng trình tải shader của bạn với việc gọi glBindAttribLocation để sửa chữa mọi thứ và làm cho chúng giống với các phiên bản GLSL và HLSL hiện đại dễ dàng và hiệu quả.


Cảm ơn cho một câu trả lời cực kỳ toàn diện. Gợi ý bổ sung về các bình luận ngữ nghĩa là đơn giản nhưng có ý nghĩa rất nhiều!
sam hocevar

Cuối cùng tôi cũng chấp nhận câu trả lời của bạn, ngay cả khi những người khác đã tỏ ra rất hữu ích. Tôi đã dành rất nhiều thời gian để suy nghĩ làm thế nào để làm điều đó đúng và cuối cùng tôi đã viết một trình phân tích cú pháp GLSL / HLSL đầy đủ giúp tôi mô phỏng vị trí thuộc tính rõ ràng khi không được hỗ trợ.
sam hocevar

5

Thứ nhất, tôi khuyên bạn nên sử dụng VertexBuffer<T>để cải thiện an toàn loại, nhưng thứ hai, tôi nghĩ rằng sự khác biệt giữa hai API là quá nhiều ở cấp độ này. Cá nhân tôi sẽ đóng gói đầy đủ các trình kết xuất phía sau một giao diện không xử lý các thứ như khai báo đỉnh hoặc đặt các thuộc tính shader.


Biệt phái; lớp trừu tượng của bạn hiện ở mức quá thấp và cần phải cao hơn để thực sự có thể đối phó với sự khác biệt của API.
Maximus Minimus

2

Cá nhân, tôi sẽ thiết lập (và thi hành) một quy ước được tiêu chuẩn hóa cho các chỉ số thuộc tính. Chỉ số GL 0 là vị trí. Chỉ số GL 1 là màu. Chỉ số 2 là bình thường, với 3 và 4 cho tiếp tuyến và nhị phân (nếu cần). Chỉ số 5-7 là tọa độ kết cấu. Có lẽ 8 và 9 là dành cho trọng lượng xương. 10 có thể là màu thứ hai nếu cần. Nếu bạn không thể sử dụng GL_ARB_explicit_attrib_locationhoặc GL 3.3+, thì bạn cũng nên thiết lập đặt tên thuộc tính được tiêu chuẩn hóa quy ước .

Theo cách đó, D3D có các quy ước và OpenGL có các quy ước. Vì vậy, người dùng thậm chí không phải hỏi chỉ số của "vị trí" là gì; họ biết đó là 0. Và sự trừu tượng của bạn biết rằng 0 có nghĩa là, trong vùng đất D3D , VertexUsage::Position.

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.