2015-07-17 5 views
7

Ich habe ein LibGDX-Spiel mit Cartoon-Wolken mit einem glatten Farbverlauf. Es gibt andere Beispiele für Farbverläufe im Spiel, die ein ähnliches Problem haben, aber die Wolken sind das offensichtlichste Beispiel. Sie sehen gut aus in Android, auf iOS und auf der Desktop-Version des Spiels, aber in der WebGL-Version werden die Farbverläufe nicht so glatt gezeichnet. Es scheint nur Alpha-Gradienten zu sein, die das Problem haben. Andere Farbverläufe sehen gut aus.Alpha-Gradienten nicht glatt in WebGL bei Verwendung von Multi-Multiplikator Alpha

Ich habe auf 3 verschiedenen Geräten in Chrome und IE versucht, und alle 3 produzieren die gleichen Ergebnisse. Einen Test der HTML5-Version finden Sie hier.

https://wordbuzzhtml5.appspot.com/canvas/

Ich habe ein Beispiel IntelliJ Projekt auf GitHub hier

https://github.com/WillCalderwood/CloudTest

hinzugefügt

Wenn Sie IntelliJ haben, dieses Projekt klonen, öffnen Sie die Datei build.gradle, drücken Sie Alt-F12, geben Sie gradlew html:superdev und wechseln Sie dann zu http://localhost:8080/html/

Der kritische Code render()here

ist

Das untere Bild hier ist die Desktop-Version, die obere ist die WebGL-Version, beide laufen auf der gleichen Hardware.

enter image description here

Es gibt nichts, klug mit der Zeichnung geht. Es ist nur ein Aufruf an

spriteBatch.draw(texture, getLeft(), getBottom(), getWidth(), getHeight()); 

Ich bin mit den Standard-Shader, mit integrierten Alpha verpackt Texturen mit der Funktion Mischung eingestellt als

spriteBatch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA); 

Dies ist das aktuelle Bild, obwohl alpha nicht Premultiplied wie das ist, getan von meinem Packer.

enter image description here

Kennt jemand einen möglichen Grund dafür und wie könnte ich es beheben?

aktualisieren

Dies erscheint nur, wenn Sie die Füllmethode GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA

Ein weiteres Update

ich versucht, mit passieren haben das ganze Spiel zu ändern Alpha Texturen verwenden nicht-premultiplied. Ich benutze den Texture Packer, der helfen kann, die Halo-Probleme zu beheben, die oft bei nicht-multipliziertem Alpha auftreten. All dies funktioniert in der Android- und Desktop-Version. In der WebGL-Version bekomme ich, während ich weiche Gradienten bekomme, immer noch einen kleinen Halo-Effekt, also kann ich das auch nicht als Lösung verwenden.

Und noch ein Update

Hier ist ein neues Bild. Desktop-Version oben, Web-Version unten.Mischmodus GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA auf der linken Seite und GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA auf der rechten

enter image description here

Hier ist eine vergrößerte Version des unteren linken Bild oben mit erhöhtem Kontrast um das Problem zu zeigen.

enter image description here

Ich habe auch eine Menge zu spielen mit dem Fragment-Shader, um zu versuchen und herauszufinden, getan, was passiert. Wenn ich

gesetzt
gl_FragColor = vec4(c.a, c.a, c.a, 1.0); 

dann ist die Steigung glatt, aber wenn ich gesetzt

gl_FragColor = vec4(c.r, c.r, c.r, 1.0); 

Dann bekommen Banding ich. Dies deutet auf ein Genauigkeitsproblem hin, da die Farbkanäle durch den Vormultiplikationsprozess in das dunklere Ende des Spektrums gedrückt wurden.

+0

Welches Texturformat verwenden Sie und was ist Ihr Frambuffer-Format? RGBA8 unkomprimiert? – solidpixel

+0

@ Isogen74 Ja, RGBA8 unkomprimiert. –

+0

Bist du sicher, dass das Ziel Alpha 1 ist? webgl wird mit dem Seitenhintergrund als primult alpha zusammengesetzt. Um klar zu sein, löschen Sie Alpha vor dem Blit. – starmole

Antwort

3

Ich verbrachte den größten Teil des Tages damit, das zu untersuchen, weil ich genau dieses Problem sehe. Ich denke, ich bin endlich auf den Grund gegangen.

