Alpha Rendering Difference Between Opengl and Webgl

Alpha rendering difference between OpenGL and WebGL

Did you set the canvas to be non-premultilied?

gl = someCanvas.getContext("webgl", { premultipliedAlpha: false });

The default for WebGL is true. The default for most OpenGL apps is false

On top of that WebGL is composited with the rest of the page. At a minimum that's the background color of the canvas or whatever it's inside (the body of your document).

To see if this is the problem try setting your canvas's background color to purple or something that will stick out

<canvas ... style="background-color: #F0F;"></canvas>

or in css

canvas { background-color: #F0F; }

OpenGL apps are rarely composited over anything where as WebGL apps effectively are ALWAYS composited.

Some solutions

  • Turn off alpha

    If you don't need alpha in your destination you can turn it off

    gl = someCanvas.getContext("webgl", { alpha: false });

    Now the alpha will effectively be 1

  • Set the alpha to 1 at the end of a frame

    // clear only the alpha channel to 1
    gl.clearColor(1, 1, 1, 1);
    gl.colorMask(false, false, false, true);
    gl.clear(gl.COLOR_BUFFER_BIT);

    don't forget to set the color mask back to all true if you need
    to clear the color buffer later

  • Set the canvas's background color to black

    canvas { background-color: #000; }

If possible I'd pick turning off alpha. The reason if is alpha is set to off it's possible the browser can turn off blending when drawing the canvas into the browser. That could be a 10-20% or more increase in speed depending on the GPU. There's no guarantee that any browser makes that optimization, only that it's possible to does whereas with the other 2 solutions it's not possible or at least far less likely

Strange behavior of alpha without blending in WebGL

This is basically already answered here

Alpha rendering difference between OpenGL and WebGL

What you're seeing is that WebGL canvases are, by default, blended with the background. Either the background color of the canvas or whatever it's a child of. The default background color for HTML is white so if you draw with [0.0, 0.0, 0.0, 0.5] that's 50% alpha black blended with the white webpage.

See the link above for how to fix it.

webgl colors and alpha

The result you get is because the browser blends in the background color behind the canvas. Try setting the background-color, and you get something like this:

The right

The blending equation by default is:

blendedColor = sourceColor + destinationColor * (1 - sourceAlpha)

So with a white background, sourceColor = c = 0.5, sourceAlpha = a = 0.7, destinationColor = white = 1.0, so blendedColor = 0.8

In this context dividing by alpha doesn't make much sense. To match the middle region, you could replicate the blending process above:

else gl_FragColor = vec4(vec3(c) + vec3(1.0) * (1.0 - a), 1.0); // middle color

Text Rendering Problem with Distance Field in WebGL

This appears to be a problem related to Premultiplied Alpha (see Why does my WebGL alpha-transparency look wrong?.

You need to multiply the color channels by the alpha channel in the fragment shader:

void main() {
// [...]

vec4 color = ...;

gl_FragColor = vec4(color.rgb * color.a, color.a);
}

and change the blend function to 1 * source_color + (1 -alpha) * dest_color:

gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

Alpha gradients not smooth in WebGL when using premultiplied alpha

I spent the best part of the day looking into this, because I'm also seeing this exact issue. I think I finally got to the bottom of it.

This is caused by the way libGDX loads images. A texture is created from a Pixmap on all platforms, where a Pixmap is basically an in-memory mutable image. This is implemented in the core library with some native code (presumably for speed).

However, since native code is obviously impossible in the browser, Pixmap has a different implementation in the GWT backend. The salient part there is the 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 does not exist");
create(img.getWidth(), img.getHeight(), Format.RGBA8888);
context.setGlobalCompositeOperation(Composite.COPY);
context.drawImage(img, 0, 0);
context.setGlobalCompositeOperation(getComposite());
}

This creates a HTMLCanvasElement and a CanvasRenderingContext2D, then draws the image to the canvas. This makes sense in the libGDX context, since a Pixmap is supposed to be mutable, but an HTML image is read-only.

I'm not exactly sure how the pixels are eventually retrieved again for upload to the OpenGL texture, but by this point we're doomed already. Because note this warning in the canvas2d spec:

Note: Due to the lossy nature of converting to and from premultiplied alpha color values, pixels that have just been set using putImageData() might be returned to an equivalent getImageData() as different values.

To show the effect, I created a JSFiddle: https://jsfiddle.net/gg9tbejf/ This doesn't use libGDX, just raw canvas, JavaScript and WebGL, but you can see that the image is mutilated after a round-trip through canvas2d.

Apparently most (all?) major browsers store their canvas2d data with premultiplied alpha, so lossless recovery is impossible. This SO question shows fairly conclusively that there is currently no way around that.


Edit: I wrote a workaround in my local project without modifying libGDX itself. Create ImageTextureData.java in your GWT project (package name matters; it 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 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;
}
}

Then add GwtTextureLoader.java anywhere in your 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;
}

// 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;
}
}

Then set that loader on your AssetManager in your GWT project only:

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

Note: You have to ensure that your images are power of two to begin with; this approach can obviously do no conversions for you. Mipmapping and texture filtering options should be supported though.

It would be nice if libGDX would to stop using canvas2d in the common case of just loading an image, and just pass the image element to texImage2D directly. I'm not sure how to fit that in architecturally (and I'm a GWT noob to boot). Since the original GitHub issue is closed, I've filed a new one with the suggested solution.

Update: the issue was fixed in this commit, which is included in libGDX 1.9.4 and above.

Do WebGL and OpenGL use the same shading language?

WebGL itself is more closely related to GLES2.0 than to desktop GL and thus uses the same shading language as ES2: The OpenGL ES Shading Language 1.0. This is somewhat similiar to the early desktop GLSL versions (like GLSL1.10 or 1.20 which were current at the time of GL2.x), but more limited. So buying a book about desktop GLSL might not be the best choice, you might want to look specifically for GLES ones.

Webgl Blending is blending to white

It seems there was potentially some bad undefined behavior in my library:

I was grabbing the gl context twice: canvas.getContext(...) and each one had the potential to have different properties for things like premultiplied alpha and alpha setting attributes for the context.

When I fixed that issue this bizarre blending error went away. So I will assume the premultiply alpha property between the two context grabs was somehow inconsistent.

WebGL alpha blending with depth

The normal way to do this is you have to split those 2 faces into 4 faces, sort them by depth and draw them furthest to nearest.

There's another technique, depth peeling but it's also fairly complicated.

There's also order independent transparency.

Both of these last techniques are probably better suited for WebGL2



Related Topics



Leave a reply



Submit