Entwickler-Ecke

Multimedia / Grafik - mit OpenGL Szene in Datei rendern


Xion - Sa 06.09.08 11:31
Titel: mit OpenGL Szene in Datei rendern
Hi,

ich möchte eine Szene in OpenGL als Bild speichern. Indem ich hier etwas rumgespielt habe (4x so große Fläche, nur ein Viertel aufm Bildschirm rendern), konnte ich ber "Druck"-Taste 4 Screenshots erstellen und diese zusammenfügen.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
  glViewport(00, VPanel.ClientWidth, VPanel.ClientHeight);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  gluPerspective(45.0, VPanel.ClientWidth/VPanel.ClientHeight, NearClipping, FarClipping);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity;


Ja, das ist Mist, ich weiß :mrgreen: Vor allem weil es nicht mit mehr als doeppelte Auflösung geht. Gibt es da nicht eine vernünftige Art, sowas zu machen?

Danke, Xion


BenBE - Sa 06.09.08 17:39

Ja, indem man sich einen Textur-Puffer anlegt und in diesen Rendert und dann diesen in ein Bitmap. Beispiele gibt's bei DelphiGL ...


toenne - So 07.09.08 01:53

Vielleicht hilfts: http://www.delphigl.com/forum/viewtopic.php?t=7772
Das überflüssige Zeugs mit dem leeren Bitmap in das dann der Snapshot kopiert wird kannst du ja weglassen ;).

Gruss
Toenne


Xion - So 07.09.08 10:44

@toenne: bei deinem Code raucht bei mir irgendwie die Grafikkarte ab oO

@BenBE: irgendwie find ich nix. ich find nie was bei DelhpiGL, kA, ich glaube ich bediene die Suche falsch :P

