Surfaces - Vormultiplizierte Transparenz - Was ist das?

    • GM 8

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Surfaces - Vormultiplizierte Transparenz - Was ist das?

      Gleich am Anfang: Dieses Tutorial beschreibt nicht, was Surfaces sind und welche Befehle es zur Nutzung deren gibt. Hierzu hat DAG schon ein ausführliches Tutorial gemacht und ich empfehle jedem, der das Tutorial hier liest, dass er sich schon recht gut (zumindest was die Surfaces und Blend Modes angeht) in GML auskennt.

      Warum dieses Tutorial?
      Da ich mich in letzter Zeit sehr viel mit Surfaces beschäftigt habe, und es bei diesen bösen Dingern einiges zu beachten gibt, habe ich mich entschlossen dieses Tutorial hier zu machen. Hauptsächlich will ich mich hier allerdings auf die Alpha-Transparenz "Probleme", die bei der ganzen Sache auftreten konzentrieren und diese mithilfe der premultiplied Alpha (zu dt.: "Vormultiplizierte Tranparenz") mehr oder weniger beheben.

      Was ist falsch mit der Transparenz?
      Nun, ich will das ganze mal mit drei Bildern verdeutlichen.


      Dieses Stück moderne Kunst wird euch leider das ganze Tutorial verfolgen.

      Also, was sieht man?
      Links ist der Sprite, der hier verwendet wird (das Raster stellt dabei die transparenten Stellen dar).
      Rechts davon ist der Sprite ganz normal im Room (und somit auf dem Backbuffer ->Doppelpufferung) mit einem Schwarzen Background gezeichnet.
      Rechts daneben wird der gleiche Sprite auf eine zuvor erstellte Surface gezeichnet (diese wurde ebenfalls mithilfe von draw_clear(c_black) schwarz gefärbt) und man erkennt, dass etwas Transparenz an den Seiten auftaucht (In weiß veranschaulicht).

      Warum passiert das?
      Das hat mit dem Standard Blendmodus (bm_normal) zutun.
      Dieser funktioniert nämlich so (Jeder Pixel wird einzeln berechnet):

      resultierendeFarbe(rot, grün, blau, alpha) = (neueFarbe(rot, grün, blau, alpha) * neueFarbe.alpha) + (jetzigeFarbe(rot, grün, blau, alpha) * (1-neueFarbe.alpha))
      draw_set_blend_mode(bm_normal) / draw_set_blend_mode_ext(bm_src_alpha, bm_inv_src_alpha)

      Das bedeutet in Worten:
      Die resultierende Farbe setzt sich aus der Rot, Grün, Blau, Alpha Mischung aus der zu zeichnenden Farbe, bei der jede Komponente jeweils mit der Alpha dieser Farbe multipliziert wurde plus die Farbe, die an dieser Stelle schon existiert, bei der jede Komponente jeweils mit der "negativen" Alpha der zu zeichnenden Farbe multipliziert wurde zusammen.


      Wenn man jetzt zum Beispiel einen Pixel mit der Farbe (1, 1, 1, 0.5) (1 ist in diesem Fall der höchst mögliche Wert) auf einen Pixel mit der Farbe (0, 0, 0, 1) zeichnet, erwartet man wohl die Farbe (0.5, 0.5, 0.5, 1):
      Kurz vorweg: es ist nicht so.

      Um dies zu zeigen, hier die Gleichung für die Transparenz (Alpha) bei bm_normal:

      resultierendeFarbe.alpha = (neueFarbe.alpha * neueFarbe.alpha) + (jetzigeFarbe.alpha * (1-neueFarbe.alpha))

      ... was mit den Zahlen aus dem Beispiel aufgelöst folgendes ergibt:

      resultierendeFarbe.alpha = (0.5 * 0.5) + (1 * 0.5)

      Somit ist die resultierende Transparenz nicht 1, sondern 0.75.
      Man erhält also die Farbe (0.5, 0.5, 0.5, 0.75).

      Warum ist das auf dem Backbuffer anders?
      Der Backbuffer hat mit Transparenz nichts am Hut. Er ignoriert einfach die ankommenden Transparenzwerte und behält seine Transparenz immer auf 1.
      Um das zu überprüfen setzen wir die Transparenz vom obigen Beispiel auf 1, und siehe da, die Farbe ist nun logischerweise (0.5, 0.5, 0.5, 1).
      Nachtrag: Seit GM: Studio kann man nun auch mit draw_set_color_write_enable(r, g, b, a) individuelle Farbchannels deaktivieren.

      Wie kann man diesen Fehler beheben?
      Nun, ich schätze mal, wir wollen die Transparenz, anders als im Backbuffer, noch mit in der Surface haben ohne, dass diese Probleme entstehen.
      Also, wie stellt man das an? Die Antwort auf diese Frage ist premultiplied Alpha und somit kommen wir endlich zum Thema dieses Tutorials!

      Was genau ist das?
      Hier sind wieder Bilder:




      Links ist jeweils der Sprite und rechts ist der selbe Sprite, jedoch mit der Transparenz von 1 bei jedem Pixel (Also komplett ohne).

      Wofür ich das mache?
      Abgesehen davon, dass es oben schon echt cool aussieht, hat man hier zwei Arten der "Bildspeicherung":

      Oben ist es der normale Sprite, der einfach getrennt Farbwerte und Alphawerte speichert.
      Unten dagegen ist der Farbwert mit dem Alphawert multipliziert (daher premultiplied Alpha ;) ).

      Was genau bringt uns das?
      Die Multiplizierung der Farbwerte mit den Alphawerten bei der neuenFarbe fällt nun weg.
      Das heißt, die Quadrierte 0.5 bei unserem Beispiel, wird nun nicht mehr Quadriert.
      Allerdings gibt es keinen Blend Mode, der nur die Farbwerte rot, grün und blau mit der Alpha multipliziert ohne, dass die Transparenz mit multipliziert wird.
      Daher multiplizieren wir die Farben vorher mit der Transparenz, damit der Blend Mode das hier nicht machen muss.

      Unser neuer Blend Mode sieht nun so aus:

      resultierendeFarbe(rot, grün, blau, alpha) = (neueFarbe(rot, grün, blau, alpha)) + (jetzigeFarbe(rot, grün, blau, alpha) * (1-neueFarbe.alpha))
      draw_set_blend_mode_ext(bm_one, bm_inv_src_alpha)


      In unserem Beispiel käme, wenn man vorher die Farben vormultipliziert, folgendes raus:

      Vormultiplizieren: neueFarbe(1, 1, 1, 0.5) -> neueFarbe(0.5, 0.5, 0.5, 0.5)

      Ausrechnen: resultierendeFarbe = neueFarbe(0.5, 0.5, 0.5, 0.5) + jetzigeFarbe(0, 0, 0, 1) * (1-neueFarbe.alpha) = (0.5, 0.5, 0.5, 0.5) + (0, 0, 0, 0.5) = (0.5, 0.5, 0.5, 1)

      Nun erhält man tatsächlich die gesuchte Farbe.

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von Joex3 ()

    • [nach oben]

      Wie verwendet man das im Game Maker?
      Man muss vor dem Zeichnen auf der Surface erst den Blend Mode ändern:
      draw_set_blend_mode_ext(bm_one, bm_inv_src_alpha);
      Und anschließend beim Zeichnen der Surface genau den selben Blend Mode setzen (Da sie ja dann ebenfalls vormultipliziert ist).

      1. Primitives
      Hiermit sind die Standard Zeichenfunktionen wie draw_rectangle(...) oder draw_vertex(...) gemeint.
      Hier ist es wirklich einfach!
      Alles, was man machen muss, ist die Farbe vorher mit dem Alphawert zu multiplizieren!

      Hierzu schreibt man sich am besten ein Skript, welches diese Aufgabe abnimmt:

      GML-Quellcode

      1. // premultiplyColor(color [, alpha]);
      2. var alpha;
      3. if(argument_count>1)
      4. alpha = argument[1];
      5. else
      6. alpha = draw_get_alpha();
      7. return make_color_rgb( color_get_red(argument0)*alpha,
      8. color_get_green(argument0)*alpha,
      9. color_get_blue(argument0)*alpha);
      Alles anzeigen


      Und dann können wir mit dem Zeichnen beginnen:

      GML-Quellcode

      1. // Angenommen, wir zeichnen auf eine Surface
      2. draw_clear(c_black);
      3. draw_set_blend_mode_ext(bm_one, bm_inv_src_alpha);
      4. draw_set_alpha(0.4);
      5. draw_set_color( premultiplyColor( c_red ) ); // Rot mit dem jetzigen Alphawert vormultiplizieren
      6. draw_rectangle(32, 32, 64, 64, false);
      7. draw_set_color( premultiplyColor( c_blue, 0.6 ) ); // Blau mit einem eigenen Alphawert vormultiplizieren
      8. draw_set_alpha(0.6);
      9. draw_rectangle(48, 48, 96, 96, false);
      10. draw_set_blend_mode(bm_normal);
      Alles anzeigen


      2. Grafiken
      Hiermit sind die Zeichenfunktionen wie draw_sprite*(...) oder draw_background*(...) gemeint.
      Hier ist es etwas schwieriger.
      Man könnte die Sprite/Backgrounds vorher mit dem Bildbearbeitungsprogramm seiner Wahl vormultiplizieren, aber das wäre viel zu viel Arbeit.

      Deshalb habe ich ein Programm* geschrieben, welches alle Bilddateien aus einem Ordner in einen neuen Ordner kopiert, wo jeweils alle Farben vormultipliziert sind.

      Dann kann man die Grafiken einfach als Sprite/Background im GM laden und schon kann man sie zeichnen.

      GML-Quellcode

      1. // Angenommen, wir zeichnen auf eine Surface
      2. draw_clear(c_black);
      3. draw_set_blend_mode_ext(bm_one, bm_inv_src_alpha);
      4. draw_sprite(spr_test, 0, 32, 32);
      5. draw_background_ext(bg_test, 32, 32, 1, 1, 0, premultiplyColor( c_white, 0.5 ), 0.5); // Hier muss die Farbe noch extra multipliziert werden, da der Background nicht komplett sichtbar gezeichnet wird.
      6. draw_set_blend_mode(bm_normal);


      * Zur Benutzung des Programmes
      Download: [url=''http://www.mediafire.com/?e6ge3xzvaieb47b']mediafire.com[/url]

      Nachdem man es gedownloaded hat, packt man es in einen Ordner mit einem Unterordner namens "gfx_src".
      Dort (Unterverzeichnisse á la "gfx_src/xy/cv/..." werden miteinbezogen) packt man dann alle seine Grafiken rein, die vormultipliziert werden sollen.
      Nun führt man dass Programm aus und siehe da, es entsteht ein Ordner namens "gfx" (Wenn alles gut ging ;)), in dem alle Grafiken wie schon erwähnt vormultipliziert wurden.
      Schon kann man die Grafiken in den GM einbinden.

      Das war's schon!
      Obwohl ich mir Mühe gegeben habe, alles ausführlich und genau zu erklären, kann es immer sein, dass Fragen aufkommen.
      In diesem Fall könnt ihr mir einfach eine PM oder hier im Thread die Frage stellen.
      Ich bemühe mich dann, diese zu beantworten.
      Über Feedback wäre ich auch sehr dankbar ;).

      Ansonsten hoffe ich, dass man doch etwas aus diesem Tutorial lernen konnte und Surfaces jetzt öfter genutzt werden (ich mag sie einfach :P).
      Vielen Dank für's Lesen!

      - Tobi97

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von Joex3 ()

    • Schön!
      Hab extra darauf gewartet bis ich dies wieder brauchen würde bevor ich hier schreibe. Wirklich sehr gut erklärt. Hatte selbst von dem zeug oftmals gehört aber konnte (bis jetzt) nie dazu bewegt werden es korekt anzuwenden (anstelle speicherverschwendende workarounds zu nutzen). :)
      Einziges Manko: das Programm funktioniert leider bei mir nicht. Es startet und findet auch die Dateien und sagt sie wären behandelt worden... ich kann aber nirgens einen src Ordner finden nach dem enter drücken :/
      Es ist doch ein GM Programm (oder nicht? hab durch die dateigröße drauf geschlossen, lol). Was würde denn dagegen sprechen es als Opensource zu veröffentlichen?

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)
    • Erst einmal, danke für das Feedback, ich dachte schon, niemand würde hier schreiben ;)

      DragonGamer schrieb:

      Einziges Manko: das Programm funktioniert leider bei mir nicht. Es startet und findet auch die Dateien und sagt sie wären behandelt worden... ich kann aber nirgens einen src Ordner finden nach dem enter drücken :/

      Versuch eventuell das "Programm" mal als Admin auszuführen, vielleicht liegt's daran.

      DragonGamer schrieb:

      Es ist doch ein GM Programm (oder nicht? hab durch die dateigröße drauf geschlossen, lol). Was würde denn dagegen sprechen es als Opensource zu veröffentlichen?

      Hihi, war klar das es jemanden auffällt :D
      Der Download Link beinhaltet jetzt auch die Source (Für das Vormultiplizieren ist consolePremultiplyFile zuständig).

      Ich hoffe, ich konnte die Fragen beantworten ;)

      - Tobi97
    • Tobi97 schrieb:


      Versuch eventuell das "Programm" mal als Admin auszuführen, vielleicht liegt's daran.
      Leider kein Unterschied :(
      Sehe mir jetzt die source an, danke :)

      Tobi97 schrieb:


      DragonGamer schrieb:

      Es ist doch ein GM Programm (oder nicht? hab durch die dateigröße drauf geschlossen, lol). Was würde denn dagegen sprechen es als Opensource zu veröffentlichen?

      Hihi, war klar das es jemanden auffällt :D
      Der Download Link beinhaltet jetzt auch die Source (Für das Vormultiplizieren ist consolePremultiplyFile zuständig).
      Naja, das hat man davon wenn man den Game Maker mit seinem 3,9 MB runner verwendet xP

      EDIT:
      Also ich weis nicht so ganz wie das gedacht war... aber bei mir wird in dem Skript "consoleReadFiles" niemals der Abschnitt ausgeführt wo drinnsteht:

      GML-Quellcode

      1. consoleTextAdd("Read directory '"+string(_arg_in+"\"+_f)+"'");
      2. directory_create(working_directory+"\"+_arg_out+"\"+_f);
      Somit wird niemals irgendein neuer Ordner erstellt...
      Wenn man die Zeile

      GML-Quellcode

      1. if !directory_exists(working_directory+"\"+_arg_out) directory_create(working_directory+"\"+_arg_out);
      einfach in den ersten Abschnitt unter den if-abfragen einfügt, dann funktioniert alles.
      Trotzdem klasse tutorial und tool!

      Willst du auf diese Drachen und -eier klicken?
      Sie werden sich freuen ;)

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von DragonGamer ()

    • Hi Tobi97(Joex3), ich danke dir vielmals für dein Tutorial, auch wenn das jetzt schon eine Zeit her ist das du es geschrieben hast :)
      Es hat mich sehr motiviert etwas näher nachzuforschen, da ich auch das Problem mit dem Alpha-Wert hatte.

      Tobi97 schrieb:

      2. Grafiken
      Hiermit sind die Zeichenfunktionen wie draw_sprite*(...) oder draw_background*(...) gemeint.
      Hier ist es etwas schwieriger.
      Man könnte die Sprite/Backgrounds vorher mit dem Bildbearbeitungsprogramm seiner Wahl vormultiplizieren, aber das wäre viel zu viel Arbeit.

      Deshalb habe ich ein Programm* geschrieben, welches alle Bilddateien aus einem Ordner in einen neuen Ordner kopiert, wo jeweils alle Farben vormultipliziert sind.

      Dann kann man die Grafiken einfach als Sprite/Background im GM laden und schon kann man sie zeichnen.


      Nachdem ich in mein Projekt einfach einen Umschaltmodus für draw_set_blend_mode_ext eingebaut hatte bin ich nach kurzem zu der lösung gekommen das es ganz einfach geht mit folgendem blend mode:

      GML-Quellcode

      1. draw_set_blend_mode_ext(bm_src_alpha_sat, bm_one);