2015-07-17 6 views
7

У меня есть игра LibGDX с мультяшными облаками с плавным градиентом. Существуют и другие примеры градиентов в игре, которые имеют аналогичную проблему, но облака являются наиболее очевидным примером. Они отлично смотрятся в Android, на iOS и на настольной версии игры, но в версии WebGL градиенты не выглядят гладкими. Это похоже только на альфа-градиенты, которые имеют проблему. Другие градиенты выглядят нормально.Альфа-градиенты не гладкие в WebGL при использовании премультиплексной альфы

Я пробовал на трех разных устройствах в Chrome и IE, и все 3 дают одинаковые результаты. Здесь вы можете найти тест версии HTML5.

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

Я добавил пример IntelliJ проект на GitHub здесь

https://github.com/WillCalderwood/CloudTest

Если у вас есть IntelliJ, клонировать этот проект, откройте файл build.gradle, нажмите Alt-F12, типа gradlew html:superdev, а затем перейти к http://localhost:8080/html/

критический код render()here

Нижнее изображение здесь - настольная версия, верхняя часть - версия WebGL, работающая на одном и том же оборудовании.

enter image description here

Там нет ничего умна происходит с чертежом. Это просто призыв к

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

Я использую шейдер по умолчанию, текстуры упакован с предварительно умноженным альфа с функцией смеси, установленной в

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

Это реальное изображение, хотя альфа не умножается, как это сделанный моим упаковщиком.

enter image description here

Кто-нибудь знает возможную причину этого и как я могу решить это?

Update

Это только кажется происходит при использовании режима смешивания GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA

Другой Update

Я попытался изменить всю игру, чтобы использовать не альфа текстуры предварительно умноженные. Я использую Texture Packer, который может помочь устранить проблемы с ореолом, которые часто возникают с непремультиплексной альфа. Все это прекрасно работает в Android и Desktop версии. В версии WebGL, когда я получаю плавные градиенты, я все равно получаю небольшой эффект ореола, поэтому я не могу использовать это как решение.

И еще одно обновление

Вот новый образ. Настольная версия сверху, веб-версия внизу.режим GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA слева и GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA Blending на правой

enter image description here

Вот увеличенная версия левого нижнего изображения выше, с повышенным контрастом, чтобы показать проблему.

enter image description here

Я также сделал много играл с пиксельным шейдером, чтобы попытаться выяснить, что происходит. Если я устанавливаю

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

то градиент является гладким, но если я устанавливаю

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

Тогда я получаю полосатость. Это указывает на точную проблему, которую я считаю, поскольку цветовые каналы были сжаты в более темный конец спектра посредством процесса предварительного умножения.

+0

Какой формат текстуры вы используете и каков ваш формат фреймбуфера? RGBA8 несжатый? – solidpixel

+0

@ Isogen74 Да, RGBA8 несжатый. –

+0

вы уверены, что цель альфа-1 полностью? webgl будет совмещаться со страничным фоном в качестве предварительной альфа-версии. Чтобы быть уверенным, просто очистить альфа до 1 до blit. – starmole

ответ

3

Я провел большую часть дня, изучая это, потому что я также вижу эту точную проблему. Думаю, я, наконец, добрался до сути.

Это связано с тем, как libGDX загружает изображения. Текстура создается из Pixmap на всех платформах, где Pixmap в основном является изменяемым в памяти образом. Это реализовано в основной библиотеке с some native code (предположительно для скорости).

Однако, так как в браузере явно невозможен код, Pixmap имеет a different implementation in the GWT backend. Выступ части есть конструктор:

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()); 
} 

Это создает HTMLCanvasElement и CanvasRenderingContext2D, затем рисует изображение на холст. Это имеет смысл в контексте libGDX, так как Pixmap предполагается изменчивым, но HTML-образ доступен только для чтения.

Я не совсем уверен, как пиксели в конечном итоге будут восстановлены для загрузки в текстуру OpenGL, но к этому моменту мы обречены уже. Поскольку записка этого предупреждения в canvas2d spec:

Примечания: В связи с потерями характера преобразования и из значений цвета предварительно умноженного альфа, пикселей, которые только что были установлены с помощью putImageData() может быть возвращены к эквивалентным getImageData() как разным значения.

Чтобы показать эффект, я создал JSFiddle: https://jsfiddle.net/gg9tbejf/ Это не использует libGDX, только сырой холст, JavaScript и WebGL, но вы можете увидеть, что изображение испорчено после редиректа через canvas2d.

По-видимому, большинство (всех?) Основных браузеров хранят свои данные canvas2d с предварительно умноженной альфа, поэтому восстановление без потерь невозможно. This SO question показывает довольно убедительно, что в настоящее время нет никакого способа обойти это.


