Objektorientiertes OpenGL

12

Ich benutze OpenGL seit einer Weile und habe eine große Anzahl von Tutorials gelesen. Abgesehen von der Tatsache, dass viele von ihnen immer noch die feste Pipeline verwenden, werden in der Regel alle Initialisierungen, Statusänderungen und Zeichnungen in einer Quelldatei gespeichert. Dies ist in Ordnung für den begrenzten Umfang eines Tutorials, aber ich habe Mühe, herauszufinden, wie ich es auf ein vollständiges Spiel hochskalieren kann.

Wie verteilen Sie Ihre OpenGL-Nutzung auf mehrere Dateien? Konzeptionell kann ich die Vorteile einer Rendering-Klasse erkennen, die lediglich das Rendern von Objekten auf dem Bildschirm ermöglicht. Aber wie funktionieren Objekte wie Shader und Lichter? Sollte ich getrennte Klassen für Dinge wie Licht und Schatten haben?

Sullivan
quelle
1
Es gibt Open-Source-Spiele sowie größere Tutorials mit mehr Code. Schauen Sie sich einige davon an und sehen Sie, wie der Code organisiert ist.
MichaelHouse
Sind Sie ein Anfänger, wenn es um das Rendern von Engines geht?
Samaursa
Diese Frage kann nicht vernünftigerweise beantwortet werden, ohne den Anwendungsbereich einzugrenzen. Was versuchst du zu bauen? Welche Art von Spiel, welche Art von Grafik usw. hat es?
Nicol Bolas
1
@Sullivan: "Ich dachte, dass diese Art von Konzepten für fast jedes Spiel gelten würde." Sie tun es nicht. Wenn Sie mich bitten, Tetris zu machen, werde ich mich nicht darum kümmern, eine große Wrapper-Ebene oder etwas um OpenGL herum zu machen. Verwenden Sie es einfach direkt. Wenn Sie mich bitten, so etwas wie Mass Effect zu erstellen, benötigen wir mehrere Abstraktionsebenen um unser Rendering-System. Wenn Sie den unwirklichen Motor auf Tetris werfen, verwenden Sie eine Schrotflinte, um Fliegen zu jagen. Es macht die Aufgabe so viel schwieriger, als sie direkt von Hand zu codieren. Sie sollten ein System nur so groß und komplex bauen, wie Sie es benötigen , und nicht größer.
Nicol Bolas
1
@Sullivan: Die einzige Komplexität, die Ihre Frage impliziert, ist, wo Sie über das Aufteilen auf mehrere Dateien sprechen. Das würde ich auch in Tetris tun. Und es gibt viele Komplexitätsstufen zwischen Tetris und der Unreal-Engine. Also nochmal: nach welchem ​​fragst du?
Nicol Bolas

Antworten:

6

Ich denke OO OpenGL ist das nicht nötig. Anders ist es, wenn Sie über Shader, Model usw. sprechen.

Grundsätzlich würden Sie zuerst die Game / Engine-Initialisierung (und andere Dinge) durchführen. Anschließend laden Sie Texturen, Modelle und Shader in den Arbeitsspeicher (falls erforderlich) und in Pufferobjekte und laden Shader hoch / kompilieren sie. Danach haben Sie in Ihrer Datenstruktur oder Klasse von Shadern, Modellen , Int- IDs von Shadern, Modellen und Texturpufferobjekten.

Ich denke, die meisten Motoren haben Motorkomponenten und jeder von ihnen hat bestimmte Schnittstellen. Alle von mir untersuchten Engines enthalten Komponenten wie Renderer oder SceneManager oder beides (abhängig von der Komplexität des Spiels / der Engine). Dann können Sie OpenGLRenderer- Klasse und / oder DXRenderer haben, die Renderer-Schnittstelle implementieren. Wenn Sie über SceneManager und Renderer verfügen , können Sie folgende Aktionen ausführen :

  • Traverse Scenemanager und jedes Objekt schicken Renderer für Rendering
  • Kapselung von Großbuchstaben in einigen SceneManager- Methoden
  • Senden Sie SceneManager an den Renderer, damit er damit umgehen kann

