Autor Beitrag
C#
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: So 07.09.14 23:55 
Nabend,

ich habe vor ein paar Tagen mal einen Thread zu ToderDefense gestartet und stehe nun vor meinem ersten Problem :mrgreen:

Also ich habe eine BufferedGraphics, mit der ich später auf ein Control zeichne. Doch bevor in die BufferedGraphics gezeichnet wird, wird auf ein Image (praktisch ein Zwischenpuffer) durch eine andere Graphics-Instanz gezeichnet. Das ganze hat den Grund, dass ich jedem GameObject ein Image übergebe und die dazugehörige Graphics. Außerdem hat jedes GameObject einen Vector2 Up;, der - wie der Name schon sagt - nach oben zeigt, also die Y-Achse nach oben. Verschiebe ich nun den Vektor, kann ich so das komplette GameObject drehen, ohne dass ich in dem GameObject selbst die Drehung berücksichtigen muss.
Das ganze sieht dann so aus:
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
foreach (GameObject g in gameObjects) 
        {
          if (!g.IsVisible) continue;

          if (g.Size != lastSize)    // Dieser if-Block wird im Test nie true, kann bei der Diagnose also ignoriert werden.
          {
            size = g.Size * TextureSize; 
            realSize = new Vector2((int) (Math.Sqrt(Math.Pow(g.Size.X * TextureSize, 2) + Math.Pow(g.Size.Y * TextureSize, 2)) + 0.5));  
            objectBuffer = new Bitmap((int) realSize.X, (int) realSize.Y);
            objectBufferGraphics = Graphics.FromImage(objectBuffer);
          }
          else
          { 
            objectBufferGraphics.Clear(Color.Transparent);
          }
                
          objectBufferGraphics.TranslateTransform((float)objectBuffer.Width / 2, (float)objectBuffer.Height / 2);
          objectBufferGraphics.RotateTransform(angle = Vector2.Up.GetAngle(g.Up));
          objectBufferGraphics.TranslateTransform((realSize.X - size.X - objectBuffer.Width) / 2f, (realSize.X - size.Y - objectBuffer.Height) / 2f);
      
          g.Draw(objectBufferGraphics, size, elapsed);

          objectBufferGraphics.ResetTransform();

          graphics.Graphics.DrawImage(objectBuffer, new RectangleF((g.Location * tileSize - (realSize - size) / 2f).ToPoint(), (tileSize * g.Size + realSize - size).ToSize()));          
          
          lastSize = g.Size;
        }

        graphics.Graphics.DrawString(String.Format("FPS: {0:0.0} Drawing   {1:0.0} Updating \n\rRotation: {2:0.00}°", fpsD.Length / fpsD.Sum(), fpsU.Length / fpsU.Sum(), angle),
        SystemFonts.MenuFont, new SolidBrush(Color.Red), 00);
        graphics.Render(Renderer);

Also mein objectBuffer ist der Zwischenpuffer auf den jedes GameObject nacheinander zeichnet. Davor wird die Matrix der zugehörigen Graphics-Instanz so manipuliert, dass die gewünschte Rotation auftritt.
Das funktioniert per se auch wunderbar (schaffe sogar 60Hz durch Multimedia-Timer!).

Zu Testzwecken zeichne ich in den Zwischenpuffer einfach ein Rechteck und ändere die GameObject.Up bei jedem Frame, sodass sich das Rechteck permanent dreht. Mein Problem ist nur, dass beim rendern der Umriss des Rechteck nicht glatt ist, sondern verpixelt. Je nach Drehwinkel unterschiedlich stark.

Im Anhang ist ein Screenshot von besagtem Rechteck.

Beide Graphics-Objekte werden gleich initialisiert:
ausblenden C#-Quelltext
1:
2:
3:
4:
      graphics = BufferedGraphicsManager.Current.Allocate(Renderer, Surface);
      graphics.Graphics.InterpolationMode = InterpolationMode.Bicubic;
      graphics.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
      graphics.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;


Weiß jemand warum das Rechteck so verpixelt und was ich dagegen tun kann - außer die Texturauflösung zu ändern und dann praktisch downsampling zu betreiben?


Nachtrag
Ich habe jetzt mal nen Performance-Test gemacht :D. Mein Renderingverfahren ist glaube ich suboptimal :mrgreen:. Ich habe mal 500 rotierende Rechtecke aufs Feld geworfen und schon hab ich einen Einbruch der FPS im Drawing-Thread auf 25 fps statt 60. Der Update Thread läuft aber solide mit 60 fps weiter. Achso der Performance-Test war jetzt ohne AntiAliasing und Interpolation. Wenn ich AntiAliasing rein mache und die Interpolation auf bikubisch, hab ich noch 16 fps :mrgreen:

Hat jemand nen Tip wie ich das Zeichnen optimieren könnte (so als kleine Nebenfrage)?
Hat jemand eine
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4799
Erhaltene Danke: 1059

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 08.09.14 12:13 
Hallo C#,

