Sprite Rendering mit ModernGL

Für Fragen zu Grafik APIs wie DirectX und OpenGL sowie Shaderprogrammierung.
Antworten
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Hallo,
ich beschäftige mich gerade mit ModernGL und mache gute Fortschritte. Ich habe bereits eine kleine 2D Game Engine entwickelt, die gut mit Legacy OpenGL funktioniert. Meine Engine ermöglicht es, individuelle Render Devices zu erstellen. Jetzt möchte ich gerne eines für OpenGL 4 erstellen.

Um meine Frage zu beantworten, ist es wichtig zu verstehen, wie die Engine funktioniert. Das Grundkonzept ist eigentlich ziemlich einfach: Ich habe Szenen, die Layer enthalten, und diese wiederum enthalten Game-Elemente. Die Klasse "GameElement" ist abstrakt, was bedeutet, dass es verschiedene Arten davon gibt (zum Beispiel Modelle für kleine 3D-Objekte, Meshes, Rechtecke und auch Sprites). Wenn eine Szene aktiv ist, werden die Layer nacheinander gerendert.

Da die Vertexdaten immer gleich sind, überlege ich, ein Strukt zu erstellen, das die Indexwerte für den Vertex Buffer, den Color Buffer und den TextCord Buffer speichert. Jedes Sprite würde dann auf diese gemeinsamen Buffers verweisen. Bei der Initialisierung würde dieses Strukt erstellt und die Buffers würden generiert. Während des Renderprozesses würden sich die Texturkoordinaten für animierte Sprites ändern. Das Ziel dahinter ist, nicht für jedes Sprite separate Buffers zu haben, sondern nur einen Vertex Buffer, einen Color Buffer und einen TextCord Buffer für alle Sprites.

Im Anhang habe ich mal ein Diagramm wie das ganze funktioniert. Meine frage ist nun, ob diese Vorgehensweise gut oder eher schlecht ist.
ModernGL Sprite Rendering.jpg
Matthias Gubisch
Establishment
Beiträge: 470
Registriert: 01.03.2009, 19:09

Re: Sprite Rendering mit ModernGL

Beitrag von Matthias Gubisch »

Ich würde die Shader noch nach Gemeinsamkeiten sortieren damit du weniger Statechanges hast.
Also nach gleicher Shader da innerhalb nach gleichen Buffern und darin wieder nach gleichen Texturen als Beispiel.
Und dann immer nur die absolut notwendigen States nur setzen.

Gäb noch weiter Möglichkeiten das zu optimieren aber dafür ist meine OGL Zeit schon zu lange her um hier belastbare Aussagen zu machen.
Bevor man den Kopf schüttelt, sollte man sich vergewissern einen zu haben
Benutzeravatar
Jonathan
Establishment
Beiträge: 2371
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Jonathan »

"ModernGL" ist ein bisschen ein lustiges Wort, da ja ganz OpenGL quasi deprecated und durch Vulcan ersetzt ist :D

Ich hab mich vor einiger Zeit mit Vegetationsrendering beschäftigt, und damit, wie man möglichst effizient möglichst viel zeichnet. Das lässt sich natürlich auf auf 2D Anwenden, im Prinzip willst du mit einem einzigen Aufruf alle Sprites die gerade im Spiel sind zeichnen. D.h. nicht einen Vertex-Buffer pro Sprite, sondern einen Buffer für alle Sprites zusammen. Da gibt es jetzt verschiedene Möglichkeiten, das zu erreichen, z.B. via Instancing oder Compute Shader.

Man könnte da jetzt sehr tief einsteigen. Man kann sich aber auch fragen, wie viele Sprites man realistisch gesehen in einer Szene hat. Ich denke mal, dass es um 2D Spiele geht. Brauchst du da eine Millionen Spielobjekte gleichzeitig? Vermutlich eher nicht. Und dann ist die Frage, wie viel Zeitaufwand man fürs Optimieren rechtfertigen kann...
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
NytroX
Establishment
Beiträge: 364
Registriert: 03.10.2003, 12:47

Re: Sprite Rendering mit ModernGL

Beitrag von NytroX »

Als Ergänzung zu dem, was Jonathan gesagt hat:
https://developer.nvidia.com/opengl-vulkan
Da steht ein bissel was dazu, welche Techniken man nutzen kann. Die Seite ist von NVIDIA, aber viele der NV_ extensions sind mittlerweile (z.B. bei OpenGL 4.6) im Core drin.
Da ist auch der klassische Link zur “AZDO” Präsentation drin, da findet man auch nochmal einiges.

