i've got libgdx game cartoon clouds smooth gradient. there other examples of gradients in game have similar issue, clouds obvious example. fine in android, on ios , on desktop version of game, on webgl version gradients not drawn smooth. appears alpha gradients have problem. other gradients ok.
i've tried on 3 different devices in chrome , ie, , 3 produce same results. can find test of html5 version here.
https://wordbuzzhtml5.appspot.com/canvas/
i've added example intellij project on github here
https://github.com/willcalderwood/cloudtest
if have intellij, clone project, open build.gradle file, press alt-f12, type gradlew html:superdev , browse http://localhost:8080/html/
the critical code render() here
the bottom image here desktop version, top webgl version, both running on same hardware.

there's nothing clever going on drawing. it's call to
spritebatch.draw(texture, getleft(), getbottom(), getwidth(), getheight()); i'm using default shader, textures packed premultiplied alpha blend function set as
spritebatch.setblendfunction(gl20.gl_one, gl20.gl_one_minus_src_alpha); this actual image, although alpha not premultiplied that's done packer.

does know possible reason , how might resolve it?
update
this appears happen when using blending mode gl20.gl_one, gl20.gl_one_minus_src_alpha
another update
i've tried changing whole game use non-premultiplied alpha textures. use texture packer can fix halo issues occur non-premultiplied alpha. works fine in android , desktop version. in webgl version, while smooth gradients, still small halo effect, can't use solution either.
and update
here's new image. desktop version on top, web version on bottom. blending mode gl20.gl_one, gl20.gl_one_minus_src_alpha on left , gl20.gl_src_alpha, gl20.gl_one_minus_src_alpha on right
here's zoomed version of bottom left image above increased contrast show issue.
i've done lot of playing fragment shader try , work out what's happening. if set
gl_fragcolor = vec4(c.a, c.a, c.a, 1.0); then gradient smooth, if set
gl_fragcolor = vec4(c.r, c.r, c.r, 1.0); then banding. points towards precision issue believe colour channels have been squeezed darker end of spectrum pre-multiplication process.
i spent best part of day looking this, because i'm seeing exact issue. think got bottom of it.
this caused way libgdx loads images. texture created pixmap on platforms, pixmap in-memory mutable image. implemented in core library some native code (presumably speed).
however, since native code impossible in browser, pixmap has a different implementation in gwt backend. salient part there constructor:
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 not exist"); create(img.getwidth(), img.getheight(), format.rgba8888); context.setglobalcompositeoperation(composite.copy); context.drawimage(img, 0, 0); context.setglobalcompositeoperation(getcomposite()); } this creates htmlcanvaselement , canvasrenderingcontext2d, draws image canvas. makes sense in libgdx context, since pixmap supposed mutable, html image read-only.
i'm not sure how pixels retrieved again upload opengl texture, point we're doomed already. because note warning in canvas2d spec:
note: due lossy nature of converting , premultiplied alpha color values, pixels have been set using
putimagedata()might returned equivalentgetimagedata()different values.
to show effect, created jsfiddle: https://jsfiddle.net/gg9tbejf/ doesn't use libgdx, raw canvas, javascript , webgl, can see image mutilated after round-trip through canvas2d.
apparently (all?) major browsers store canvas2d data premultiplied alpha, lossless recovery impossible. this question shows conclusively there no way around that.
edit: wrote workaround in local project without modifying libgdx itself. create imagetexturedata.java in gwt project (package name matters; accesses package-private fields):
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 not use pixmap"); } @override public boolean disposepixmap() { throw new gdxruntimeexception("this texturedata implementation not use 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; } } then add gwttextureloader.java anywhere in gwt project:
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; } // 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; } } then set loader on assetmanager in gwt project only:
assetmanager.setloader(texture.class, new gwttextureloader(assetmanager.getfilehandleresolver())); note: have ensure images power of 2 begin with; approach can no conversions you. mipmapping , texture filtering options should supported though.
it nice if libgdx stop using canvas2d in common case of loading image, , pass image element teximage2d directly. i'm not sure how fit in architecturally (and i'm gwt noob boot). since original github issue closed, i've filed a new one suggested solution.
update: issue fixed in this commit, included in libgdx 1.9.4 , above.


Comments
Post a Comment