für dein AntiAlias-Problem habe ich leider keine Lösung, aber ich kenne noch diesen Artikel Zeichnen Optimieren / Schnelles zeichnen (der auch mehrere Testprojekte enthält), welcher sich mit deiner letzten Frage beschäftigt.

PS:
C# hat folgendes geschrieben:
Hat jemand eine
- ja, was denn? :lol:

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 08.09.14 12:49 
Hoppla, dass muss wohl im Halbschlaf gewesen sein :mrgreen:

Dein Link werde ich mir mal ansehen. Ich habe gestern noch etwas rumexperimentiert und mal zu den Rechtecken noch Kreise gemacht, die mit einem GradientBrush gefüllt werden und dass sieht dann richtig schlimm aus :nixweiss:. Im Anhang habe ich einen Screenshot. Da sieht man wie es aussieht wenn es gedreht wird und wenn es starr bleibt.

Nochmal der prinzipielle Ablauf meines Zeichenverfahrens:

1. Bitmap erzeugen mit entsprechender Größe (konstante Werte: 64x64, 128x128, ...)
2. Graphics von Bitmap erstellen.
3. Bitmapgraphics-Tranform-Matrix verschieben und drehen.
4. GameObject zeichnet auf Bitmap (Bitmapgraphics wird als Parameter übergeben).
5. Bitmapgraphics-Tranform-Matrix zurücksetzen.
6. Renderergraphics zeichnet Bitmap - je nach Zeichenfläche skaliert - in den Puffer der BufferedGraphics
7. Rebderergraphics wird gerendert.

Wie gesagt dass ist nur der grobe Ablauf. Ich habe schon ein paar Optimierungen drin, dass nicht jedes mal eine neue Bitmap erstellt werden muss, etc.

Weiß jemand warum die Formen so unsauber werden?
Einloggen, um Attachments anzusehen!
_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 08.09.14 17:55 
Also user profile iconTh69, ich habe mir mal den Link (und alle Sublinks :mrgreen:) durchgelesen. Der Sinn dahinter und die Funktionsweise ist mir klar, jedoch weiß ich nicht wie ich das auf meine Mini-Engine anwenden soll :(.

Ich hätte halt gerne, dass man in der Draw()-Methode der einzelnen GameObjects keine zu großen Gedanken um Performance machen muss, weil das später wichtig für den Spielablauf ist. Mein GameManager - die Klasse, die Update() und Draw() aufruft und die Gesamtszene rendert - sollte nach Möglichkeit die harte Arbeit machen. Wenn ich in jedem GameObject z.B. explizit die Bildänderung zum vorherigen Zeichenvorgang ermitteln muss oder den zu zeichnenden Bereich berechnen muss, wird das Ganze recht kompliziert. Ich möchte GameObject so einfach lassen wie nur möglich.

Weiß jemand wie das bei richtigen 2D Engines gehandhabt wird?

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 918
Erhaltene Danke: 158

Win 10
VS 2013, VS2015
BeitragVerfasst: Mo 08.09.14 20:40 
Ich vermute jetzt mal, dass du da zu viele "Layer" drin hast :P

Ich habe das immer so gemacht: Der GameManager hat ein großes Bild und eine Liste an GameObjects (oder so). Jedes GameObject benötigt natürlich genügend Infos, um sich komplett zu zeichnen. Dazu gehört in deinem Fall (rotierende Rechtecke) Position (X, Y) aktueller Winkel (phi) und Drehgeschwindigkeit (omega).
Es sind natürlich auch Objekte ohne Position denkbar, zum Beispiel ein Hintergrundbild.

Zu Beginn eines Frames löscht der Manager sein komplettes Bild oder erzeugt ein neues. Dann geht er seine Liste durch und übergibt jedem GameObject sein Graphics-Objekt. Jedes GameObject zeichnet sich dann darauf. Nachdem alle Fertig sind, wird (mit dem GUI-Thread synchronisiert) das Bild auf die GUI gemalt.

Die einzelnen GameObjects haben somit kein Bitmap, sondern bekommen immer nur gesagt "hier haste was, mal dich mal drauf"

Klar, das heißt natürlich, dass man im GameObject.Paint() trigonometrische Berechnungen durchführen muss. Dafür braucht es dann vielleicht eine Funktion, die direkt ein Array von PointF transformiert.

Für diesen Beitrag haben gedankt: C#
C# Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 561
Erhaltene Danke: 65

Windows 10, Kubuntu, Android
Visual Studio 2017, C#, C++/CLI, C++/CX, C++, F#, R, Python
BeitragVerfasst: Mo 08.09.14 22:49 
Okay habe jetzt mal den Zwischenpuffer rausgeschmissen. Jetzt schaffe ich 1000 Objekte mit 44 fps. Und dass bei AntiAliasing und bikubischer Interpolation. Wenn ich beide deaktiviere, schaffe ich sogar 2000 Objekte mit 44 fps.

Das Aliasing ist auch weniger geworden, seit ich direkt auf die Gesamtszene zeichne.

_________________
Der längste Typ-Name im .NET-Framework ist: ListViewVirtualItemsSelectionRangeChangedEventHandler