Dies wird durch die Art verursacht, wie libGDX Bilder lädt. Eine Textur wird von einem Pixmap auf allen Plattformen erstellt, wobei ein Pixmap im Grunde ein veränderbares Bild im Speicher ist. Dies ist in der Core-Bibliothek mit some native code (vermutlich für Geschwindigkeit) implementiert.

Da jedoch nativer Code im Browser offensichtlich nicht möglich ist, hat Pixmapa different implementation in the GWT backend. Der markante Teil gibt den Konstruktor ist:

public Pixmap (FileHandle file) { 
    GwtFileHandle gwtFile = (GwtFileHandle)file; 
    ImageElement img = gwtFile.preloader.images.get(file.path()); 
    if (img == null) throw new GdxRuntimeException("Couldn't load image '" + file.path() + "', file does not exist"); 
    create(img.getWidth(), img.getHeight(), Format.RGBA8888); 
    context.setGlobalCompositeOperation(Composite.COPY); 
    context.drawImage(img, 0, 0); 
    context.setGlobalCompositeOperation(getComposite()); 
} 

Dies schafft ein HTMLCanvasElement und CanvasRenderingContext2D, zieht dann das Bild auf die Leinwand. Dies ist im libGDX-Kontext sinnvoll, da ein Pixmap veränderbar sein soll, aber ein HTML-Bild schreibgeschützt ist.

Ich bin nicht genau sicher, wie die Pixel schließlich wieder für den Upload auf die OpenGL-Textur abgerufen werden, aber zu diesem Zeitpunkt sind wir bereits verloren. Da Notiz dieser Warnung im canvas2d spec:

Hinweis: Aufgrund der verlustbehafteten Natur zu und von integrierten Alphafarbwerten umzuwandeln, Pixel, die gerade eingestellt wurden putImageData() mit möglicherweise zu einer äquivalenten getImageData() als anderen zurückgeführt wird Werte.

Um den Effekt zu zeigen, habe ich eine JSFiddle erstellt: https://jsfiddle.net/gg9tbejf/ Diese verwenden nicht Libgdx, nur rohe Leinwand, JavaScript und WebGL, aber man kann sehen, dass das Bild nach einer Rundreise durch Canvas2D verstümmelt.

Anscheinend speichern die meisten (alle?) Großen Browser ihre canvas2d-Daten mit Alpha-Vormultiplex, so dass eine verlustfreie Wiederherstellung unmöglich ist. This SO question zeigt ziemlich eindeutig, dass es derzeit keinen Weg gibt.


Edit: Ich schrieb eine Abhilfe in meinem lokalen Projekt ohne Änderung Libgdx selbst. Erstellen ImageTextureData.java in Ihrem GWT-Projekt (Paketname Angelegenheiten, es greift auf Paket-private Felder):

package com.badlogic.gdx.backends.gwt; 

import com.badlogic.gdx.Gdx; 
import com.badlogic.gdx.graphics.GL20; 
import com.badlogic.gdx.graphics.Pixmap; 
import com.badlogic.gdx.graphics.TextureData; 
import com.badlogic.gdx.utils.GdxRuntimeException; 
import com.google.gwt.dom.client.ImageElement; 
import com.google.gwt.webgl.client.WebGLRenderingContext; 

public class ImageTextureData implements TextureData { 

    private final ImageElement imageElement; 
    private final Pixmap.Format format; 
    private final boolean useMipMaps; 

    public ImageTextureData(ImageElement imageElement, Pixmap.Format format, boolean useMipMaps) { 
     this.imageElement = imageElement; 
     this.format = format; 
     this.useMipMaps = useMipMaps; 
    } 

    @Override 
    public TextureDataType getType() { 
     return TextureDataType.Custom; 
    } 

    @Override 
    public boolean isPrepared() { 
     return true; 
    } 

    @Override 
    public void prepare() { 
    } 

    @Override 
    public Pixmap consumePixmap() { 
     throw new GdxRuntimeException("This TextureData implementation does not use a Pixmap"); 
    } 

    @Override 
    public boolean disposePixmap() { 
     throw new GdxRuntimeException("This TextureData implementation does not use a Pixmap"); 
    } 

    @Override 
    public void consumeCustomData(int target) { 
     WebGLRenderingContext gl = ((GwtGL20) Gdx.gl20).gl; 
     gl.texImage2D(target, 0, GL20.GL_RGBA, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, imageElement); 
     if (useMipMaps) { 
      gl.generateMipmap(target); 
     } 
    } 