ich habs mal so gemacht ( von hier [http://www.delphigl.com/forum/viewtopic.php?t=3924&highlight=snapshot] )


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure SnapShot;
var ATarget: TBitmap;
begin
 ATarget:=TBitmap.Create;
 ATarget.PixelFormat := pfDevice;
 ATarget.Width := Screen.Width;
 ATarget.Height := Screen.Height;
 BitBlt(ATarget.Canvas.Handle, 00, Screen.Width, Screen.Height, DC, 00, SRCCOPY);
 ATarget.SaveToFile('test.bmp');
end;


Das Problem ist aber, dass ich damit ja nicht über die Bildschirmgröße rauskomme. Ich hätte es eigentlich gerne so groß, dass man es drucken kann (also ca. 3*3-fache Bildschirmgröße)


BenBE - So 07.09.08 12:45

Bzgl. Screenshots siehe auch http://wiki.delphigl.com/index.php/Screenshot


toenne - So 07.09.08 15:35

Zitat:
@toenne: bei deinem Code raucht bei mir irgendwie die Grafikkarte ab oO

Hast du ihn doch komplett kopiert oder wie?
Du brauchst doch nur...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//Snapshotroutine von WhiteHunter
 glGetIntegerv(GL_VIEWPORT, @Viewport);
 Rohbild := TBitmap.Create;
 try
 Rohbild.Width := Viewport[2];
 Rohbild.Height := Viewport[3];
 Rohbild.PixelFormat := pf24bit;
 for i := 0 to Viewport[3] - 1 do
 glReadPixels(0, Viewport[3] - i, Viewport[2], 1, GL_BGR, GL_UNSIGNED_BYTE, Rohbild.ScanLine[i]);

...und schon hast du eine Kopie in 'Rohbild'. Der ganze andere Krempel ist für dich irrelevant.
Aber wie ich dich jetzt verstehe willst du gar nicht den Bildschirminhalt kopieren sondern die ganze Szene? Hmm, was über die Bildschirmgrenzen hinausgeht wird doch gar nicht gerendert denke ich?

Gruss
Toenne


BenBE - So 07.09.08 16:18

Ich denke, er will eine Kopie eines Viewports haben, der größer als der Bildschirm ist ...


Lossy eX - Mo 08.09.08 11:18

Xion: Ich denke du hast 2 Möglichkeiten.

1) Wie es BenBE schon so vorgeschlagen hatte. Du erzeugst einen offscreen buffer und renderst dort dein Bild hinein. Das Problem ist aber Aufgrund des Formates den ein OffScreen Buffer (32Bit RGBA + Tiefenbuffer etc) hat steigt der Speicherverbrauch um ein vielfaches an. Und dessen Größe ist irgendwoe auch begrenzt. Oben drein kommt hinzu, dass du recht hohe Hardwareanforderungen hast. Aber mit einem FrameBuffer Object [http://wiki.delphigl.com/index.php/Tutorial_Framebufferobject] wäre es möglich einen solchen Buffer zu erstellen. Da der seine Daten in einer Textur ablegt wäre es ziemlich einfach die Daten abzufragen.

2) Du musst deine Szene X mal Rendern und immer die passenden Teile des Bildes kopieren und dir daraus ein großes Bild zusammen basteln. Es gab vor ewigen Zeiten auf delphi3d.net mal ein Demo dazu aber leider ist die Seite seit geraumer Zeit offline. Ich weiß es mittlerweile nicht mehr genau was der dort gemacht hatte deswegen kann ich nur einen eigenen Vorschlag machen. Allerdings habe ich keine Ahnung ob das immer so funktioniert.

Du kannst einen Viewport setzen der entsprechend groß ist. Allerdings der Framebuffer bekommt die maximal Größe des Fensters. Bei glViewport setzt du normal ClientHeight/ClientWidth. Wenn du aber deine gewünschte Größe einsetzt, dann wird das Bild entsprechend Groß gezeichnet. Du kannst bei X und Y dann aber entsprechende Werte setzen um den Viewport zu verschieben und einen anderen Teil in den Framebuffer zu verschieben. Der Einfachheit halber solltet du zu Begin erst mal mit einer Größe von 512x512 arbeiten. Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
glViewport(0081928192);
// zeichnen und kopieren

glViewport(512081928192);
// zeichnen und kopiren

glViewport(1024081928192);
// zeichnen und kopiren

usw.


Das müsstest du so häufig machen bist du alle Teile kopiert hast. Kopieren musst du dann Zeilenweise. Dazu solltest du dich mal in dem Screenshot Artikel im Wiki von DGL umschauen. Also prinzipiel mit glReadPixel. Du solltest glReadBuffer auf GL_BACK stellen und SwapBuffers nicht ausführen wärend du die Teile generierst. Dann wird aus dem nicht sichtbaren Backbuffer gelesen und niemand bekommt diese temporären Bilder zu sehen. Aber je nach Größe der Szene kann es dauern, da du im Falle von 8192x8192 die gesammte Szene 256 Mal zeichnen musst. Du kannst aber auch größere Stücke als 512x512 nehmen. Könnte nur beim Zusammenbauen etwas komplizierter/verwirrender werden.

Ich weiß allerdings nicht ob es zu einem Problem führen kann, wenn der Viewport zu groß wird. Mitunter können einige Karten nur einen Viewport von 4096x4096 Pixel. Ich weiß nicht ob sich das auf die maximale Anzeigegröße bezieht oder wirklich auf Width und Height bei glViewport. Die größe kannst du wie folgt abfragen.

Delphi-Quelltext
1:
2:
3:
4:
var
  VP: array[0..1of Integer;
begin
  glGetIntegerv(GL_MAX_VIEWPORT_DIMS, @VP);


Xion - Fr 12.09.08 14:53

wow, danke, ich hab momentan nicht so viel Zeit.

Also ich habs nach methode 2 schonmal probiert, hab sie jetzt auch einigermaßen zum laufen bekommen (bin am notebook fast verzweifelt, scheinbar kann es nicht mehr als 2x2 Bildschirme berechnen). Leider ist es nicht ganz einwandfrei, Size=2 funktioniert prima, Size=3 ist unten etwas abgeschnitten und Size=4 ist verzerrt oO

Hier mal der Code:

Delphi-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:
procedure TOpenGLWorks.SnapShot(Redraw: TTickProc; GameTimer: TGameTimer);
var ATarget: Graphics.TBitmap;  X,Y,W,H: integer;  Size: integer;
begin
 Size:=3;
 
 W:=VPanel.ClientWidth;
 H:=VPanel.ClientHeight;

 glViewport( 00, W*Size, H*Size );
 Redraw(0);


 ATarget:=Graphics.TBitmap.Create;
 ATarget.PixelFormat := pfDevice;
 ATarget.Width := W*Size;
 ATarget.Height := H*Size;
 GameTimer.Enabled:=False;
 for X:= 0 to (Size-1do
   for Y:= 0 to (Size-1do
     begin
       glViewport( -X*W, -Y*H, W*Size, H*Size );
       Redraw(0);
       glViewport( -X*W, -Y*H, W*Size, H*Size );
       Redraw(0);
       BitBlt(ATarget.Canvas.Handle, X*W, ( (Size-1)-Y)*H, W,H, DC, 00, SRCCOPY);
     end;
 ATarget.SaveToFile('test.bmp');

 glViewport( 00, W, H );
 Redraw(0);
 GameTimer.Enabled:=True;
end;


Zudem verstehe ich nicht, warum ich 2x die Szene rendern muss, um ein anständiges Bild zu bekommen (sonst kommt jeder Block doppelt)


Xion - Sa 13.09.08 12:54

das hier ist durch den obigen Code entstanden...das Ergebnis ist mir unerklärlich (das rausgeschnittene Eck).

Originalgröße: 4428x3328

Anmerkung: er scheint irgendwo nicht lange genug zu warten, bis er es als Screenshot ausliest. Ich denke mal das ist die differenz zwischen dem fertigen rendern und dem tatsächlichen anzeigen auf dem Bildschirm. Ich habs mal mit glReadPixels versucht, funktioniert aber leider so nicht:


Delphi-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:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
procedure TOpenGLWorks.SnapShot(Redraw: TTickProc; GameTimer: TGameTimer; Size: integer);
var ATarget, SubTarget: Graphics.TBitmap;  X,Y,W,H: integer; DestRect,TargetRect: TRect;
begin

 W:=VPanel.ClientWidth;
 H:=VPanel.ClientHeight;

 glViewport( 00, W*Size, H*Size );
 Redraw(0);

 DestRect.Left:=0;
 DestRect.Top:=0;
 DestRect.Right:=W;
 DestRect.Bottom:=H;


 ATarget:=Graphics.TBitmap.Create;
 ATarget.PixelFormat := pfDevice;
 ATarget.Width := W*Size;
 ATarget.Height := H*Size;

 SubTarget:=Graphics.TBitmap.Create;
 SubTarget.PixelFormat := pfDevice;
 SubTarget.Width := W;
 SubTarget.Height := H;

 GameTimer.Enabled:=False;
 for X:= 0 to (Size-1do
   for Y:= 0 to (Size-1do
     begin
       glViewport( -X*W, -Y*H, W*Size, H*Size );
       Redraw(0);
       glReadPixels( X*W, Y*H, W, H, GL_RGB, GL_BITMAP, @SubTarget );

       TargetRect.Left:=X*W;
       TargetRect.Top:=Y*H;
       TargetRect.Right:=W+X*W;
       TargetRect.Bottom:=H+Y*H;

       ATarget.Canvas.CopyRect(TargetRect,SubTarget.Canvas,DestRect);
     //  BitBlt(ATarget.Canvas.Handle, X*W, ( (Size-1)-Y)*H, W,H, DC, 0, 0, SRCCOPY);
     end;
 ATarget.SaveToFile('test.bmp');

 glViewport( 00, W, H );
 Redraw(0);
 GameTimer.Enabled:=True;
end;


toenne - Mo 15.09.08 20:53

Willkommen im Club: http://www.delphigl.com/forum/viewtopic.php?p=65288#65288
Ich habs mittlerweile aufgegeben mir über das zweifache Rendern den Kopf zu zerbrechen...ist halt so, Hauptsache es funktioniert...

Gruss
Toenne