Редактировать: Я написал обходной путь в своем локальном проекте без изменения самого libGDX. Создание ImageTextureData.java в проекте GWT (вопросы, имя пакета, он получает доступ пакет-частного поля):

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

Затем добавьте 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; 
    } 
} 

Затем установить, что загрузчик на вашем AssetManager в ваш проект GWT только:

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

Примечание: у вас есть т o убедитесь, что ваши изображения имеют силу два для начала; этот подход, очевидно, не делает конверсий для вас. Тем не менее, следует поддерживать параметры фильтрации Mipmapping и текстуры.

Было бы неплохо, если бы libGDX прекратил использование canvas2d в общем случае только для загрузки изображения и просто передал элемент изображения в texImage2D напрямую. Я не знаю, как это сделать в архитектуре (и я загружаю GWT noob). Поскольку original GitHub issue закрыт, я подал a new one с предлагаемым решением.

Обновление: вопрос был исправлен в this commit, который включен в libGDX 1.9.4 и выше.

+0

Спасибо, что посмотрели на это. Вам нужно будет создать новую проблему github, поскольку старую проблему нельзя возобновить. Создайте новый и вернитесь к старой проблеме и этому ответу. –

+0

Добавлен код для обходного пути, который не требует модификации libGDX. – Thomas

+0

Подано https://github.com/libgdx/libgdx/issues/3782. – Thomas

4

WebGL рассматривает альфа немного иначе, чем стандартный OpenGL, и часто может вызывать проблемы.

This site объясняет различия довольно хорошо.

Самая большая разницей между OpenGL и WebGL является то, что OpenGL делает к BackBuffer, не скомпонованные с чем так или эффективно не скомпонованным ни с чем по оконному менеджеру операционной системы, в поэтому не имеет значения, что ваша альфа.

WebGL является скомпонованы браузером с веб-страницы, и по умолчанию является использование предварительно умноженную альфа так же, как .png теги с прозрачности и 2d тегов холста. *

Этот сайт также дает обходные пути для типичных проблем, с которыми сталкиваются люди. Это немного связано, но нужно разобраться в ваших проблемах.

Я не собираюсь вставлять всю статью здесь, но я подозреваю, что вы лучше придерживаетесь неперемноженного и убедитесь, что вы очищаете альфа-канал после каждого рендеринга. Сайт идет гораздо подробнее.

+0

Я столкнулся с этим, пытаясь решить, как решить эту проблему. Я не мог найти что-либо в этой статье, которая могла бы предложить мне увидеть эффект, который я вижу здесь. –

+0

Я думаю, что принцип заключается в том, что если вы закончите с альфа-значением не 1.0 в буфере кадров, тогда цвет за холстом WebGL проявится, когда браузер его соберет. Если вы принудительно настроили alpha на 1.0, это перестает быть проблемой (либо не имея альфы, не очищая ее, либо не создавая маскировку). – solidpixel

+0

@ Isogen74 Я пробовал все рекомендации на странице Фила. Ни один из них, похоже, не имеет никакого значения. –

1

Интересно, если вы сталкиваетесь с проблемой точности где-то - предварительно умноженные альфа-текстуры делают цветные каналы темнее оригинала.

Концептуально этот процесс выдавливает значения цвета в нижний край диапазона цветов, что может привести к появлению квантования при повторном кодировании в виде 8-битной текстуры. То, что я не могу объяснить, - это то, почему вы получаете колебания между светом и тьмой, если это не взаимодействие разных полос частот в цветовых и альфа-каналах.

OpenGL и OpenGL ES 3.0 поддерживают текстуры sRGB, которые могли бы помочь в этом (они значительно способны выражать цветовые различия в темном конце спектра, жертвуя высокими значениями яркости, когда глаз менее способен их разграничить) , К сожалению, это не широко доступно на мобильных устройствах OpenGL ES 2.0 (и, следовательно, не на WebGL, поскольку WEbGL основан на ES 2.0, хотя расширение sRGB может быть доступно на некоторых устройствах).

+0

Мне интересно, это что-то в этом роде. Я не понимаю, почему это происходит только в webgl. –

+0

Согласовано - это меня сбивает с толку ... это должно быть просто трубкой, хотя вызовы на native OpenGL ES, за исключением изменений по умолчанию в формате framebuffer, обсуждавшихся ранее. – solidpixel

+0

Я только что обновил свой вопрос с некоторыми из сегодняшних исследований. Шейдер LibGDX содержит следующий префикс #ifdef GL_ES ; # endif' Я попытался изменить это на highp, чтобы увидеть, имеет ли он эффект без изменений. Может быть, это связано с этим? –