    @Override 
    public int getWidth() { 
     return imageElement.getWidth(); 
    } 

    @Override 
    public int getHeight() { 
     return imageElement.getHeight(); 
    } 

    @Override 
    public Pixmap.Format getFormat() { 
     return format; 
    } 

    @Override 
    public boolean useMipMaps() { 
     return useMipMaps; 
    } 

    @Override 
    public boolean isManaged() { 
     return false; 
    } 
} 

Dann fügen Sie GwtTextureLoader.java überall in Ihrem GWT Projekt:

package com.example.mygame.gwt; 

import com.badlogic.gdx.assets.AssetDescriptor; 
import com.badlogic.gdx.assets.AssetManager; 
import com.badlogic.gdx.assets.loaders.AsynchronousAssetLoader; 
import com.badlogic.gdx.assets.loaders.FileHandleResolver; 
import com.badlogic.gdx.assets.loaders.TextureLoader; 
import com.badlogic.gdx.backends.gwt.GwtFileHandle; 
import com.badlogic.gdx.backends.gwt.ImageTextureData; 
import com.badlogic.gdx.files.FileHandle; 
import com.badlogic.gdx.graphics.Pixmap; 
import com.badlogic.gdx.graphics.Texture; 
import com.badlogic.gdx.graphics.TextureData; 
import com.badlogic.gdx.utils.Array; 
import com.google.gwt.dom.client.ImageElement; 

public class GwtTextureLoader extends AsynchronousAssetLoader<Texture, TextureLoader.TextureParameter> { 
    TextureData data; 
    Texture texture; 

    public GwtTextureLoader(FileHandleResolver resolver) { 
     super(resolver); 
    } 

    @Override 
    public void loadAsync(AssetManager manager, String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) { 
     if (parameter == null || parameter.textureData == null) { 
      Pixmap.Format format = null; 
      boolean genMipMaps = false; 
      texture = null; 

      if (parameter != null) { 
       format = parameter.format; 
       genMipMaps = parameter.genMipMaps; 
       texture = parameter.texture; 
      } 

      // Mostly these few lines changed w.r.t. TextureLoader: 
      GwtFileHandle gwtFileHandle = (GwtFileHandle) fileHandle; 
      ImageElement imageElement = gwtFileHandle.preloader.images.get(fileHandle.path()); 
      data = new ImageTextureData(imageElement, format, genMipMaps); 
     } else { 
      data = parameter.textureData; 
      if (!data.isPrepared()) data.prepare(); 
      texture = parameter.texture; 
     } 
    } 

    @Override 
    public Texture loadSync(AssetManager manager, String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) { 
     Texture texture = this.texture; 
     if (texture != null) { 
      texture.load(data); 
     } else { 
      texture = new Texture(data); 
     } 
     if (parameter != null) { 
      texture.setFilter(parameter.minFilter, parameter.magFilter); 
      texture.setWrap(parameter.wrapU, parameter.wrapV); 
     } 
     return texture; 
    } 

    @Override 
    public Array<AssetDescriptor> getDependencies(String fileName, FileHandle fileHandle, TextureLoader.TextureParameter parameter) { 
     return null; 
    } 
} 

dann, dass Loader mit auf AssetManager in nur Ihre GWT Projekt:

assetManager.setLoader(Texture.class, new GwtTextureLoader(assetManager.getFileHandleResolver())); 

Hinweis: Sie haben t o Stellen Sie sicher, dass Ihre Bilder zu Beginn mit der Zweierpotenz versehen sind. Dieser Ansatz kann offensichtlich keine Conversions für Sie vornehmen. Mipmapping- und Texturfilterungsoptionen sollten jedoch unterstützt werden.

Es wäre schön, wenn libGDX die Verwendung von canvas2d im üblichen Fall des Ladens eines Bildes beenden würde und das Bildelement einfach direkt an texImage2D übergeben würde. Ich bin mir nicht sicher, wie ich das architektonisch anpassen soll (und ich bin ein GWT-Noob zum Booten). Da die original GitHub issue geschlossen ist, habe ich a new one mit der vorgeschlagenen Lösung eingereicht.

Update: Das Problem wurde in this commit behoben, die in libGDX 1.9.4 und höher enthalten ist.

+0

