[OpenGL] Echtzeittaugliche Blur-Effekte

Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Neue Antwort erstellen

Lobby

Betreff: [OpenGL] Echtzeittaugliche Blur-Effekte

BeitragDi, März 19, 2013 21:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Um das vornweg klarzustellen, es werden keinerlei Kenntnisse zu OpenGL vorausgesetzt. Es geht schlichtweg um einen solchen Grafikeffekt:
user posted image
(die Szene stammt aus einem Spiel das an dieser Stelle nebensächlich ist)

Dieses Weichzeichnen lässt sich auch mit BlitzMax und Max2D echtzeittauglich realisieren. Eine Möglichkeit dazu werde ich im Folgenden aufzeigen. Die Erklärungen mögen nicht sonderlich tiefgreifend sein, dennoch habe ich mich für diesen Thread gegen das Codearchiv entschieden, weil ja doch ein erklärendes Element vorhanden ist, das über den Code hinaus geht.

Die Methodik dahinter
Zu erst einmal geht es um die simple Frage, wie man solche Verwischeffekte wie im Bild oben gezeigt, erzeugen kann - und das auch noch echtzeittauglich. Die Antwort hierrauf heißt Akkumulationspuffer. Das ist ein Bildpuffer, vergleichbar mit den Imagebuffern von Blitz3D. Das Besondere: Den Inhalt des Backbuffers kann man additiv auf den Inhalt des Akkumulationspuffers anwenden, zudem kann man den Akkumulationsbuffer wiederum in den Backbuffer ausgeben. Für den einzuspeisenden Backbuffer kann man zudem einen Helligkeitsfaktor angeben, sodass man auf die Helligkeit des Ergebnisses im Akkumulationspuffer Einfluss nehmen kann.
Soweit so gut, doch was nutzt das? Im Falle des Weichzeichnens kann man nun so vorgehen, dass man die gesamte Szene als Bild auffasst, je um ein paar Pixel verschoben in den Akkumulationspuffer einspeist, und den Endzustand des Akkumulationspuffers als Ergebnis ausgibt. Schon wäre die gesamte Szene leicht verschwommen und besäße bei geeignet gewählten Helligkeitsfaktoren für das Einspeisen ungefähr auch noch die gleiche Helligkeit wie vorher.
Nebenbei, der Akkumulationspuffer wird auch für Effekte wie Tiefenunschärfe und Antialiasing verwendet, ist also mit seiner einfachen Funktionsweise sehr vielseitig. Im Folgenden eine kleine Hilfsklasse die den Akkumulationspuffer in OpenGL etwas handlicher macht (TGLAccum.bmx). BlitzMax: [AUSKLAPPEN]
Rem
Little class to handle accum buffer in BlitzMax via OpenGL.

Written by Lobby Divinus
end rem


SuperStrict

Import pub.opengl

Type TGLAccum
Function add(fac:Float = 1.0)
glAccum(GL_ACCUM, fac)
End Function
Function Result(fac:Float = 1.0)
glAccum(GL_RETURN, fac)
End Function
Function Clear()
glClear(GL_ACCUM_BUFFER_BIT)
End Function
End Type

Kurz zur Verwendung, wenn man den Puffer nutzen will sollte man ihn zuerst mit Clear leeren, dann kann man mit add(fac:Float=1.0) und einem Faktor den Backpuffer additiv auf den Puffer anwenden so oft wie gewünscht. Schließlich gibt man mit Result(fac:Float=1.0) den Puffer in den Backpuffer aus. Der Akkumulationspuffer speichert jeden Farbwert übrigens in mehr als 8 Bit ab, sodass durch seine Verwendung die Anzahl der verschiedenen möglichen Farben nicht verloren gehen sollte.

Screen to image
Wer den vorherigen Abschnitt gelesen hat dürfte vielleicht bemerkt haben, dass man zuerst ein Bild von der Szene bräuchte, um so einen einfachen Unschärfeeffekt zu erzeugen (man könnte sie auch einfach mehrmals komplett, aber jedes mal ein wenig verschoben zeichnen, das würde sich anbieten wenn das noch performant genug wäre). BlitzMax bietet zwar eine Funktion GrabPixmap an, doch diese, inklusive dem Prozess um daraus wieder ein Bild zu machen, ist nicht gerade schnell wenn man den gesamten Grafikbereich damit erfassen will.
Was also tun? Das Langsame an dem genannten Vorgang ist unter anderem der Umweg von der Grafikkarte in den Arbeitsspeicher (die Pixmap), und von dort wiederum zur Grafikkarte. Schneller geht es in OpenGL etwa, indem man den Backbuffer mittels glCopyTexImage2D in die Textur eines bestehenden TImages kopiert. Dabei wird zwar nicht die zum TImage gehörende Pixmap mit geändert, aber da wir im Folgenden nicht geplant haben diese zu verwenden, ist das auch nicht weiter wichtig. Hier also eine Kleine Hilfsklasse dafür (TScreenToImage.bmx). BlitzMax: [AUSKLAPPEN]
Rem
This little class can be used to put the current screen in realtime into an image.
The returned image is y-reversed so that you have to draw it with setScale 1,-1 in order to draw it correctly.
The handle is already set to imageheight.

