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.
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 ()