Проблема альфа-смешения в точечных спрайтах (Android / OpenGL ES 2.0)
Недавно я начал изучать OpenGL ES для Android и работаю над приложением для рисования. Я реализовал некоторые основы, такие как точечные спрайты, сглаживание пути и FBO для двойной буферизации. В данный момент я играю с glBlendFunc, более конкретно, когда я помещаю две текстуры близко друг к другу с одинаковыми значениями цвета / альфа, альфа добавляется, так что она становится темнее на пересечении спрайтов. Это проблема, потому что непрозрачность обводки не сохраняется, если множество точек близко друг к другу, так как цвет имеет тенденцию к большей непрозрачности, а не остается с той же непрозрачностью. Есть ли способ заставить текстуры иметь один и тот же цвет на пересечении, то есть иметь одинаковое значение альфа для пересекающихся пикселей, но сохранить значения альфа для остальных пикселей?
Вот как я сделал соответствующие части приложения:
для рисования списка точечных спрайтов я использую смешивание следующим образом:
GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
приложение использует FBO с текстурой, где сначала отрисовывается каждый мазок кисти, а затем эта текстура отображается на главном экране. Функция смешивания есть:
GLES20.glEnable(GLES20.GL_BLEND); GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
OpenGL ES 2.0 не поддерживает альфа-маскирование;
- в приложении нет функции DEPTH_TEST;
- текстуры для точечных спрайтов - это PNG с прозрачным фоном;
- приложение поддерживает маскирование текстур, что означает, что одна текстура используется для формы, а одна текстура используется для содержимого;
мой фрагментный шейдер выглядит так:
precision mediump float; uniform sampler2D uShapeTexture; uniform sampler2D uFillTexture; uniform float vFillScale; varying vec4 vColor; varying float vShapeRotation; varying float vFillRotation; varying vec4 vFillPosition; vec2 calculateRotation(float rotationValue) { float mid = 0.5; return vec2(cos(rotationValue) * (gl_PointCoord.x - mid) + sin(rotationValue) * (gl_PointCoord.y - mid) + mid, cos(rotationValue) * (gl_PointCoord.y - mid) - sin(rotationValue) * (gl_PointCoord.x - mid) + mid); } void main() { // Calculations. vec2 rotatedShape = calculateRotation(vShapeRotation); vec2 rotatedFill = calculateRotation(vFillRotation); vec2 scaleVector = vec2(vFillScale, vFillScale); vec2 positionVector = vec2(vFillPosition[0], vFillPosition[1]); // Obtain colors. vec4 colorShape = texture2D(uShapeTexture, rotatedShape); vec4 colorFill = texture2D(uFillTexture, (rotatedFill * scaleVector) + positionVector); gl_FragColor = colorShape * colorFill * vColor; }
мой вершинный шейдер таков:
attribute vec4 aPosition; attribute vec4 aColor; attribute vec4 aJitter; attribute float aShapeRotation; attribute float aFillRotation; attribute vec4 aFillPosition; attribute float aPointSize; varying vec4 vColor; varying float vShapeRotation; varying float vFillRotation; varying vec4 vFillPosition; uniform mat4 uMVPMatrix; void main() { // Sey position and size. gl_Position = uMVPMatrix * (aPosition + aJitter); gl_PointSize = aPointSize; // Pass values to fragment shader. vColor = aColor; vShapeRotation = aShapeRotation; vFillRotation = aFillRotation; vFillPosition = aFillPosition; }
Я попытался поиграться с параметрами glBlendFunc, но я не могу найти правильную комбинацию, чтобы нарисовать то, что я хочу. Я приложил несколько изображений, показывающих, чего я хотел бы достичь и что у меня есть на данный момент. Какие-либо предложения?
Решение
Наконец-то удалось добиться правильной работы всего за несколько строк благодаря @ Rabbid76. Прежде всего мне пришлось настроить функцию проверки глубины, прежде чем я начну рисовать в FBO:
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
GLES20.glDepthFunc(GLES20.GL_LESS);
// Drawing code for FBO.
GLES20.glDisable(GLES20.GL_DEPTH_TEST);
Затем в моем фрагментном шейдере я должен был убедиться, что любые пиксели с альфа < 1 в маске отбрасываются следующим образом:
...
vec4 colorMask = texture2D(uMaskTexture, gl_PointCoord);
if (colorMask.a < 1.0)
discard;
else
gl_FragColor = calculatedColor;
И результат (мерцание происходит из-за эмулятора Android и инструмента захвата gif):
2 ответа
Если вы установите glBlendFunc с функциями (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
и вы используете glBlendEquation с уравнением GL_FUNC_ADD
тогда цвет назначения вычисляется следующим образом:
C_dest = C_src * A_src + C_dest * (1-A_src)
Если вы смешаете, например, C_dest = 1
с C_src = 0.5
а также A_src = 0.5
затем:
C_dest = 0.75 = 1 * 0.5 + 0.5 * 0.5
Если вы повторите смешивание того же цвета C_src = 0.5
а также A_src = 0.5
тогда цвет назначения становится темнее:
C_dest = 0.625 = 0.75 * 0.5 + 0.5 * 0.5
Поскольку новый целевой цвет всегда является функцией исходного целевого цвета и исходного цвета, цвет не может оставаться равным при смешивании 2 раза, поскольку целевой цвет уже изменился после первого смешивания (кроме GL_ZERO
).
Вы должны избегать того, чтобы любой фрагмент смешивался дважды. Если все фрагменты нарисованы на одной глубине (2D), вы можете использовать тест глубины для этого:
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LESS );
// do the drawing with the color
glDisable( GL_DEPTH_TEST );
Или тест трафарета может быть использован. Например, тест трафарета может быть установлен на прохождение только тогда, когда буфер трафарета равен 0. Каждый раз, когда должен быть записан фрагмент, буфер трафарета увеличивается:
glClear( GL_STENCIL_BUFFER_BIT );
glEnable( GL_STENCIL_TEST );
glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
glStencilFunc( GL_EQUAL, 0, 255 );
// do the drawing with the color
glDisable( GL_STENCIL_TEST );
Расширение до ответа
Обратите внимание, что вы можете отказаться от фрагментов, которые не должны быть нарисованы. Если фрагмент в вашей текстуре спрайта имеет альфа-канал 0, вы должны отказаться от него.
Обратите внимание, что если вы отбросите фрагмент, ни цвет, ни глубина и буфер трафарета не будут записаны.
Фрагментные шейдеры также имеют доступ к
discard
команда. При выполнении эта команда приводит к тому, что выходные значения фрагмента отбрасываются. Таким образом, фрагмент не переходит к следующим этапам конвейера, и любые выходные данные фрагмента шейдера теряются.
Фрагмент шейдера
if ( color.a < 1.0/255.0 )
discard;
Это невозможно сделать с помощью смешивания с фиксированными функциями в OpenGL ES 2.0, потому что на самом деле вам не нужно альфа-смешивание. То, что вы хотите, это логическая операция (например, max(src, dst)), которая довольно отличается от того, как работает смешивание OpenGL ES.
Если вы хотите выполнить рендеринг траектории / обводки / заливки с точными пиксельными ребрами, вы можете получить что-нибудь, используя трафаретные маски и трафаретные тесты, но в этом случае вы не можете сделать прозрачность - просто логические операторы.