Written by Lobby Divinus
end rem


SuperStrict

Import brl.max2d
Import brl.glmax2d
Import pub.opengl

Type TScreenToImage
Global img:TImage

Function do:TImage(targetImage:TImage = Null, imageFlags:Int = 0)
Local gw:Int = GraphicsWidth()
Local gh:Int = GraphicsHeight()
Local img:TImage

If targetImage = Null Then
If Self.img = Null Or Self.img.width <> gw Or Self.img.Height <> gh Then
Self.img = CreateImage(gw, gh, 1, imageFlags)
End If
img = Self.img
Else
If targetImage.width <> gw Or targetImage.height <> gh Then
targetImage = CreateImage(gw, gh, 1, targetImage.flags)
End If
img = targetImage
End If
img.handle_x = 0
img.handle_y=gh

Local Frame:TGLImageFrame = TGLImageFrame(img.Frame(0))
glReadBuffer(GL_BACK)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, Frame.Name)
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1.0 / Frame.uscale, 1.0 / Frame.vscale, 0)

Return(img)
End Function
End Type

Für eigene Zwecke sollte es hier eigentlich ausreichen, die Methode do() ohne Parameter aufzurufen. Sie liefert schlicht ein TImage zurück, mit dem sich der aktuelle Inhalt des Backbuffers zeichnen lässt. Zu beachten ist, dass das zurückgegebene Bild an der Y-Achse gespiegelt ist, es also erst mit SetScale(1, -1) richtig herum dargestellt wird (das lässt sich bestimmt ganz leicht ändern, ist aber nicht so schlimm).

Weichzeichnen
So, und schon haben wir das Nötige um die oben gezeigten Weichzeichner zu implementieren. Ich gehe mal kurz darauf ein wie die drei gezeigten Arten erzeugt werden:
Scaled Blur (vgl. Zoom Blur)
Hierbei wird die Szene zu einem Punkt hin verschieden skaliert gezeichnet.
Twisted Blur
Hierbei zeichnet man die Szene zu einem Punkt hin gedreht.
General Blur
Das ist die bereits zu Anfang erwähnte Art, bei der die gesamte Szene unscharf wird indem man sie verschoben zeichnet.

Die genannten Weichzeichner-Arten sind keine Fachtermini, sondern nur frei erfunden. Wenn man nun einen jeweiligen Blur-Effekt mit an sich kleiner Wirkung zeichnet, dies jedoch mehrmals hintereinander, dann wird der Effekt um so stärker (so sind auch die Bilder oben entstanden). Nun fehlt nur noch eine Klasse, die diese simplen drei Typen zur Verfügung stellt (TScreenEffect.bmx). BlitzMax: [AUSKLAPPEN]
Rem
This class provides nice blur effects for your scene. You can switch off the Accumbuffer use by using useAccumBuffer(False).

Written by Lobby Divinus
end rem


SuperStrict

Import "TScreenToImage.bmx"
Import "TGLAccum.bmx"

Type TScreenEffect
Field img:TImage

Field useAccum:Int

Method New()
Self.img = CreateImage(1, 1, 1, FilteredImage)
Self.useAccum = True
End Method

Method useAccumBuffer(use:Int = True)
Self.useAccum = use
End Method

Method scaledBlur(x:Float, y:Float, steps:Int, quality:Float = 0.002)
Local scale:Float
For Local i:Int = 0 Until steps
scale = 1.0 + quality * 2 ^ i
Self.scaledBlurOneStep(x, y, scale)
Next
End Method