Der Renderer würde wahrscheinlich die Zeichenfunktion des Objekts aufrufen, die die Zeichenfunktion jedes Netzes aufruft, aus dem das Netz besteht, und Texturobjekt binden, Shader binden, OpenGL-Zeichenfunktion aufrufen und dann Shader-, Textur- und Objektdatenpufferobjekte nicht verwenden.

HINWEIS: Dies ist nur ein Beispiel. Sie sollten SceneManager genauer studieren und Ihren Anwendungsfall analysieren, um die beste Implementierungsoption zu ermitteln

Sie würden natürlich andere Komponenten der Engine haben, wie MemoryManager , ResourceLoader usw., die sich sowohl um die Video- als auch die RAM-Speichernutzung kümmern, sodass sie nach Bedarf bestimmte Modelle / Shader / Texturen laden / entladen könnten. Zu den Konzepten hierfür gehören Memory Caching, Memory Mapping usw. usw. Zu den einzelnen Komponenten gibt es zahlreiche Details und Konzepte.

Schauen Sie sich eine detailliertere Beschreibung anderer Spiele-Engines an, es gibt viele davon und die Dokumentation ist ziemlich gut verfügbar.

Aber ja, der Unterricht erleichtert das Leben. Sie sollten sie vollständig verwenden und sich an Kapselung, Vererbung, Schnittstellen und weitere interessante Dinge erinnern.

edin-m
quelle
Dies ist die Antwort, nach der ich gesucht habe. Wenn Sie jedoch sagen: "... laden Sie Texturen, Modelle und Shader (falls erforderlich) und Pufferobjekte in den Arbeitsspeicher und laden / kompilieren Sie Shader ...", kehren Sie zu meiner ursprünglichen Frage zurück. Soll ich Objekte verwenden, um Dinge wie Modelle und Shader zu kapseln?
Sullivan
object ist eine Instanz einer Klasse, und 'Sie sollten sie vollständig verwenden'.
edin-m
Entschuldigung, ich wollte fragen, wie ich Objekte verwenden soll. Mein Fehler.
Sullivan
Nun, es hängt von Ihrem Klassenentwurf ab. Ein grundlegendes Beispiel wäre intuitiv: object3d (class) hat meshes (class); Jedes Netz hat Material (Klasse); Jedes Material hat eine Textur (kann eine Klasse sein oder nur eine ID) und einen Shader (eine Klasse). Beim Lesen der einzelnen Komponenten werden Sie jedoch feststellen, dass SceneManager, Renderer und MemoryManager auf unterschiedliche, aber gleichberechtigte Weise erstellt werden (dies können einzelne oder mehrere Klassen sein, geerbt usw. usw.). Dutzende Bücher sind zu diesem Thema geschrieben (Game-Engine-Architektur), und um Fragen im Detail zu beantworten, könnte man eines schreiben.
edin-m
Ich denke, das ist die beste Antwort, die ich bekommen werde. Vielen Dank für Ihre Hilfe :)
Sullivan
2

OpenGL enthält bereits einige 'Object'-Konzepte.

Zum Beispiel kann alles mit einer ID als Objekt durch sein (es gibt auch Dinge, die speziell als "Objekte" bezeichnet werden). Puffer, Texturen, Vertex-Pufferobjekte, Vertex-Array-Objekte, Frame-Pufferobjekte usw. Mit ein wenig Arbeit können Sie Klassen um sie wickeln. Es gibt Ihnen auch eine einfache Möglichkeit, auf veraltete OpenGL-Funktionen zurückzugreifen, wenn Ihr Kontext die Erweiterungen nicht unterstützt. Beispielsweise könnte ein VertexBufferObject auf die Verwendung von glBegin (), glVertex3f () usw. zurückgreifen.

Es gibt einige Möglichkeiten, sich von den herkömmlichen OpenGL-Konzepten zu entfernen. Beispielsweise möchten Sie wahrscheinlich Metadaten zu den Puffern in den Pufferobjekten speichern. Zum Beispiel, wenn der Puffer Eckpunkte speichert. Was ist das Format der Eckpunkte (dh Position, Normalen, Texkoordinaten usw.)? Welche Grundelemente werden verwendet (GL_TRIANGLES, GL_TRIANGLESTRIP usw.), Größeninformationen (wie viele Floats sind gespeichert, wie viele Dreiecke repräsentieren sie usw.). Nur um es einfacher zu machen, sie in die Befehle zum Zeichnen von Arrays zu stecken.