Insgesamt läuft es aber darauf hinaus, möglichst alle Daten in einem/wenigen Buffern auf der GPU zu haben und mit nur einem/wenigen MultiDraw*Indirect Calls zu arbeiten.
Dann kann man sich die Bind- Calls größtenteils sparen, wenn man die Sprites durch-iteriert.
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Vielen dank für die ganzen Antworten. Ja ich dachte ich beschäftige mich erstmal mit "modernem" OpenGL und dann wenn ich das soweit raus habe mit Vulkan, da das ja nochmal komplexer sein soll. Das mit dem einzigen Draw Call macht natürlich sinn, wobei das auch interessant wird herauszufinden wie man das am schlausten macht. Ja, viele Sprites habe ich nicht die es zu rendern gibt. Vor allem lasse ich nur die rendern wo auch wirklich sichtbar sind. Sprites die außerhalb der Kamera liegen werden zwar iteriert aber dann nicht zum Renderer weiter gegeben.

Mein Ziel ist jetzt auch nicht so eine riesige Engine zu entwickeln, mir macht es nur sehr viel spaß neue Sachen zu lernen und die Grafik Programmierung finde ich ein sehr spannendes Thema.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4857
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Schrompf »

x separate VertexBuffer finde ich zuviel. Das sind dann drei synchronisierte Transfers zur GPU anstatt einem, das würde ich gefühlt sein lassen.

Was ich ausprobiert habe:
a) Einfach einen großen VertexBuffer für jedes Sprite mit 4 Vertex-Strukturen füllen, vorgefertigten IndexBuffer dazu, alles rendern... geht ganz gut.
b) In die VertexStruktur Texturkoords des Rechtecks, Rotationswinkel, Darstellungsgröße und so reinschreiben und im VertexShader daraus die finalen Koords berechnen - spart bissl CPU bei großen Mengen Sprites.
c) Instancing: ein fester VertexBuffer mit nur (0,0), (1,0), (0,1), (1,1), ein fester IndexBuffer mit [0, 1, 2, 1, 3, 2], und pro Instanz eine Instanzstruktur mit Mittelpunkt, Größe, Texturrechteck, Rotation, Farbe usw.. Spart einiges an CPU und einiges an Transfer-Bandbreite, belastet dafür die GPU bissl mehr, aber das geht dann schon bis paar hundert K Sprites
d) Nur noch Points mit den Instanzstrukturen rendern und im Geometry- oder MeshShader zu Vierecken aufblasen. Hab ich noch nicht ausprobiert, bringt wahrscheinlich erst was, wenn man die Instanzstrukturen nur noch auf der GPU verrechnet - bei nem GPU-Partikelsystem oder so.

Da Du Spaß am Rendern hast und gar keine extremen Mengen Sprites brauchst, würde ich glaube ich erst a) bauen, dann vielleicht c).
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Ok Perfekt, ich habe das jetzt so gemacht, wie du gesagt hast. Hier mal mein Code

Code: Alles auswählen

private void BuildSpriteShape()
        {
            var spriteShape = new InstancedShape();
            float[] verticies =
            {
                //Verticies
                -0.5f, -0.5f, 0.0f,
                -0.5f, 0.5f, 0.0f,
                0.5f, 0.5f, 0.0f,

                -0.5f, -0.5f, 0.0f,
                0.5f, 0.5f, 0.0f,
                0.5f, -0.5f, 0.0f,

                //Colors
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,

                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,
                1.0f, 1.0f, 1.0f,

                //Tex Coords
                0.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,

                0.0f, 0.0f,
                1.0f, 1.0f,
                1.0f, 0.0f
            };

            // Create Vertexbuffer and set the data
            spriteShape.vbo = gl.GenBuffer(1);
            gl.BindBuffer(OpenGL.ArrayBuffer, spriteShape.vbo);
            gl.BufferData(OpenGL.ArrayBuffer, verticies.Length * sizeof(float), verticies, OpenGL.DynamicDraw);
            gl.EnableVertexAttribArray(0);
            gl.VertexAttribPointer(0, 3, OpenGL.Float, false, 0, 0);
            gl.EnableVertexAttribArray(1);
            gl.VertexAttribPointer(1, 3, OpenGL.Float, false, 0, 18 * sizeof(float));
            gl.EnableVertexAttribArray(2);
            gl.VertexAttribPointer(2, 2, OpenGL.Float, false, 0, 36 * sizeof(float));

            this.InstancedShapes.Add("SpriteShape", spriteShape);
        }
und dann hier meine render funktion

Code: Alles auswählen