Danke für die Untersuchung. Sie müssen ein neues GitHub-Problem erstellen, da das alte Problem nicht erneut geöffnet werden kann. Erstellen Sie ein neues und verknüpfen Sie es mit dem alten Problem und dieser Antwort. –

+0

Code für eine Problemumgehung hinzugefügt, bei der libGDX nicht geändert wird. – Thomas

+0

Abgelegt in https://github.com/libgdx/libgdx/issues/3782. – Thomas

4

WebGL behandelt Alphas etwas anders als Standard-OpenGL und kann oft Probleme verursachen.

This site erklärt die Unterschiede recht gut.

Der größte Unterschied zwischen OpenGL und WebGL ist, dass OpenGL auf einen Backbuffer macht das mit etwas Compositing ist nicht so, oder mit etwas effektiv nicht durch den Window-Manager OS Compositing, so spielt es keine Rolle, was Dein Alpha ist.

WebGL wird vom Browser mit der Webseite und der Standard Compositing vorge multiplizierten alpha die gleiche wie .png Schlüssel Transparenz und 2d Leinwand-Tags zu verwenden. *

auch, dass Website gibt Workarounds für typische Probleme von Menschen. Es ist ein wenig beteiligt, sollte aber Ihre Probleme lösen.

Ich werde hier nicht den ganzen Artikel einfügen, aber ich vermute, dass Sie am besten mit nicht-vor-multipliziert bleiben und sicherstellen, dass Sie den Alpha-Kanal nach jedem Rendern löschen. Die Seite geht viel detaillierter.

+0

Ich stieß darauf, wenn Sie herausfinden wollten, wie Sie dieses Problem lösen können. Ich konnte in diesem Artikel nichts finden, was darauf hindeutet, dass ich den Effekt sehen könnte, den ich hier sehe. –

+0

Ich denke, das Prinzip ist, dass, wenn Sie mit einem nicht 1.0 Alpha-Wert im Framebuffer enden, dann die Farbe hinter dem WebGL Canvas durch, wenn der Browser es zusammensetzt. Wenn Sie den Alpha-Wert zwingend auf 1.0 setzen, stoppt das ein Problem (indem Sie entweder kein Alpha haben, es löschen oder es maskieren). – solidpixel

+0

@ Isogen74 Ich habe alle Empfehlungen auf der Seite Phil ausprobiert. Keiner von ihnen scheint einen Unterschied zu machen. –

1

Ich frage mich, ob Sie irgendwo ein Präzisionsproblem lösen - die Alpha-Texturen mit Vormultiplex machen die Farbkanäle dunkler als das Original.

Konzeptionell werden durch diesen Vorgang Farbwerte in das untere Ende des Farbbereichs gequetscht, was bei der Neucodierung als 8-Bit-Textur eine Quantisierung verursachen kann. Was ich nicht erklären kann ist, warum man Schwingungen zwischen Hell und Dunkel bekommt, es sei denn, dies ist eine Wechselwirkung verschiedener Bandschritte in den Farb- und Alphakanälen.

OpenGL und OpenGL ES 3.0 unterstützen sRGB-Texturen, die dabei helfen könnten (effektiv sind sie viel besser in der Lage, Farbunterschiede am dunklen Ende des Spektrums auszudrücken, hohe Luminanzwerte opfernd, wo das Auge weniger disistiert) . Leider ist dies auf OpenGL ES 2.0-Mobilgeräten nicht weit verbreitet (und daher nicht auf WebGL, da WEbGL auf ES 2.0 basiert, obwohl die sRGB-Erweiterung auf einigen Geräten verfügbar sein könnte).

+0

Ich frage mich, ob es etwas in dieser Richtung ist. Ich verstehe nicht, warum es nur im Webgl auftreten würde. –

+0

Einverstanden - das verwirrte mich wirklich ... es sollte nur die Aufrufe von nativem OpenGL ES durchbohren, mit Ausnahme der Standardeinstellungen für das Framebuffer-Format, die bereits erwähnt wurden. – solidpixel

+0

Ich habe gerade meine Frage mit einigen der heutigen Forschung aktualisiert. Der LibGDX-Shader enthält den folgenden '#ifdef GL_ES Präzisions-Mittelschwimmer; # endif' Ich habe versucht, das zu highp zu ändern, um zu sehen, ob es einen Effekt durch keine Änderung hat. Könnte es etwas damit zu tun haben? –

Verwandte Themen