Ich empfehle Ihnen, sich OGLplus anzuschauen . Es sind C ++ - Bindungen für OpenGL.

Auch glxx , das ist allerdings nur für das Laden von Erweiterungen.

Zusätzlich zum Umschließen der OpenGL-API sollten Sie einen etwas höheren Build darüber erstellen.

Zum Beispiel eine Materialmanagerklasse, die für alle Ihre Shader verantwortlich ist, die sie laden und verwenden. Es wäre auch verantwortlich für die Übertragung von Eigenschaften an sie. Auf diese Weise können Sie einfach Folgendes aufrufen: materials.usePhong (); material.setTexture (sometexture); material.setColor (). Dies ermöglicht mehr Flexibilität, da Sie neuere Dinge wie gemeinsame einheitliche Pufferobjekte verwenden können, um nur einen großen Puffer mit allen Eigenschaften, die Ihre Shader verwenden, in einem Block zu haben. Falls dies nicht unterstützt wird, können Sie auf das Hochladen in jedes Shaderprogramm zurückgreifen. Sie können einen großen monolithischen Shader verwenden und mithilfe einheitlicher Routinen zwischen verschiedenen Shader-Modellen wechseln, sofern dies unterstützt wird, oder auf mehrere kleine Shader zurückgreifen.

Sie können auch die Ausgaben in den GLSL-Spezifikationen zum Schreiben Ihres Shader-Codes überprüfen. Zum Beispiel wäre #include unglaublich nützlich und sehr einfach in Ihren Shader-Lade-Code zu implementieren (es gibt auch eine ARB-Erweiterung dafür). Sie können Ihren Code auch im laufenden Betrieb generieren, indem Sie festlegen, welche Erweiterungen unterstützt werden. Verwenden Sie beispielsweise ein gemeinsam genutztes einheitliches Objekt oder greifen Sie auf normale Uniformen zurück.

Schließlich möchten Sie eine übergeordnete Rendering-Pipeline-API, die Szenengraphen, Spezialeffekte (Unschärfe, Glühen) und Dinge, die mehrere Rendering-Durchgänge erfordern, wie Schatten, Beleuchtung und dergleichen, ausführt. Hinzu kommt eine Spiel-API, die nichts mit der Grafik-API zu tun hat, sondern sich nur mit Objekten in einer Welt befasst.

David C. Bishop
quelle
2
"Ich empfehle Ihnen, sich OGLplus anzuschauen. Es sind C ++ - Bindungen für OpenGL." Ich würde empfehlen , gegen die. Ich liebe RAII, aber es ist für die meisten OpenGL-Objekte völlig ungeeignet. OpenGL-Objekte sind einem globalen Konstrukt zugeordnet: dem OpenGL-Kontext. Sie können diese C ++ - Objekte also erst dann erstellen, wenn Sie über einen Kontext verfügen, und Sie können diese Objekte nicht löschen, wenn der Kontext bereits zerstört wurde. Außerdem entsteht die Illusion, dass Sie Objekte ändern können, ohne den globalen Status zu ändern. Dies ist eine Lüge (es sei denn, Sie verwenden EXT_DSA).
Nicol Bolas
@NicolBolas: Konnte ich nicht überprüfen, ob es einen Kontext gibt und erst dann initialisieren? Oder erlauben Sie vielleicht nur Managern, die Objekte zu erstellen, die einen OpenGL-Kontext erfordern? --- Übrigens, tolle Tutorials (Link in deinem Profil)! Irgendwie bin ich bei der Suche nach OpenGL / Graphics nie auf sie gestoßen.
Samaursa
@ Samaursa: Zu welchem ​​Zweck? Wenn Sie einen Manager für OpenGL-Objekte haben, müssen sich Objekte nicht wirklich um sich selbst kümmern. Der Manager kann es für sie tun. Und wenn Sie sie nur dann initialisieren, wenn ein Kontext vorhanden ist, was passiert, wenn Sie versuchen, ein Objekt zu verwenden, das nicht ordnungsgemäß initialisiert wurde? Es schafft nur Schwachstellen in Ihrer Codebasis. Es ändert auch nichts, was ich über die Fähigkeit gesagt habe, diese Objekte zu ändern, ohne den globalen Zustand zu berühren.
Nicol Bolas
@NicolBolas: "Sie können diese C ++ - Objekte erst erstellen, wenn Sie einen Kontext haben" ... unterscheidet sich das von der Verwendung von OpenGL-Konstrukten? In meinen Augen oglplus::Contextmacht die Klasse diese Abhängigkeit sehr gut sichtbar - wäre das ein Problem? Ich glaube, es wird neuen OpenGL-Benutzern helfen, viele Probleme zu vermeiden.
xtofl
1

