libgdx open gles 2.0 альфа-маскирование трафарета

Я ищу решение для реализации альфа-маскирования с буфером трафарета в libgdx с открытыми gles 2.0.

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

Проблема заключается в том, что когда я хочу использовать какую-либо маску градиентного изображения или расплывчатую маску png, я не получаю то, что хотел (я получаю «заполненную» прямоугольную маску без альфа-канала), вместо этого я хочу плавную маску затухания.

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

Я недавно начал играть с OpenGL ES, поэтому у меня все еще есть некоторые недоразумения.

Мои вопросы: как настроить буфер трафарета для хранения значений, отличных от 0 и 1, и как использовать эти значения позже для альфа-маскирования?

Tnx заранее.

В настоящее время это моя установка трафарета:

    Gdx.gl.glClearColor(1, 1, 1, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_STENCIL_BUFFER_BIT |   GL20.GL_DEPTH_BUFFER_BIT);

    // setup drawing to stencil buffer
    Gdx.gl20.glEnable(GL20.GL_STENCIL_TEST);
    Gdx.gl20.glStencilFunc(GL20.GL_ALWAYS, 0x1, 0xffffffff);
    Gdx.gl20.glStencilOp(GL20.GL_REPLACE, GL20.GL_REPLACE, GL20.GL_REPLACE);
    Gdx.gl20.glColorMask(false, false, false, false);
    Gdx.gl20.glDepthMask(false);

    spriteBatch.setShader(shaderStencilMask);
    spriteBatch.begin();

    // push to the batch
    spriteBatch.draw(Assets.instance.actor1, Gdx.graphics.getWidth() / 2, Gdx.graphics.getHeight() / 2, Assets.instance.actor1.getRegionWidth(), Assets.instance.actor1.getRegionHeight());

    spriteBatch.end();

    // fix stencil buffer, enable color buffer
    Gdx.gl20.glColorMask(true, true, true, true);
    Gdx.gl20.glDepthMask(true);
    Gdx.gl20.glStencilOp(GL20.GL_KEEP, GL20.GL_KEEP, GL20.GL_KEEP);

    // draw where pattern has NOT been drawn
    Gdx.gl20.glStencilFunc(GL20.GL_EQUAL, 0x1, 0xff);

    decalBatch.add(decal);
    decalBatch.flush();

    Gdx.gl20.glDisable(GL20.GL_STENCIL_TEST);

    decalBatch.add(decal2);
    decalBatch.flush();

person Veljko    schedule 18.07.2014    source источник
comment
Трафарет не допускает частичных альфа-значений. Чтобы получить промежуточные значения, я думаю, вам придется рисовать свои вещи в FrameBuffer, а затем рисовать на экране с мультитекстурированием, используя цветовую текстуру FrameBuffer и текстуру, представляющую альфу. Но может быть более простое решение в зависимости от ваших конкретных потребностей.   -  person Tenfour04    schedule 18.07.2014
comment
Я пытаюсь замаскировать 3D-плоскости, например, одна плоскость представляет собой маску, а другая плоскость находится за плоскостью маски. Я хочу, чтобы за плоскостью стало прозрачным, где текстура маски прозрачна. В настоящее время я использую Decals в Libgdx. Это вообще возможно без падения фпс?   -  person Veljko    schedule 18.07.2014
comment
Под плоскостями вы подразумеваете прямоугольники в трехмерном пространстве? Если два прямоугольника абсолютно одинаковы по размеру, ориентации и положению, вы можете просто сделать это с помощью мультитекстурирования, хотя это потребует некоторой работы с декалями.   -  person Tenfour04    schedule 18.07.2014
comment
Да, плоскости — это в основном прямоугольники в трехмерном пространстве. Маска должна быть статична, а самолет за маской может двигаться. А перекрывающиеся части должны быть замаскированы.   -  person Veljko    schedule 18.07.2014
comment
Я могу думать только о способах, связанных с FrameBuffers. Влияет ли это на FPS, зависит от вашего узкого места.   -  person Tenfour04    schedule 18.07.2014
comment
Я думаю, что рендеринг кадрового буфера в реальном времени и обновление текстуры в 3D-плоскости могут быть узким местом. Что вы думаете?   -  person Veljko    schedule 18.07.2014
comment
Невозможно предсказать, не видя весь ваш проект. Например, если у вас на экране 10000 декалей, то узким местом, скорее всего, будет загрузка ЦП при передаче вершин или перерисовке этих смешанных декалей, а не рисованию FBO.   -  person Tenfour04    schedule 18.07.2014
comment
В порядке. Я попробую так... и посмотрю, что получится. Спс за совет.   -  person Veljko    schedule 18.07.2014


