Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

11

16.04.2012, 15:46

Was FSA schrieb, war genau mein erster Ansatz, aber wie David Scherfgen schon schrieb ist das elend langsam. Ich suche nun nach einem Weg, wie ich das effizienter gestalten kann. Das Instancing wurde von david_pb ins Spiel gebracht und ich weiß noch immer nicht, ob das auch für LPD3DXMESH funktioniert. Seinen letzten Post habe ich nicht verstanden.

FSA

Community-Fossil

  • Private Nachricht senden

12

16.04.2012, 16:37

Irgendwie ist das bei mir nicht langsam :huh: Ich verwenden ein eigenes Dateiformat, aber benutze letztendlich auch DrawIndexedPrimitive(...);

Zitat

Der RCCSWU (RandomCamelCaseSomtimesWithUndersquare) Stil bricht auch mal mit den veraltet strukturierten Denkmustern und erlaubt dem Entwickler seine Kreativität zu entfalten.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

13

16.04.2012, 16:48

@FSA:
Wie viele Meshes renderst du denn?
Wenn es nur 100 sind, wirst du nichts merken.
Wenn es aber 1000 oder 10000 sind, dann schon, denn jeder Aufruf von DrawIndexedPrimitive bringt einen gewissen Overhead mit sich + das Setzen der Shader-Konstanten (Matrizen).

@Endgegner:
Du darfst halt nicht die Draw-Methode des D3DXMeshes benutzen, sondern musst dir eine eigene schreiben, die Instancing unterstützt.
Es gibt im SDK doch ein Beispiel für Instancing, bringt dir das denn nichts?

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

14

16.04.2012, 16:53

Das kommt auf die Größe des Mesh an.

Instancing rentiert sich vor allen bei kleinen Meshes(zb. Grasbüschel) die sehr oft an unterschiedlichen Stellen platziert werden müssen.

Der Geschwindigkeitsvorteil kommt einfach daher, dass nicht so oft der Drawcall (evt. auch noch mit Vertex\Indexdaten) über den Grafikbus wandern muss.
Bei kleinen Modellen kann das nämlich länger dauern als das eigentliche Rendern des Modelles.

Bei FSA, ist also wahrscheinlich das Modell zu groß.

FSA

Community-Fossil

  • Private Nachricht senden

15

16.04.2012, 17:36

@David insgesamt nur 250 oder so ^^
@SpieleProgrammierer: 1 Großes und 249 kleine( aber recht simple gehalten ). Bei Gras habe ich das auch schon gemacht, aber immer die Entfernung zum rendern sehr niedrig eingestellt. Vielleicht sollte ich mich auch mal mit Instancing beschäftigen :thinking:

Zitat

Der RCCSWU (RandomCamelCaseSomtimesWithUndersquare) Stil bricht auch mal mit den veraltet strukturierten Denkmustern und erlaubt dem Entwickler seine Kreativität zu entfalten.

16

18.04.2012, 09:45

Hallo, tut mir leid für die späte Antwort, aber ich kam die letzten Tage nicht dazu, zu antworten.

Ich entwickel gerade ein kleines Spiele-Projekt, das mir seit Jahren im Kopf rumschwirrt. Ich habe dabei ein Tile-Basiertes Gelände, wobei das eigentliche Gelände eine Matrix ist, die für jede Kachel einen Pointer auf das dazugehörige Tile hält. In der Render-Funktion meines Geländes wird dann durch die Matrix iteriert und die Render-Funktion jeder Kachel aufgerufen und es wird an die entsprechende Stelle transformiert.

Beispiel: Ich habe ein Schachbrett mit weißen und schwarzen Feldern. Dann habe ich eine 8x8-Matrix mit Pointern auf weiße und schwarze Kacheln. Von der schwarzen und weißen Kachel hab ich je eine Klasse und wenn ich das Schachbrett rendern möchte, render ich jeweils die Kachel und transformiere sie dann an ihre Position.

Ich hoffe, ich habe meinen Ansatz verständlich beschrieben :-)

Jedenfalls wollte ich das Gelände mit einer Kachel intialisieren, auf der ein Baum steht. Bei einer Standard-Karten-Größe von 100x100 Feldern macht das 10.000 Bäume.