Method scaledBlurOneStep(x:Float, y:Float, scale:Float)
Local gh:Int = GraphicsHeight()
Self.img = TScreenToImage.do(Self.img)
Self.img.handle_x = x
Self.img.handle_y = gh - y
SetRotation(0)
If Self.useAccum Then
TGLAccum.Clear()
SetScale(1.0 / scale, -1.0 / scale)
DrawImage(Self.img, x, y)
TGLAccum.add(0.5)
SetScale(scale, -scale)
DrawImage(Self.img, x, y)
TGLAccum.add(0.5)
TGLAccum.Result()
Else
SetClsColor(0, 0, 0)
Cls()
SetBlend(LightBlend)
SetColor(127, 127, 127)
SetScale(1.0 / scale, -1.0 / scale)
DrawImage(Self.img, x, y)
SetScale(scale, -scale)
DrawImage(Self.img, x, y)
SetColor(255, 255, 255)
SetBlend(AlphaBlend)
End If
End Method

Method twistedBlur(x:Float, y:Float, steps:Int, quality:Float = 0.1)
Local rotation:Float
For Local i:Int = 0 Until steps
rotation = quality * 2 ^ i
Self.twistedBlurOneStep(x, y, rotation)
Next
End Method

Method twistedBlurOneStep(x:Float, y:Float, rotation:Float)
Local gh:Int = GraphicsHeight()
Self.img = TScreenToImage.do(Self.img)
Self.img.handle_x = x
Self.img.handle_y = gh - y
SetTransform(0, 1, -1)
If Self.useAccum Then
TGLAccum.Clear()
SetRotation(-rotation)
DrawImage(Self.img, x, y)
TGLAccum.add(0.5)
SetRotation(rotation)
DrawImage(Self.img, x, y)
TGLAccum.add(0.5)
TGLAccum.Result()
Else
SetClsColor(0, 0, 0)
Cls()
SetBlend(LightBlend)
SetColor(127, 127, 127)
SetRotation(-rotation)
DrawImage(Self.img, x, y)
SetRotation(rotation)
DrawImage(Self.img, x, y)
SetColor(255, 255, 255)
SetBlend(AlphaBlend)
End If
End Method

Method blur(steps:Int, quality:Float = 1.1)
Local radius:Float
For Local i:Int = 0 Until steps
radius = quality * 2 ^ i
Self.blurOneStep(radius)
Next
End Method

Method blurOneStep(radius:Float)
Self.img = TScreenToImage.do(Self.img)
SetTransform(0, 1, -1)
If Self.useAccum Then
TGLAccum.Clear()
DrawImage(Self.img, -radius, 0)
TGLAccum.add(0.25)
DrawImage(Self.img, radius, 0)
TGLAccum.add(0.25)
DrawImage(Self.img, 0, -radius)
TGLAccum.add(0.25)
DrawImage(Self.img, 0, radius)
TGLAccum.add(0.25)
TGLAccum.Result()
Else
SetClsColor(0, 0, 0)
Cls()
SetBlend(LightBlend)
SetColor(64, 64, 64)
DrawImage(Self.img, -radius, 0)
DrawImage(Self.img, radius, 0)
DrawImage(Self.img, 0, -radius)
DrawImage(Self.img, 0, radius)
SetColor(255, 255, 255)
SetBlend(AlphaBlend)
End If
End Method
End Type


Verwendung
Nun, das wären schon alle Klassen um auf eine gegebene Szene ein paar Weichzeichner-Effekte anzuwenden. Da ich mich hier auf OpenGL beschränke, muss man vor dem Aufruf von Graphics entsprechend den Grafiktreiber auf GLMax2DDriver() setzen und den Accumbuffer aktivieren. Das könnte dann so aussehen. BlitzMax: [AUSKLAPPEN]
SetGraphicsDriver(GLMax2DDriver(), GRAPHICS_BACKBUFFER | GRAPHICS_ACCUMBUFFER)

Nicht vergessen, das muss vor Graphics aufgerufen werden, damit es eine Wirkung hat. Die Flags schon hier mit anzugeben ist sinnvoll, da der Accumbuffer so auch in Canvas-Gadgets der MaxGUI verwendet werden kann (und auch sonst an allen Orten, wo man sonst nicht nach den flags gefragt wird).

Zu guter letzt noch ein Gesamtpaket das nochmal alle Klassen sowie ein Beispielprogramm enthält. Ob das Ganze tatsächlich Echtzeittauglich ist hängt von der Auflösung, der Anzahl der Iterationen sowie der Grafikkarte auf der das Programm läuft ab. Wenn ihr einen solchen Effekt verwenden wollt, dann bietet also unbedingt die Möglichkeit an, diese Effekte auszuschalten.