Ответы (1)


Единственные способы, которыми я могу это сделать, - это с помощью FrameBuffer.

Вариант 1

Нарисуйте фон вашей сцены (вещи, которые не будут маскироваться) в FrameBuffer. Затем нарисуйте всю свою сцену без масок на экране. Затем нарисуйте декали маски на экране, используя цветовую вставку FrameBuffer. Недостатком этого метода является то, что в OpenGL ES 2.0 на Android FrameBuffer может иметь RGBA4444, а не RGBA8888, поэтому по краям масок будут видны швы, где изменяется битовая глубина цвета.

Вариант 2

Нарисуйте маскирующие надписи как черно-белые, непрозрачные для вашего FrameBuffer. Затем нарисуйте фон на экране. Когда вы рисуете что-либо, что можно замаскировать, рисуйте это с мультитекстурированием, умножая на цветовую текстуру FrameBuffer. Потенциальным недостатком является то, что абсолютно все, что можно замаскировать, должно быть отрисовано мультитекстурированным с помощью специального шейдера. Но если вы просто используете декали, то это не сложнее, чем вариант 1.

Следующее не тестировалось... может потребоваться небольшая отладка.

В обоих вариантах я бы создал подкласс CameraGroupStrategy для использования с DecalBatch при рисовании декалей маски и переопределил бы beforeGroups, чтобы также установить вторую текстуру.

public class MaskingGroupStrategy extends CameraGroupStrategy{

    private Texture fboTexture;

    //call this before using the DecalBatch for drawing mask decals
    public void setFBOTexture(Texture fboTexture){
        this.fboTexture = fboTexture;
    }