Mein Verfahren funktioniert super, ist aber elendig langsam, wenn ich immer die Renderfunktion des DirectX-Meshes aufrufe.

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

17

18.04.2012, 10:21

Das kommt auf die Größe des Mesh an.

Instancing rentiert sich vor allen bei kleinen Meshes(zb. Grasbüschel) die sehr oft an unterschiedlichen Stellen platziert werden müssen.

Der Geschwindigkeitsvorteil kommt einfach daher, dass nicht so oft der Drawcall (evt. auch noch mit Vertex\Indexdaten) über den Grafikbus wandern muss.
Bei kleinen Modellen kann das nämlich länger dauern als das eigentliche Rendern des Modelles.


Instancing ist vor allem rentabel für große Mengen ähnlicher, dynamischer Objekte. Grasbüschel sind dabei eigentlich kein besonders gutes Beispiel, die ließen sich nämlich ideal in einem Drawcall batchen..

Jedenfalls wollte ich das Gelände mit einer Kachel intialisieren, auf der ein Baum steht. Bei einer Standard-Karten-Größe von 100x100 Feldern macht das 10.000 Bäume.

Mein Verfahren funktioniert super, ist aber elendig langsam, wenn ich immer die Renderfunktion des DirectX-Meshes aufrufe.


Du kannst dabei an verschiedenen Stellen optimieren:
  • Nahe beienanderstehende Bauminstanzen in Cluster zusammenfassen um diese schnell cullen zu können
  • Da Bäume meißt nicht dynamisch sind, sind diese gute Kandidaten zum batchen
  • Ein LOD System mit weniger Details für Bäume in der Entfernung, evtl sogar einfach Billboards für die kleinste LOD Stufe
  • ...
@D13_Dreinig

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

18

18.04.2012, 10:54

Für Kacheln (ich übersetze das als "Tiles", bitte Korrektur, falls das falsch ist) ist der Instancing-Ansatz schon ok. Immerhin reden wir hier nur von einem Rechteck mit 4 Vertizes und 2 Dreiecken. Nimm anstatt D3DXMesh einfach D3DXSprite oder so, der batcht für Dich automatisch. Und Du solltest dringend soviele wie möglich der Grafiken auf eine Textur zusammenfassen. Dann ist das Zeichnen eines anderen Tiles kein Texturwechsel mehr, sondern nur noch alternative Texturkoordinaten, was jeder ordentliche Sprite-Renderer dann mit batchen können müsste.

Ich mach das über einen kleinen eigenem Sprite-Renderer. Der arbeitet per Instancing: ein winziger VertexBuffer, der das Standard-Quadrat enthält, ein winziger IndexBuffer mit nicht mehr als 0, 1, 2, 1, 3, 2 drin, und ein dynamischer VertexBuffer, der die Instanzdaten aufnimmt. Jeder Zeichenaufruf der Art "Grafik x an Position y mit Farbe, Rotation, Skalierung, etc" speichert erstmal nur eine kleine Helferstruktur. Bei Aufruf der Finalisierungsfunktion wird dann der dynamische VertexBuffer gelockt, pro Sprite ein Vertex geschrieben und das Gesamtkonstrukt dann mit Instancing gezeichnet. Funktioniert prima, mit DX10 und einem Geometrie-Shader könnte man sich noch das Instancing komplett ersparen und die Quads gleich in der GPU konstruieren.

Mit dieser Methode komme ich auf einem aktuellen Core i7 auf vielleicht 20 Millionen Sprites pro Sekunde. Rotation, Skalierung usw. macht der VertexShader, plus einen kleinen Trick mit den Texturkoordinaten, um bei Skalierungen zu verhindern, dass die Interpolation oder die MipMaps benachbarte Fehlfarben rüberholt. Ich kann den Ansatz also empfehlen.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

19

18.04.2012, 20:42

plus einen kleinen Trick mit den Texturkoordinaten, um bei Skalierungen zu verhindern, dass die Interpolation oder die MipMaps benachbarte Fehlfarben rüberholt. Ich kann den Ansatz also empfehlen.

Kannst du das mal näher erläutern?
Irgendwann muss das ja passieren, spätestens wenn du bei der 1x1-MIP-Map angekommen bist :)

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

20

