Modulare 3D-Engines



  • Ich arbeite mich gerade in das Thema 3D-Engine ein. Irgendwie kam mir die Idee eine modulare Engine zu proggen. Die Engine sollte wie Bausteine zusammensetzbar sein (KI,Sound,Grafik usw.).

    Das heisst:
    Ich erstelle 2 DLL-Dateien (OGL.dll, DX.dll) die die gleichen Funktionen haben (z.b. Zeichne_Bild). Die Funktionen aber führen in ihrer DLL die API-Spezifischen Befehle aus. Wenn ich dass nun bei der Sound-API auch so machen könnte man das Programm voll auf seinen Computer abstimmen.

    A: "Also game immer mit DX und DSound. Leider sind die Test-Bots zu stark"

    B: "Nee, OpenGL in Kombination mit SoundTest ist doch viel schneller. Saug dir doch das Bot-Update 1.2, dort sind sie ein bisschen schwächer"

    usw... 🙂 🙂 🙂

    Nun meine Frage, ist das so in etwa überhaupt machbar, da beide DLL's ja theoretisch dieselbe DLL.h benützen könnten sollte das doch eigentlich kein
    Problem darstellen ???? 😕



  • Kein Problem, ich habe sowas ähnliches auch mal programmiert.
    Allerdings solltest Du dann die DLL-Dateien manuell laden, mit den Funktionen LoadLibrary und GetProcAddress (damit kriegst Du die Funktionszeiger).

    Z.B. könntest Du es so machen:

    class Renderer
    {
    public:
        void DrawTriangle(...);
        ...
    }
    

    Direct3D.dll:
    Definiert eine von Renderer abgeleitete Klasse D3DRenderer.
    Die Funktion "GetRenderer" liefert den Zeiger: Renderer* GetRenderer() {return new D3DRenderer;}

    OpenGL.dll:
    class OpenGLRenderer : public Renderer;
    Renderer* GetRenderer() {return new OpenGLRenderer;}

    Im Hauptprogramm lädst Du dann entweder Direct3D.dll oder OpenGL.dll. Dann findest Du mit GetProcAddress den Zeiger auf die "GetRenderer"-Funktion. Die rufst Du auf. Und schon ist alles schön abstrahiert und das Programm braucht sich nicht drum zu kümmern, welcher Renderer es nun ist.


  • Mod

    das ist keine modulare engine sondern ein API-Wrapper. eine modulare engine ist eine, bei der, ohne sie selbst neu kompilieren zu müssen, du funktionalität einbringen kannst z.B. neue objektarten oder cullingmethoden.

    und ja, ist machbar und wenn du nichtmal sowas weißt, weil du dir also nicht genug andere engines angesehen hast, solltest du es lieber jetzt machen, als später eine halbfertige engine mit haufenweise designflows zu haben, die nie laufen wird, weil das konzept nicht stimmt.

    als beispiel könntest du dir z.B. die ogre engine oder die Fly3D engine anschauen.

    ist alles nicht böse gemeint :), aber deine frage ist nichtmal richtig gestellt 😉

    mein absurdes gegenbeispiel: "ich bin noch nie nen wagen gefahren, aber ich hab da so ne idee für einen eigenen, ich würde dann wegen dem besseren luftwiederstand den motor gerne hinten einbauen, glaubt ihr das wäre möglich?" 😋

    rapso->greets()



  • rapso schrieb:

    das ist keine modulare engine sondern ein API-Wrapper. eine modulare engine ist eine, bei der, ohne sie selbst neu kompilieren zu müssen, du funktionalität einbringen kannst z.B. neue objektarten oder cullingmethoden.

    Interessant. Und wo genau ist diese Weisheit in Stein gemeißelt?



  • Sorry.
    Du hast natürlich recht, nicht die Engine wäre modular sondern das Programm das darauf läuft wäre modular.

    Wobei ich sagen muss das dieser Fehlausdruck nichts über das Wissen zum Thema OGL und DX aussagt !!! 😡

    Wobei wenn ich nachdenke ist die Engine trotzdem modular, man kann zwischen den verschiedenen DLL's ohne neukompillierung wechseln. Villeicht hast du mich auch falsch verstanden.

    Besseres Beispiel:

    In meinem Ordner "My-Game" befindet sich die Game.exe, die 3D-Engine.dll, die OpenGL.dll, die DX7.dll und die DX8.DLL.

    Nun starte ich das erste mal das Programm und wähle DX7.dll als "Engine-Befehlssatz". Das Programm wird wie bei einem Treiber in DX7 ausgeführt.

    Lösche ich nun vor dem zweiten Start die DX7.dll so könnte eine Abfrage feststellen das es die Datei nicht mehr gibt und automatisch auf OpenGl.dll oder DX8.dll umschalten.

    Ich kann also zwischen openGL / DX7 / DX8 ohne eine Neukompillierung die Engine verändern (Die Engine.dll wird ja nicht verändert).



  • Hat Dir mein Beitrag wenigstens geholfen? 🙂


  • Mod

    jeder der sich mit der materie beschäftigt weiß, dass man nicht eine ganze engine in mehreren versionen schreibt, womöglich eine mit d3d&DS, d3d&oAL, d3d&... das natürlich mit socket,ipx und dplay, für archiv und filesystem zugriff...
    so macht das natürlich niemand. dafür hat man einen wrapper, der bilden für die engine mit einem einheitlichem system die darunter liegende Ebene ab.

    und was modularisierung ist scchrieb ich ja bereits. dabei kann ich mich auf viele steine berufen z.B. das buch zu fly3d bzw fly3d2, dort ist es sehr modular aufgebaut, in dem editor werden verschiedene parameter der module angezeigt, so kann man sich in dem eigenem modul z.B. eine variable als "COLOR" definieren und der editor wird dafür keinen hexwert verlangen sondern wie es sich gehört einen colordialog öffnet.

    ansonsten nutze meine lieblingsstein http://www.google.com/search?q=3d+engine+modules&sourceid=opera&num=0&ie=utf-8&oe=utf-8 da gibt es viele möglichkeiten, die veranschaulicht werden.

    rapso->greets();



  • @ TomasRiker: Danke, der Beitrag hat mir sehr geholfen. Aber eigentlich habe ich mir das ganze in der Theorie überlegt, gedanken über den Code hab ich mir noch nicht gemacht. Deshalb habe ich die Frage der Machbarkeit gestellt, da Personen wie du sich sicherlich besser mit DLL's auskennen als ich.


  • Mod

    vampalive schrieb:

    Sorry.
    Du hast natürlich recht, nicht die Engine wäre modular sondern das Programm das darauf läuft wäre modular.

    wenn du dir andere engines ansiehst, würdest du sehen, dass es zwischen engine und API noch eine ebene gibt, die alles maskiert. das ist der einfachste weg es zu machen...

    vampalive schrieb:

    Wobei ich sagen muss das dieser Fehlausdruck nichts über das Wissen zum Thema OGL und DX aussagt !!! 😡

    wer behaupted denn sowas? bloss weil du nicht weiß, wie das geläufigste konzept für eien API unabhängige engine heißt, kannst du trotzdem die APIs selbst programmieren können... ich kann ja auch serveranwendungen coden und bin trotzdem ein linuxDAU

    vampalive schrieb:

    Wobei wenn ich nachdenke ist die Engine trotzdem modular, man kann zwischen den verschiedenen DLL's ohne neukompillierung wechseln. Villeicht hast du mich auch falsch verstanden.

    Besseres Beispiel:

    In meinem Ordner "My-Game" befindet sich die Game.exe, die 3D-Engine.dll, die OpenGL.dll, die DX7.dll und die DX8.DLL.

    Nun starte ich das erste mal das Programm und wähle DX7.dll als "Engine-Befehlssatz". Das Programm wird wie bei einem Treiber in DX7 ausgeführt.

    Lösche ich nun vor dem zweiten Start die DX7.dll so könnte eine Abfrage feststellen das es die Datei nicht mehr gibt und automatisch auf OpenGl.dll oder DX8.dll umschalten.

    Ich kann also zwischen openGL / DX7 / DX8 ohne eine Neukompillierung die Engine verändern (Die Engine.dll wird ja nicht verändert).

    du möchtest also in die engine für jede mögliche API die richtige verwendung einprogrammieren? sodass die dir bekannten module (oGL,dx7,dx8...) verwendet werden können?
    also wenn es auf modulen aufsetzt, dann müßtest du auch ein neues hinzufügen können z.B. meinechtzeitraytracer.dll ohne was an der engine kompilieren zu müssen. sonst ist es nicht wirklich modular. du hast ja dadurch dann nichts gewonnen, du mußt (APIANZAHL)*Engine coden, weil du alles für jede mögliche anordnung implementieren mußt.... welch ein aufwand...

    rapso->greets();



  • @ Rapso:

    Also eigentlich möchte ich das die Engine alle verfügbaren API.dll's sucht und man dann eine auswählen kann. Es muss für eine neue API also nur eine neue DLL programmiert werden.

    Natürlich gibt es einen grossen Aufwand. Aber interessant ist es allemal.



  • Eigentlich müsste ja nur die zu ladende DLL verändert werden, die Funktionen heissen ja eh alle gleich.

    Das könnte in etwa so aussehen:

    API1 = OpenGL
    API2 = DX7
    API3 = DX8
    //usw...
    
    if(API=API1)
    {
    Lade_DLL(OpenGL.dll)
    }
    
    if(API=API2)
    {
    Lade_DLL(DX7.dll)
    }
    
    if(API=API3)
    {
    Lade_DLL(DX8.dll)
    }
    
    Lade_Level()
    //usw...
    

    Natürlich wäre die Anzahl an API's beschränkt, aber ich glaube mehr als 10 API's braucht kein Mensch.


  • Mod

    tjo, aber das ist ja keine modularisierte engine. zeig mir doch bitte mal die module? das API gehört ja nicht zu deiner engine, also hast du nur deine engine mit hunderten von fallunterscheidungen.

    z.B. mußt du bei oGL dann vielleicht die extensions einladen, dort wo du also den vertexbuffer erstellst bei d3d, mußt du für ogL noch alle verschiedenen extension der grakas nach vertexarrays bzw vertexobjects untersuchen.... wozu muss das die engine machen?

    dabei wäre es ja so einfach, die engine greift auf deinen wrapper zu und der jeweilige wrapper arbeitet mit seiner API. dann mußt du nicht in der engine abfragen welches koordinatensystem du gerade verwenden solltest, welche texelcenter berechnung.
    denn es kommen immer mehr sachen rein, je mehr APIs du einbaust, dann mußt du die ganze engine eventuell umstellen, weil z.B. die engine an verschiedenen stellen sich vertexbuffer erstellt hat, es aber nun sein kann dass du VertexArrays von NVidia nutzen möchtest und dort nur einen riesen speicherblock dir hollen kannst. das bedeutet du müßtest in der engine alles so umschreiben, dass für den fall NVidia der speichermanager genutzt wird, aber wie löst du dann jetzt die refraktion des speichers? da müßtest du also in der engine prüfen (an jeder stelle) ob der speicher für den vertexbuffer verschoben werden müßte und deswegen die pointer nicht mehr stimmen...

    ach da _kannst_ du (MUSS DIR NATÜRLICH NICHT PASSIEREN) in soviele fallen tappen, besonders wenn du das zum ersten mal machst!

    wenn du dir einen wrapper dazwischen schiebst, dann kannst du zumindestens in der engine klar definieren, wie du den wrapper benutzen möchtest, und falls die API etwas nicht kann, dann ist das das problem im wrapper, du wirst die engine nicht umkonzipieren müssen, nur weil jemand einen softwarerenderer geschrieben hat, der das d3d matrizensystem benutzt aber die oGL koordinatenanordnung nutzt. da muss man nen wrapper schreiben der an der klar definierten schnittstelle von A nach B übersetzt.

    rapso->greets();



  • Du hast recht, die If..-Lösung wäre tausendmal aufwändiger, um trotzdem nur zum selben Ziel zu kommen. Die Wrapper-Lösung scheint die Ideallösung zu sein. Ich werde mich einmal daran versuchen, wenn ich Probs habe weiss ich ja wo ich Hilfe finde.

    Danke für die Entscheidungshilfe.


  • Mod

    wir helfen gerne. meißten erkennt man die beste lösung daran, was sich im Darwin'schen system durchgesetzt hat 🙂 ... und die allermeißten machen das so.

    <tip>
    ich würde dir trotzdem vorschlagen, dass du dir anregungen für die art und weise wie man es realisieren kann, in ein paar engines anzuschaust, denn es gibt viele kleine stolpersteine, die man vielleicht in den wrappern anderer engines findet, von denen man noch nicht wuste, dass es sie überhaupt gibt
    </tip>

    rapso->greets();



  • vampalive schrieb:

    Eigentlich müsste ja nur die zu ladende DLL verändert werden, die Funktionen heissen ja eh alle gleich.

    Eine Funktion reicht: die nämlich, welche den Zeiger auf die Klasse liefert, die von der DLL bereitgestellt wird. Siehe mein Beispiel.



  • @raspo
    Verstehst du unter Wrapper eine Schnittstelle (interface Klasse)? Und die vererbten Klassen sind dann in den DLLs gespeichert?



  • Zum Thema modulare Engine... Schau dir mal das Konzept von Half-Life an. Die benutzen zwar nur eine engine.dll und eine game.dll, verbunden mit einer hl.exe, aber das Konzept kann man trotzdem einwandfrei auch adaptieren um mehrere Module zu benutzen. Was die Problematik der richtigen DLL für die richtige Konfiguration betrifft, würde ich z.B. das liblist.gam - Konzept aus HL verwenden (oder wie das file dort heisst...) da wird einfach der Name der zu ladenden DLL für das game in das File gekritzelt und dann beim Laden ausgelesen...

    -junix


  • Mod

    AJ schrieb:

    @raspo
    Verstehst du unter Wrapper eine Schnittstelle (interface Klasse)? Und die vererbten Klassen sind dann in den DLLs gespeichert?

    eine interfaceklasse ist eine abstrakte klasse die also nur virtuelle funktionen deklariert ohne sie zu definieren, weil sich die definition woandes befinden und man auf sie zugreifen möchte.
    die vererbten klassen können in dlls sein, wenn du eine Engine(.dll) benutzt, dann kannst du auch in deiner exe von einem interface ableiten um es der engine quasi als "user erweiterung zu geben", z.B. wenn du ein partikelsystem hast und für jeden partikel eine berechnung durchführen möchtest, wie sie die engine standardmässig nicht macht, dann kannst vom (class I)ParticleAnimator ableiten und der engine dann deine klasse als Animator für alle partikel geben. natürlich ist es nur ein beispiel 🙂
    man kann auch ganz neue dinge einbauen, je nachdem wie abtract das interface ist, z.B. wenn die engine garkeine particle unterstützt, könntest du von einer objektklasse ableiten, einmal für einen particle-collector und einmal für die partikel selbst. dann würde die engine immer noch das culling und sonstiges für dich machen, aber die particle selbst würden sich nicht rendern, sondern bei Particle-collector "anmelden" und wenn der sich dann rendern soll, zeichnet er alle "angemeldeten" particle... je nachdem wie flexible eine engine ist, natürlich verliert man für die flexibilität einiges an performance.

    rapso->greets();



  • @raspo
    Aja, danke.
    Aber leider bin ich jetzt nicht schlauer als vorher. Was eine Interfaceklasse ist, wusste ich schon. Ich weiß nur nicht was du mit Wrapper meinst. Mir ist der Begriff nicht geläufig. Wie ich es aus dem ganzen Zusammenhang verstanden habe, wäre ein Wrapper genau so eine Interfaceklasse bzw. die vererbten Klassen davon, oder??


  • Mod

    ganz genau gesagt ist ein wrapper nur die ummantelung einer anderen API. anstatt also direkt z.B. auf die festplatte zuzugreifen, sagst du sowas wie pWrapper->Open(...); und der wrapper macht intern dann das API bedingte zeug.

    das gute an sowas ist, dass du ein fest definiertes Interface hast, mit dem du auf die daten zugreifen willst. möchtest du nun z.B. über das internet daten laden, mußt du in dein programm nichts neues implementieren, du schreibst dir einen wrapper, der die selben interfaces anbietet wie der gerade und tauscht ihn mit dem vorherigen aus. nun lädt dein programm alles aus dem netz ohne geändert worden zu sein.

    der vorteil ist nicht nur dass du ohne programmeingriff neue "features" unterstützen kannst, du kannst dabei auch den selben Wrapper für viele programme nutzen, und programmierst du einmal eine neue variation davon für deinen wrapper, können alle applikationen soeinen nutzen.

    ich hab z.b. schon lange einen wrapper für das öffnen von bildern, schaut in etwa so aus

    class IImage
    {
    public:
    virtual bool Open(const char* pFileName)=NULL;
    virtual bool Save(const char* pFileName)=NULL;
    virtual bool Create(const int SizeX,const int SizeY)=NULL;
    
    virtual unsigned int Pixel(const int SizeX,const int SizeY)=NULL;
    virtual void Pixel(const int SizeX,const int SizeY,unsigned int Color)=NULL;
    virtual unsigned int Pixel(const int SizeX,const int SizeY)=NULL;
    
    virtual unsigned int SizeX()=NULL;
    virtual unsigned int SizeX()=NULL;
    }
    

    ok, das ist nur ganz grob hier mal reingetippt, aber mit diesem interface greife ich auf eine "Image.dll", diese lädt bei initialisierung alle plugins aus dem verzeichniss "ImagePlugins/" und dort füge ich ab und zu ein neues dateiformat ein, ab da können alle meine programme damit arbeiten, sowas ist wirklich sehr nützlich, weil ich nicht jedes programm, dass ich mal wieder nutzen möchte, dann neu compilieren muss. außerdem kann ich das programm nach außen freigeben und jeder der es nutz, kann sein format einbinden, ohne dass er dafür den source vom hauptprogramm haben muss.

    sowas ist wirklich angenehm.

    rapso->greets();

    ps. vielleicht release ich das ja mal 🙂


Anmelden zum Antworten