使用预乘alpha时,AlphaGL渐变在WebGL中不平滑

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)

然后我得到了条带.这指向了一个精确的问题我相信,因为色彩通道已被预乘法过程挤入光谱的较暗端.

Phi*_*son 5

WebGL对alphas的处理方式与标准OpenGL略有不同,并且经常会导致问题.

该网站很好解释了这些差异.

OpenGL和WebGL之间最大的区别在于OpenGL渲染到一个没有与任何东西合成的后备缓冲区,或者实际上没有被操作系统的窗口管理器与任何东西合成,所以你的alpha是什么并不重要.

WebGL由浏览器与网页合成,默认情况下使用预乘的alpha与.png标签相同,透明度和2d画布标签.*

该网站还为人们面临的典型问题提供了解决方法.这有点牵扯,但应该解决你的问题.

我不会在这里粘贴整篇文章,但我怀疑你最好坚持使用非预乘,并确保在每次渲染后清除alpha通道.该网站更详细.


Tho*_*mas 4

我花了一天中最好的时间来研究这个问题,因为我也看到了这个确切的问题。我想我终于弄清楚了。

这是由 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 及更高版本中。