19.04.2012, 10:09

Ja, die Fehler des kleinen Tricks werden immer schlimmer, je kleiner die Zielgröße ist. Damit werden sie dann aber auch optisch auch wieder kleiner :-)

Ausgangspunkt 1:1-Abbildung. Man kann ja eigentlich stressfrei die Quads so mappen, dass wirklich ein Texturpixel auf genau einen Bildschirmpixel abgebildet wird. Bis jemand skaliert, rotiert oder AntiAliasing anstellt. Und letzteres, bei 2D-Spielen ja nur bedingt nützlich, kann ja auch vom Nutzer per Driver Override erzwungen werden, man kommt also nicht so recht drumrum.

Also dachte sich der kleine Thomas, dass man einfach die Texturkoordinaten im PixelShader einen halben Texel vor dem Rand der Grafik aufhält. Im PixelShader, damit das UV-Mapping noch erhalten bleibt - wenn man es im VertexShader machen würde, würde man die ganze Grafik unmerklich strecken und damit bei bilinearer Interpolation Grafikmatsch bekommen. Der PixelShader sieht am Anfang also so aus:

C-/C++-Quelltext

1
2
3
4
5
6
float4 MachMalPixel( VertexAusgabe rinnhey )
{
  float2 texkoords = clamp( rinnhey.texkoords, rinnhey.koordgrenz.xy, rinnhey.koordgrenz.zw);
  float4 farbe = tex2D( gTextur, texkoords);
  //usw.
}


Und im VertexShader berechnet man die texKoordGrenze für den Anfang mit (MinTex plus ein halber Texel, MaxTex minus ein halber Texel). Durchkompilieren, ausprobieren, klappt perfekt. Man kann jetzt seine Sprites/Tiles schonmal im Subpixel-Bereich bewegen und man kann rotieren, ohne Fehlfarb-Ränder zu bekommen. Wenn man aber skaliert, kriegt man immernoch Fehlfarben rein. An der Stelle habe ich im VertexShader die Grafikgröße mit der Spritegröße verrechnet, um das wahrscheinliche MipMap-Level herauszufinden, und habe damit die Texturgrenze entsprechend erweitert.

Im Code sieht das so aus:

C-/C++-Quelltext

1
2
3
4
5
6
// Größe des Tex-Randbereiches ermitteln. Standard ist ein halber Pixel, bei verkleinerter Darstellung
// und entsprechender MipMap-Nutzung muss der Grenzbereich entsprechend größer werden
float randGrenze = min( rein.groesse.x / rein.grafik.z, rein.groesse.y / rein.grafik.w);
randGrenze = 0.5f / clamp( randGrenze, 1.0f/16.0f, 1.0f);

raus.texGrenz = (rein.grafik.xyxy + float4( 0, 0, 1, 1) * rein.grafik.zwzw + float4( 1, 1, -1, -1) * randGrenze) * gQuellGroesse.xyxy;


Erklärung: "rein" ist die Vertex-Eingabestruktur, bestehend aus dem Model Vertex (das Quad) und dem Instance-Vertex (Position, Größe, Rotation, Farbmodulation, Texturbereich auf dem Texturatlas). "rein.grafik" ist der Texturbereich der Grafik - .xy ist die linke obere Ecke, .zw ist die Grafikgröße in Texeln. Das erklärt vielleicht die etwas seltsam anmutende letzte Zeile: da werden Min und Max der Texturkoordinaten in einem Rutsch berechnet.

Und der obige Code hat noch eine Schwäche, die ich ihm noch nicht abgewöhnt habe: die Randgrenze müsste eigentlich exponentiell steigen. Wenn man ein Sprite mit einer Verkleinerung irgendwo zwischen den exakten MipMap-Stufen zeichnet, wird je nach Grafikkarte, Bildschirmposition und Filtereinstellung bereits irgendwo zwischen den Stufen die nächstkleinere MipMap-Stufe einberechnet. Man müsste also, um sicher zu gehen, die Randgrenze immer auf die nächste Zweierpotenz aufrunden. Das wäre noch ein log(), ceil() und pow() im VertexShader - bei Sprite Rendering tut das sicher keinem weh, aber ich bin einfach noch nicht dazu gekommen.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

Werbeanzeige