In modernem OpenGL können Sie gerenderte Objekte mit verschiedenen Vaos- und Shader-Programmen fast vollständig voneinander trennen. Und selbst die Implementierung eines Objekts kann in viele Abstraktionsschichten unterteilt werden.

Wenn Sie beispielsweise ein Terrain implementieren möchten, können Sie ein TerrainMesh definieren, dessen Konstruktor die Scheitelpunkte und Indizes für das Terrain erstellt, sie in Array-Puffer setzt und - wenn Sie ihm eine Attributposition zuweisen - Ihre Daten schattiert. Es sollte auch wissen, wie es gerendert wird, und es sollte darauf achten, alle Kontextänderungen, die es zum Einrichten des Renderns vorgenommen hat, rückgängig zu machen. Diese Klasse selbst sollte nichts über das Shader-Programm wissen, mit dem sie gerendert wird, und sie sollte auch nichts über andere Objekte in der Szene wissen. Über dieser Klasse können Sie ein Terrain definieren, das den Shader-Code kennt, und dessen Aufgabe es ist, die Verbindung zwischen dem Shader und TerrainMesh herzustellen. Dies sollte bedeuten, Attribute und einheitliche Positionen zu erhalten und Texturen und ähnliches zu laden. Diese Klasse sollte nichts darüber wissen, wie das Terrain implementiert ist, welchen LoD-Algorithmus sie verwendet, sie ist nur für die Schattierung des Terrains verantwortlich. Darüber können Sie die Nicht-OpenGL-Funktionalität wie Verhalten und Kollisionserkennung definieren.

Kommen wir zum Punkt, obwohl OpenGL für die Verwendung auf niedriger Ebene konzipiert ist, können Sie dennoch unabhängige Abstraktionsebenen erstellen, mit denen Sie Anwendungen mit der Größe eines unwirklichen Spiels skalieren können. Die Anzahl der gewünschten / benötigten Ebenen hängt jedoch von der Größe der gewünschten Anwendung ab.

Aber lügen Sie sich nicht wegen dieser Größe an. Versuchen Sie nicht, das Objektmodell von Unity in einer 10-KB-Zeilen-Anwendung nachzuahmen. Das Ergebnis wird eine völlige Katastrophe sein. Bauen Sie die Ebenen schrittweise auf, und erhöhen Sie die Anzahl der Abstraktionsebenen nur dann, wenn dies erforderlich ist.

Csala Tamás
quelle
0

ioDoom3 ist wahrscheinlich ein guter Ausgangspunkt, da Sie sich darauf verlassen können, dass Carmack eine gute Codierungspraxis befolgt. Ich glaube auch, dass er Megatexturing in Doom3 nicht verwendet, es ist also relativ einfach als Renderpipe.

chrisvarnz
quelle
6
"Da Sie sich irgendwie auf Carmack verlassen können, um der großartigen Codierungspraxis zu folgen" Oh man, das ist eine gute. Carmack folgt einer großartigen C ++ - Codierungspraxis. Das ist ein Aufstand! Oh ... du hast nicht gescherzt. Um ... haben Sie jemals tatsächlich sehen in seinem Code? Er ist ein C-Programmierer und denkt so. Was für C in Ordnung ist, aber die Frage betrifft C ++ .
Nicol Bolas
Ich habe einen C-Hintergrund, daher macht sein Code für mich sehr viel Sinn: P und ja, ich habe ein bisschen Zeit damit verbracht, an Quake-basierten Spielen zu arbeiten.
Chrisvarnz