public void DrawSprite(Sprite sprite)
        {
            //Create the modelview matrix
            mat4 mt_mat = mat4.Translate(sprite.Location.X, sprite.Location.Y, sprite.Location.Z);
            mat4 mr_mat = mat4.RotateZ(sprite.Rotation);
            mat4 ms_mat = mat4.Scale(sprite.Size.X, sprite.Size.Y, sprite.Size.Z);
            mat4 m_mat = mt_mat * mr_mat * ms_mat;

            //Create the mvp matrix
            mat4 mvp = p_mat * v_mat * m_mat;

            //Load the shader program and set the mvp matrix
            gl.Enable(NetGL.OpenGL.Texture2D);
            gl.UseProgram(ShaderPrograms["MVPShader"].ProgramID);
            gl.UniformMatrix4fv(gl.GetUniformLocation(ShaderPrograms["MVPShader"].ProgramID, "mvp"), 1, false, mvp.ToArray());

            //Load the texture and send it to the shader
            gl.BindTexture(NetGL.OpenGL.Texture2D, sprite.Texture.RenderID);
            gl.TexParameteri(NetGL.OpenGL.Texture2D, NetGL.OpenGL.TextureWrapS, NetGL.OpenGL.Repeate);
            gl.TexParameteri(NetGL.OpenGL.Texture2D, NetGL.OpenGL.TextureWrapT, NetGL.OpenGL.Repeate);
            gl.Uniform1I(gl.GetUniformLocation(ShaderPrograms["MVPShader"].ProgramID, "textureSampler"), 0);

            //Load the vertex buffer and set the new tex coords
            int vertexBuffer = this.InstancedShapes["SpriteShape"].vbo;
            gl.BindBuffer(OpenGL.ArrayBuffer, vertexBuffer);
            float[] textCoordsf =
            {
                // Erstes Dreieck
                sprite.TexCoords.BottomLeft.X, sprite.TexCoords.BottomLeft.Y,
                sprite.TexCoords.TopLeft.X, sprite.TexCoords.TopLeft.Y,
                sprite.TexCoords.TopRight.X, sprite.TexCoords.TopRight.Y,

                sprite.TexCoords.BottomLeft.X, sprite.TexCoords.BottomLeft.Y,
                sprite.TexCoords.TopRight.X, sprite.TexCoords.TopRight.Y,
                sprite.TexCoords.BottomRight.X, sprite.TexCoords.BottomRight.Y,
            };
            gl.BufferSubData(OpenGL.ArrayBuffer, 36 * sizeof(float), textCoordsf.Length * sizeof(float), textCoordsf);

            //Draw the Sprite
            gl.DrawArrays(OpenGL.Triangles, 0, 6);

            gl.Disable(NetGL.OpenGL.Texture2D);
            Console.WriteLine(gl.GetError());
        }
Funktioniert sehr gut soweit
Matthias Gubisch
Establishment
Beiträge: 470
Registriert: 01.03.2009, 19:09

Re: Sprite Rendering mit ModernGL

Beitrag von Matthias Gubisch »

Ich dachte in OGL musste man immer die Texturen die man verwenden wollte binden vor dem DrawCall? Damit muss man entweder einen Texturatlas basteln oder alles auf einmal Rendern fällt weg.

In meiner (Vulkan)-Engine nutze ich DescriptorIndexing und der Shader sucht sich die Textur aus einem Array raus, dann kann man natürlich alles auf einmal Rendern, aber ich wusste nicht dass sowas in OpenGL mittlerweile auch geht.
Bevor man den Kopf schüttelt, sollte man sich vergewissern einen zu haben
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Hm? Sie wird doch gebinded und dann an den Shader gesendet.
Matthias Gubisch
Establishment
Beiträge: 470
Registriert: 01.03.2009, 19:09

Re: Sprite Rendering mit ModernGL

Beitrag von Matthias Gubisch »

Ich ging bei meiner Antwort davon aus dass du verschiedenen Texturen und Shader hast.
Dann kannst du Pro Shader/Textur paar einen Drawcall machen, entweder per Instancing oder mit einem großen VertexBuffer wie oben schon von einigen beschrieben.
Bevor man den Kopf schüttelt, sollte man sich vergewissern einen zu haben
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Achso 😅 das habe ich dann wohl falsch verstanden. Mit dem was ich jetzt abgeändert habe bin ich recht zufrieden, aber neue Technologien lernen ist natürlich auch was gutes. Werde versuchen die anderen Methoden auch umzusetzen
Benutzeravatar
Jonathan
Establishment
Beiträge: 2371
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Jonathan »

Andy90 hat geschrieben: 26.10.2023, 13:35 Funktioniert sehr gut soweit
Joah, ist ja erstmal ok so. Aber wenn du irgendwann schneller werden willst wäre halt der entscheidende Punkt, nicht mehr jedes einzelne Sprite einzeln zu rendern, sondern alle in einer großen "Liste" (halt in entsprechenden Buffern etc.) zu haben und dann mit einem rutsch zu rendern. Ich bin mir ziemlich sicher, dass wenn du einen großen Buffer mit 10.000 Sprites renderst, von denen aber nur 100 gleichzeitig auf dem Bildschirm sichtbar sind, das trotzdem schneller geht, als 100 Sprites einzeln zu rendern. Weil Grafikkarten parallel arbeiten wollen und man bei 2 Dreiecken nicht tuasend Threads gleichzeitig benutzen kann.