Dieses Tutorial erhebt keinen Anspruch auf Richtigkeit, Vollständigkeit, Qualität und Funktionsweise des bereitgestellten Codes. Ich hoffe in Zukunft mehr BlitzMax Projekte mit solchen Grafikeffekten ausgestattet zu sehen und wünsche noch viel Spaß beim selbst Experimentieren.
Lobby Divinus
  • Zuletzt bearbeitet von Lobby am Di, Jul 31, 2018 11:24, insgesamt 5-mal bearbeitet
 

PhillipK

BeitragMi, März 20, 2013 2:59
Antworten mit Zitat
Benutzer-Profile anzeigen
Coole sache. Auch wenn das Testprogramm auf höheren zoomstufen nervige Grafikfehler gibt, dennoch eine nette spielerei, um mal ohne shader ein paar weichzeichner einzubauen.

Störts dich, wenn ich das ganze nach c# portiere (OpenTK)? Mir steht grade der sinn nach spielerei Very Happy

Lobby

BeitragMi, März 20, 2013 10:32
Antworten mit Zitat
Benutzer-Profile anzeigen
Danke dir, mach mit dem Code was du willst Wink .
Nervige Grafikeffekte bei vielen Iterationen lassen sich über eine kleinere Zahl bei Quality erreichen (so heißt ein Parameter bei den Methoden scaledBlur, twistedBlur und blur in der Klasse TScreenEffect). Da dadurch aber auch die Zahl der Iterationen erhöht werden muss, um einen ähnlich starken Effekt zu erhalten, habe ich die Standardwerte für Quality möglichst groß gewählt. Bei hohen Auflösungen kann es auch sinnvoll sein, diese Zahl zu verkleinern.

Wenn das allein nicht das gewünschten Effekt bringt, kann es auch sinnvoll sein die einzelnen Blur-Effekte manuell zu bearbeiten - bei Scaled Blur beispielsweise wird derzeit ja immer sowohl auf den Punkt zu als auch von dem Punkt weg skaliert. Würde man das Hinskalieren durch eine unskalierte Szene ersetzen, so könnte man bei diesem Effekt die Rechteckbildung vermeiden. Für Twisted Blur würde es sich anbieten die Basis von rotation in der Methode twistedBlur() kleiner zu wählen, vl. sogar auf ein expotentielles Wachstum zu verzichten.

Die gezeigten Effekte lassen sich über den Backbuffer auch ohne Akkumulationspuffer erzeugen (was auf manchem Grafikkarten zudem schneller wäre), allerdings kann es dabei zu erheblichem Farbverlust kommen, zudem kann man nur schwer verhindern, dass dabei die Ränder dunkler werden.
  • Zuletzt bearbeitet von Lobby am Mi, März 20, 2013 11:57, insgesamt 3-mal bearbeitet

Lobby

BeitragMi, März 20, 2013 10:36
Antworten mit Zitat
Benutzer-Profile anzeigen
Upps, einen Doppelpost wollte ich gar nicht.
TheoTown - Eine Stadtaufbausimulation für Android, iOS, Windows, Mac OS und Linux
 

PhillipK

BeitragMi, März 20, 2013 11:19
Antworten mit Zitat
Benutzer-Profile anzeigen
Ah sehr schön, damit hats das direkt geklärt *grins*

Bin grade ein wenig eingespannt, deswegen konnte ich mir den code nonet durchlesen. Hab ihn nur grob überflogen (wow: So wenig für so viel? cool!)

Der Accumbuffer war mir bisher gänzlich unbekannt, ich hoffe, wenn ich den code portiere, lern ich ein wenig mehr Razz Mach ich undso! ^^ (aber erst ab freitag...)

Lobby

BeitragMi, März 20, 2013 12:25
Antworten mit Zitat
Benutzer-Profile anzeigen
Habe nun zwei nette Dinge hinzugefügt:
Die TScreenEffect-Klasse kann nun auch die Blur-Effekte rein über den Backbuffer erzeugen. Das ist um Einiges schneller, erzeugt aber möglicherweise ungewünschte, dunkle Ränder und kann zu sichtbarem Farbverlust führen. Im Beispielsprogramm kann die Nutzung des Akkumulationspuffers über die Leertaste ein- und ausgeschaltet werden.

Die Methoden zum Darstellen von Scaled- und Twisted Blur bieten jetzt Parameter x, y an, mit denen der Punkt um den der Effekt generiert werden soll, bestimmt werden kann. Im Beispielprogramm ist das der Mauszeiger.
TheoTown - Eine Stadtaufbausimulation für Android, iOS, Windows, Mac OS und Linux

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group