Wil*_*ood 7 html5 blending opengl-es webgl libgdx
我有一个带有平滑渐变的卡通云的LibGDX游戏.游戏中还有其他具有类似问题的渐变示例,但云是最明显的例子.它们在Android,iOS和游戏的桌面版本上都很好看,但在WebGL版本中,渐变并不是那么平滑.它似乎只是alpha梯度有问题.其他渐变看起来不错.
我在Chrome和IE中尝试了3种不同的设备,所有3种设备都产生相同的结果.您可以在此处找到HTML5版本的测试.
https://wordbuzzhtml5.appspot.com/canvas/
我在github上添加了一个示例IntelliJ项目
https://github.com/WillCalderwood/CloudTest
如果你有intelliJ,克隆该项目,打开build.gradle文件,按Alt-F12,键入gradlew html:superdev
然后浏览到http:// localhost:8080/html /
关键代码在render()
这里
这里的底部图像是桌面版本,顶部是WebGL版本,两者都在相同的硬件上运行.
绘图没有什么巧妙的.这只是一个电话
spriteBatch.draw(texture, getLeft(), getBottom(), getWidth(), getHeight());
Run Code Online (Sandbox Code Playgroud)
我正在使用默认着色器,纹理用预乘alpha填充,混合函数设置为
spriteBatch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA);
Run Code Online (Sandbox Code Playgroud)
这是实际的图像,虽然alpha不是像我的包装工那样预乘的.
有谁知道这个的可能原因以及我如何解决它?
更新
仅在使用混合模式时才会出现这种情况 GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA
另一个更新
我已经尝试改变整个游戏以使用非预乘的alpha纹理.我使用Texture Packer可以帮助修复非预乘alpha经常出现的晕圈问题.所有这些在Android和桌面版本中都能正常工作.在WebGL版本中,虽然我得到了平滑的渐变,但我仍然得到一个小的光晕效果,所以我不能用它作为解决方案.
另一个更新
这是一张新图片.桌面版本位于顶部,网页版本位于底部.GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA
左侧和GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA
右侧的混合模式
这是上面左下角图像的缩放版本,增加对比度以显示问题.
我还做了大量的片段着色器,试图找出正在发生的事情.如果我订
gl_FragColor = vec4(c.a, c.a, c.a, 1.0);
Run Code Online (Sandbox Code Playgroud)
然后渐变是平滑的,但如果我设置
gl_FragColor = vec4(c.r, c.r, c.r, 1.0);
Run Code Online (Sandbox Code Playgroud)
然后我得到了条带.这指向了一个精确的问题我相信,因为色彩通道已被预乘法过程挤入光谱的较暗端.
WebGL对alphas的处理方式与标准OpenGL略有不同,并且经常会导致问题.
OpenGL和WebGL之间最大的区别在于OpenGL渲染到一个没有与任何东西合成的后备缓冲区,或者实际上没有被操作系统的窗口管理器与任何东西合成,所以你的alpha是什么并不重要.
WebGL由浏览器与网页合成,默认情况下使用预乘的alpha与.png标签相同,透明度和2d画布标签.*
该网站还为人们面临的典型问题提供了解决方法.这有点牵扯,但应该解决你的问题.
我不会在这里粘贴整篇文章,但我怀疑你最好坚持使用非预乘,并确保在每次渲染后清除alpha通道.该网站更详细.
我花了一天中最好的时间来研究这个问题,因为我也看到了这个确切的问题。我想我终于弄清楚了。
这是由 libGDX 加载图像的方式引起的。Pixmap
在所有平台上,纹理都是从 a 创建的,其中 aPixmap
基本上是内存中的可变图像。这是在核心库中使用一些本机代码实现的(大概是为了速度)。
然而,由于本机代码在浏览器中显然是不可能的,因此在 GWT 后端Pixmap
有不同的实现。最重要的部分是构造函数:
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());
}
Run Code Online (Sandbox Code Playgroud)
这将创建 aHTMLCanvasElement
和 a CanvasRenderingContext2D
,然后将图像绘制到画布上。这在 libGDX 上下文中是有意义的,因为 aPixmap
应该是可变的,但 HTML 图像是只读的。
我不太确定最终如何再次检索像素以上传到 OpenGL 纹理,但此时我们已经注定了。因为请注意canvas2d 规范中的此警告:
注意:由于与预乘 Alpha 颜色值之间的转换存在有损性质,刚刚设置的像素
putImageData()
可能会返回为getImageData()
不同值的等效值。
为了展示效果,我创建了一个 JSFiddle: https: //jsfiddle.net/gg9tbejf/这不使用 libGDX,仅使用原始画布、JavaScript 和 WebGL,但是您可以看到图像在往返之后被毁坏了canvas2d。
显然,大多数(所有?)主要浏览器都使用预乘 alpha 存储其 canvas2d 数据,因此无损恢复是不可能的。这个问题相当明确地表明,目前没有办法解决这个问题。
编辑:我在本地项目中编写了一个解决方法,而没有修改 libGDX 本身。在您的 GWT 项目中创建ImageTextureData.java
(包名称很重要;它访问包私有字段):
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;
}
}
Run Code Online (Sandbox Code Playgroud)
GwtTextureLoader.java
然后在 GWT 项目中的任意位置添加:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
AssetManager
然后仅在 GWT 项目中设置该加载程序:
assetManager.setLoader(Texture.class, new GwtTextureLoader(assetManager.getFileHandleResolver()));
Run Code Online (Sandbox Code Playgroud)
注意:首先你必须确保你的图像是2的幂;这种方法显然不能为你做任何转换。不过,应该支持纹理贴图和纹理过滤选项。
如果 libGDX 在仅加载图像的常见情况下停止使用 canvas2d 并直接传递图像元素,那就太好了texImage2D
。我不知道如何在架构上适应它(我是一个 GWT 菜鸟)。由于最初的 GitHub 问题已关闭,我已提交了一个包含建议解决方案的新问题。
更新:该问题已在此提交中修复,该提交包含在 libGDX 1.9.4 及更高版本中。