    @Override
    public void beforeGroups () {
        super.beforeGroups();
        fboTexture.bind(1);
        shader.setUniformi("u_fboTexture", 1);
        shader.setUniformf("u_screenDimensions", Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    }
}

И в вашем шейдере вы можете получить цвет текстуры FBO следующим образом:

vec4 fboColor = texture2D(u_fboTexture, gl_FragCoord.xy/u_screenDimensions.xy);

Тогда для варианта 1:

gl_FragColor = vec4(fboColor.rgb, 1.0-texture2D(u_texture, v_texCoords).a);

или для варианта 2:

gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
gl_FragColor.a *= fboColor.r;
person Tenfour04    schedule 18.07.2014
comment
Эй, это отлично работает на рабочем столе! Но на андроиде ничего не рисуется. У вас есть идеи, почему? Пробовал с форматом кадрового буфера 8888 и 4444, но все то же самое. - person Veljko; 19.07.2014
comment
Я отладил... рисование в буфере кадров работает, но шаг, использующий пользовательскую групповую стратегию, ничего не рисует на экране. Это мой метод перед группой: Gdx.gl.glEnable(GL20.GL_DEPTH_TEST); шейдер.начало(); Shader.setUniformMatrix(u_projTrans, camera.combined); Gdx.graphics.getGL20().glActiveTexture(GL20.GL_TEXTURE1); fboTexture.bind(1); шейдер.setUniformi(u_fboTexture, 1); Shader.setUniformf(u_screenDimensions, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); Gdx.graphics.getGL20().glActiveTexture(GL20.GL_TEXTURE0); шейдер.setUniformi(u_texture, 0); - person Veljko; 19.07.2014
comment
Когда что-то работает на рабочем столе, но не на Android, для меня это обычно происходит из-за чего-то в шейдере, из-за чего шейдер не компилируется. После компиляции шейдера поместите это для целей отладки: if (!shader.isCompiled())Gdx.app.log("shader compile failed", shader.getLog());. Это сообщит вам о любых предупреждениях или ошибках, которые произошли при компиляции. На самом деле он дает вам номер строки и номер столбца, в которых возникают ошибки, но не указывает, происходят ли они в вершинном или фрагментном шейдере, поэтому иногда вам нужно проверять и то, и другое. - person Tenfour04; 19.07.2014
comment
Эй, я попробовал свой код на телефоне, он работает ... также я попробовал его на устройстве STB, и ничего не рисуется. В шейдере нет журнала ошибок или предупреждений. Я даже пытался заменить его простым красным прямоугольником, но это не имеет никакого значения. Знаете ли вы некоторые настройки, которые могут помочь? Устройство STB — моя целевая платформа, поэтому оно должно на ней работать. Какие-либо предложения? Это желейные бобы 4.2.2. - person Veljko; 21.07.2014
comment
Я нашел решение: единственное, что мне нужно было добавить, это v_color в исчислении для цвета фрагмента в шейдере фрагмента. Событие это бесполезно, я думаю, что-то не так с версией компилятора на STB или что-то в этом роде. - person Veljko; 21.07.2014
comment
Обычно дело не в компиляторе, а в том, что другие устройства, на которых вы тестировали, были более снисходительны к деталям синтаксиса, чем требует стандарт OpenGL ES. Например, большинство графических процессоров будут автоматически преобразовывать целые числа в числа с плавающей запятой, если можно сделать вывод, что это должно быть число с плавающей запятой, но это не требуется для OpenGL ES, поэтому есть несколько устройств, в которых использование целого числа в неправильном месте приведет к тому, что это не произойдет. работать. - person Tenfour04; 21.07.2014
comment
У меня была идея улучшить производительность... прямо сейчас fboColor вычисляется с зависимым чтением текстуры. Вы можете вычислить эквивалент gl_FragCoord.xy/u_screenDimensions.xy в вершинном шейдере и передать его как переменную. Я думаю, вы бы сделали это, используя gl_Position после того, как вычислили его. Итак, v_fboTexCoords = gl_Position.xy * 0.5 + 0.5;. - person Tenfour04; 21.07.2014
comment
Трюк с оптимизацией работает, но результат не тот... в маске все становится меньше. Я не очень понимаю, что это за постоянные коэффициенты, которые вы умножаете и складываете. Не могли бы вы объяснить идею и цель этого? - person Veljko; 21.07.2014
comment
Когда вы умножаете позицию вершины на матрицу проекции, чтобы получить gl_Position, она преобразует позицию в пространство экрана, где экран отображается от {-1,-1} (внизу слева) до {1,1} (вверху справа). Поэтому я делил на два и добавлял 0,5, чтобы сопоставить {0,0} с {1,1}. Но возможно я где-то ошибся. - person Tenfour04; 21.07.2014
comment
Я понимаю идею. Как я уже сказал, это работает, но кажется, что замаскированная текстура в 2 раза меньше, чем текстура с предыдущим методом. Я не могу исправить это, переместив мою замаскированную текстуру ближе к маске. Но хорошо, если у меня будут проблемы с производительностью, я посмотрю ближе к этому. Пока я буду придерживаться метода 1. Большое спасибо за вашу помощь!!! - person Veljko; 21.07.2014
comment
Может, просто добавить 0,5, не умножая на 0,5? - person Tenfour04; 21.07.2014