Und dann so Dinge wie eine 4x4 Matrix übergeben um damit 4 Eckpunkte zu transformieren - naja, die könnte man auch vortransformiert als 8 floats übergeben. Aktuel hast du eine Matrix mit 16 Einträgen sowie Vertexbuffer mit viel zu vielen Einträgen (3D Positionen brauchst du nicht, Vertex-Positionen und Texturkoordinaten speichern quasi die selbe Information, etc.). Da könnte man noch überall sehr viel rausholen. Aber bevor du nicht 100.000 Sprite gleichzeitig am Bildschirm hast, brauchst du das wohl nicht, trotzdem ist es vielleicht nicht schlecht sich des Potentials bewusst zu sein.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Ja, dass zu wissen ist auf jeden fall sehr gut. Auch das mit den den vor transformieren werde ich mir anschauen.
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

FPS technisch bin ich jetzt auf 1000 gekommen, Aber es werden auch nicht so viele Sprites gerendert. Auf jeden fall ist es spannend. Heute morgen waren es noch 800 FPS. Ich meine in einem fertigem game wird man sicher nicht diese werte erreichen, oder was meint ihr ?
Benutzeravatar
Schrompf
Moderator
Beiträge: 4857
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Schrompf »

800 oder 1000fps sind bedeutungslos, das heißt nur, dass Du noch nix Wirkliches tust.

Aktuell ist bei Dir jedes Sprite ein DrawCall. D.h. Du wirst am Ende für 60fps auf durchschnittlicher ConsumerHardware ein paar tausend Sprites hinkriegen. Wenn Du dann mehr brauchst (oder CPU schonen willst), sagst Du Bescheid. Und mach Bilder :-)
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Ich habe nun auch mal meinen alten Wafefront-Loader überarbeitet und die Sponza-Szene mit "modernem" OpenGL gerendert. Die Ergebnisse sind ziemlich vielversprechend, jedoch könnte der Shader noch optimiert werden. Die FPS lag bei stabilen 100 (ist aber auch gecapped dort)
sponza.png
Benutzeravatar
Zudomon
Establishment
Beiträge: 2254
Registriert: 25.03.2009, 07:20
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Zudomon »

Hey cool!
Aber mein Vorschlag wäre, dass du lieber nochmal einen eigenen Thread im Vorstellungsbereich erstellst, wo du deine Engine vorstellst und immer Neuerungen rein postest. Hier geht das letztendlich schnell unter, weil es ja eigentlich um die Frage des effektiven Sprite Rendering geht.

Statt FPS solltest du lieber den Kehrwert nehmen, also Millisekunden pro Frame bzw. Feature... da sich die FPS nicht linear verhalten.
Bei 800 FPS verbrauchst du 1,25 ms... renderst du zweimal hast du noch 400 FPS... bei 3 mal 266 FPS... da ist 1,25 ms, 2,5 ms und 3,75 ms aussagekräftiger.
Wenn du statt die Frames pro Sekunde zu zählen, die Millisekunden misst, die du für das Rendern eines Frames brauchst, und deine Szene z.B. in 6 ms gerendert wird, dann kannst du umgekehrt auch ermitteln, dass du theoretisch bei 166 FPS liegst, auch wenn die Frames bei 100 gecappt werden.
Wobei es sinnvoll ist, die Framezeiten für CPU und GPU separat zu ermitteln.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2371
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Sprite Rendering mit ModernGL

Beitrag von Jonathan »

Hehe, oder erstmal Global Illumination implementieren (dafür war die Testszene ja soweit ich weiß ursprünglich gedacht) und dann nochmal gucken ob die FPS immer noch über 100 liegen :D

Btw. das Bild sieht etwas grisselig aus. Gibt es dafür einen Grund?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Andy90
Beiträge: 63
Registriert: 08.10.2023, 13:02

Re: Sprite Rendering mit ModernGL

Beitrag von Andy90 »

Ja, ich habe noch die falschen Texture Filter Settings verwendet und keine MipMaps. Hab das geändert, dass ganze schaut nun so aus.

Bild

Auch noch nicht wirklich viel schöner, aber immerhin etwas besser. Habe mich erstmal damit beschäftigt wie ich überhaupt 3D Physik in die Engine bekomme. Habe mich da für Bullet entschieden, aber ein Template System für die Physik in der Engine erstellt. Das ermöglicht mir weiterhin das nutzen der alten 2D Physik oder eben die neue Physik mit Bullet, besser gesagt Bulletsharp. Ich war überrascht wie schnell man so eine Physik Engine integrieren kann.
Antworten