OpenGL рендеринг в текстуру с частичной прозрачностью (прозрачность) и затем рендеринг это на экран

Я нашел несколько мест, где об этом спрашивали, но пока не нашел хорошего ответа.

Проблема: я хочу визуализировать в текстуру, а затем я хочу нарисовать эту визуализированную текстуру на экране одинаково как бы это выглядело, если бы я пропустил шаг рендеринга к текстуре и просто рендерил прямо на экран. В настоящее время я использую режим смешивания glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). У меня есть glBlendFuncSeparate, чтобы поиграть также.

Я хочу иметь возможность визуализировать частично прозрачные элементы перекрытия с этой текстурой. Я знаю, что функция смешивания в настоящее время портит значения RGB на основе альфы. Я видел некоторые расплывчатые предложения использовать «предварительно умноженную альфу», но описание плохое относительно того, что это значит. Я делаю PNG-файлы в фотошопе, я знаю, что у них есть бит прозрачности, и вы не можете легко редактировать альфа-канал независимо, как вы можете с TGA. При необходимости я могу переключиться на TGA, хотя PNG более удобен.

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

Как только я рендерил свою сцену в текстуру, мне нужно рендерить эту текстуру в другую сцену, и мне снова нужно СЛИВАТЬ текстуру, предполагая частичную прозрачность. Здесь вещи разваливаются. В предыдущих шагах наложения я четко изменял значения RGB на основе альфа-канала, повторяя его, все в порядке, если альфа-значение равно 0 или 1, но если оно находится между ними, результатом является дальнейшее затемнение этих частично полупрозрачных пикселей.

Играя с режимами смешивания, мне очень мало повезло. Лучшее, что я могу сделать, это рендерить текстуру с помощью:

glBlendFuncSeparate (GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);

Я обнаружил, что рендеринг несколько раз с помощью glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) будет приближаться к правильному цвету (если вещи не перекрываются). Но это не совсем идеально (как вы можете видеть на следующем изображении, участки, где зеленые / красные / синие прямоугольники перекрываются, становятся темнее или накапливают альфа. (РЕДАКТИРОВАТЬ: если я выполняю многократное рисование на рендере на часть экрана и только При рендеринге один раз в текстуру проблема накопления альфа исчезает и это работает, но почему ?! Я не хочу, чтобы одна и та же текстура сотня раз появлялась на экране, чтобы заставить ее накапливаться правильно)

Вот некоторые изображения, детализирующие проблему (несколько проходов рендеринга с базовым смешиванием (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), и они визуализируются несколько раз на этапе рендеринга текстуры. 3 поля справа отображаются на 100% красным, зеленым или синим цветом (0-255), но при альфа-значениях 50% для синего, 25% для красного и 75% для зеленого:
введите описание изображения здесь

Итак, разбивка того, что я хочу знать:

  1. Я установил режим смешивания на: Икс?
  2. Я отрисовываю свою сцену в текстуру. (Может быть, я должен сделать несколько режимов смешивания или несколько раз?)
  3. Я установил режим смешивания на: Y?
  4. Я рендерил свою текстуру на экран поверх существующей сцены. (Может быть, мне нужен другой шейдер? Может быть, мне нужно визуализировать текстуру несколько раз?)

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

  1. Я установил режим смешивания на: (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  2. Я отрисовываю свою сцену на экране.

И, для полноты, вот мой код с моей первоначальной наивной попыткой (просто регулярное смешивание):

    //RENDER TO TEXTURE.
void Clipped::refreshTexture(bool a_forceRefresh) {
if(a_forceRefresh || dirtyTexture){
auto pointAABB = basicAABB();
auto textureSize = castSize<int>(pointAABB.size());
clippedTexture = DynamicTextureDefinition::make("", textureSize, {0.0f, 0.0f, 0.0f, 0.0f});
dirtyTexture = false;
texture(clippedTexture->makeHandle(Point<int>(), textureSize));
framebuffer = renderer->makeFramebuffer(castPoint<int>(pointAABB.minPoint), textureSize, clippedTexture->textureId());
{
renderer->setBlendFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
SCOPE_EXIT{renderer->defaultBlendFunction(); };

renderer->modelviewMatrix().push();
SCOPE_EXIT{renderer->modelviewMatrix().pop(); };
renderer->modelviewMatrix().top().makeIdentity();

framebuffer->start();
SCOPE_EXIT{framebuffer->stop(); };

const size_t renderPasses = 1; //Not sure?
if(drawSorted){
for(size_t i = 0; i < renderPasses; ++i){
sortedRender();
}
} else{
for(size_t i = 0; i < renderPasses; ++i){
unsortedRender();
}
}
}
alertParent(VisualChange::make(shared_from_this()));
}
}

Вот код, который я использую для настройки сцены:

    bool Clipped::preDraw() {
refreshTexture();

pushMatrix();
SCOPE_EXIT{popMatrix(); };

renderer->setBlendFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
SCOPE_EXIT{renderer->defaultBlendFunction();};
defaultDraw(GL_TRIANGLE_FAN);

return false; //returning false blocks the default rendering steps for this node.
}

И код для рендеринга сцены:

test = MV::Scene::Rectangle::make(&renderer, MV::BoxAABB({0.0f, 0.0f}, {100.0f, 110.0f}), false);
test->texture(MV::FileTextureDefinition::make("Assets/Images/dogfox.png")->makeHandle());

box = std::shared_ptr<MV::TextBox>(new MV::TextBox(&textLibrary, MV::size(110.0f, 106.0f)));
box->setText(UTF_CHAR_STR("ABCDE FGHIJKLM NOPQRS TUVWXYZ"));
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({0, 0, 1, .5})->position({80.0f, 10.0f})->setSortDepth(100);
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({1, 0, 0, .25})->position({80.0f, 40.0f})->setSortDepth(101);
box->scene()->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({0, 1, 0, .75})->position({80.0f, 70.0f})->setSortDepth(102);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({.0, 0, 1, .5})->position({110.0f, 10.0f})->setSortDepth(100);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({1, 0, 0, .25})->position({110.0f, 40.0f})->setSortDepth(101);
test->make<MV::Scene::Rectangle>(MV::size(65.0f, 36.0f))->color({.0, 1, 0, .75})->position({110.0f, 70.0f})->setSortDepth(102);

И вот мой скриншот:

renderer.clearScreen();
test->draw(); //this is drawn directly to the screen.
box->scene()->draw(); //everything in here is in a clipped node with a render texture.
renderer.updateScreen();

* РЕДАКТИРОВАТЬ: НАСТРОЙКА КАДРА / КАРТА ТЕАРДОНА:

void glExtensionFramebufferObject::startUsingFramebuffer(std::shared_ptr<Framebuffer> a_framebuffer, bool a_push){
savedClearColor = renderer->backgroundColor();
renderer->backgroundColor({0.0, 0.0, 0.0, 0.0});

require(initialized, ResourceException("StartUsingFramebuffer failed because the extension could not be loaded"));
if(a_push){
activeFramebuffers.push_back(a_framebuffer);
}

glBindFramebuffer(GL_FRAMEBUFFER, a_framebuffer->framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, a_framebuffer->texture, 0);
glBindRenderbuffer(GL_RENDERBUFFER, a_framebuffer->renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, roundUpPowerOfTwo(a_framebuffer->frameSize.width), roundUpPowerOfTwo(a_framebuffer->frameSize.height));

glViewport(a_framebuffer->framePosition.x, a_framebuffer->framePosition.y, a_framebuffer->frameSize.width, a_framebuffer->frameSize.height);
renderer->projectionMatrix().push().makeOrtho(0, static_cast<MatrixValue>(a_framebuffer->frameSize.width), 0, static_cast<MatrixValue>(a_framebuffer->frameSize.height), -128.0f, 128.0f);

GLenum buffers[] = {GL_COLOR_ATTACHMENT0};
//pglDrawBuffersEXT(1, buffers);renderer->clearScreen();
}

void glExtensionFramebufferObject::stopUsingFramebuffer(){
require(initialized, ResourceException("StopUsingFramebuffer failed because the extension could not be loaded"));
activeFramebuffers.pop_back();
if(!activeFramebuffers.empty()){
startUsingFramebuffer(activeFramebuffers.back(), false);
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

glViewport(0, 0, renderer->window().width(), renderer->window().height());
renderer->projectionMatrix().pop();
renderer->backgroundColor(savedClearColor);
}
}

И мой четкий код экрана:

void Draw2D::clearScreen(){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

8

Решение

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

Вариант 1: одна функция смешивания, предварительное умножение

Этот подход работает с единственной функцией смешивания на протяжении всего процесса. Функция смешивания:

glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA,
GL_ONE_MINUS_DST_ALPHA, GL_ONE);

Это требует предварительно умноженных цветов, что означает, что если ваш входной цвет будет обычно (r, g, b, a), ты используешь (r * a, g * a, b * a, a) вместо. Вы можете выполнить предварительное умножение в фрагментном шейдере.

Последовательность:

  1. Установите функцию смешивания на (GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE),
  2. Установите цель рендеринга на FBO.
  3. Визуализируйте слои, которые вы хотите визуализировать в FBO, используя предварительно умноженные цвета.
  4. Установите цель рендеринга в кадровый буфер по умолчанию.
  5. Рендеринг слоев, которые вы хотите ниже содержимого FBO, используя предварительно умноженные цвета.
  6. Визуализировать приложение FBO, без применение предварительного умножения, так как цвета в FBO уже предварительно умножены.
  7. Отрисовывайте нужные слои поверх содержимого FBO, используя предварительно умноженные цвета.

Вариант 2. Переключение функций смешивания без предварительного умножения

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

  1. Установите функцию смешивания на (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE),
  2. Установите цель рендеринга на FBO.
  3. Рендеринг слоев, которые вы хотите визуализировать в FBO.
  4. Установите цель рендеринга в кадровый буфер по умолчанию.
  5. (необязательно) Установите функцию смешивания на (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
  6. Рендеринг слоев, которые вы хотите ниже содержимого FBO.
  7. Установите функцию смешивания на (GL_ONE, GL_ONE_MINUS_SRC_ALPHA),
  8. Рендер FBO вложения.
  9. Установите функцию смешивания на (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
  10. Рендеринг слоев, которые вы хотите поверх содержимого FBO.

Объяснение и доказательство

Я думаю, что Вариант 1 лучше и, возможно, более эффективен, потому что он не требует переключения функций наложения во время рендеринга. Таким образом, подробное объяснение ниже для Варианта 1. Математика для Варианта 2 почти такая же. Единственная реальная разница в том, что Вариант 2 использует GL_SOURCE_ALPHA для первого члена функции наложения, чтобы выполнить предварительное умножение, где это необходимо, где Вариант 1 ожидает, что предварительно умноженные цвета войдут в функцию наложения.

Чтобы проиллюстрировать, что это работает, давайте рассмотрим пример, где визуализируются 3 слоя. Я сделаю все расчеты для r а также a компоненты. Расчеты для g а также b будет эквивалентно тем для r, Мы будем рендерить три слоя в следующем порядке:

(r1, a1)  pre-multiplied: (r1 * a1, a1)
(r2, a2)  pre-multiplied: (r2 * a2, a2)
(r3, a3)  pre-multiplied: (r3 * a3, a3)

Для эталонного расчета мы смешаем эти 3 слоя со стандартным GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA смешанная функция. Нам не нужно отслеживать полученную альфа здесь, так как DST_ALPHA не используется в функции смешивания, и мы еще не используем предварительно умноженные цвета:

after layer 1: (a1 * r1)
after layer 2: (a2 * r2 + (1.0 - a2) * a1 * r1)
after layer 3: (a3 * r3 + (1.0 - a3) * (a2 * r2 + (1.0 - a2) * a1 * r1)) =
(a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3) * (1.0 - a2) * a1 * r1)

Таким образом, последний срок является нашей целью для конечного результата. Теперь мы рендерим слои 2 и 3 в FBO. Позже мы будем рендерить слой 1 в буфер кадра, а затем смешивать FBO поверх него. Цель состоит в том, чтобы получить тот же результат.

С этого момента мы будем применять функцию смешивания, указанную в начале, и использовать предварительно умноженные цвета. Нам также нужно будет рассчитать альфы, так как DST_ALPHA используется в функции смешивания. Сначала мы рендерим слои 2 и 3 в FBO:

after layer 2: (a2 * r2, a2)
after layer 3: (a3 * r3 + (1.0 - a3) * a2 * r2, (1.0 - a2) * a3 + a2)

Теперь мы визуализируем первичный кадровый буфер. Так как мы не заботимся о получающейся альфе, я вычислю только r компонент снова:

after layer 1: (a1 * r1)

Теперь мы смешиваем содержимое FBO с этим. Итак, что мы рассчитали для «после слоя 3» в FBO, это наш исходный цвет / альфа, a1 * r1 цвет назначения, и GL_ONE, GL_ONE_MINUS_SRC_ALPHA все еще функция смешивания. Цвета в FBO уже предварительно умножены, поэтому в шейдере не будет предварительного умножения при смешивании содержимого FBO:

srcR = a3 * r3 + (1.0 - a3) * a2 * r2
srcA = (1.0 - a2) * a3 + a2
dstR = a1 * r1
ONE * srcR + ONE_MINUS_SRC_ALPHA * dstR
= srcR + (1.0 - srcA) * dstR
= a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - ((1.0 - a2) * a3 + a2)) * a1 * r1
= a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3 + a2 * a3 - a2) * a1 * r1
= a3 * r3 + (1.0 - a3) * a2 * r2 + (1.0 - a3) * (1.0 - a2) * a1 * r1

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

Этот ответ на похожий вопрос имеет более GL_ONE_MINUS_DST_ALPHA, GL_ONE часть функции смешивания: OpenGL ReadPixels (Скриншот) Альфа.

13

Другие решения

Я достиг своей цели. Теперь позвольте мне поделиться этой информацией с Интернетом, поскольку она существует нигде еще что я мог найти.

введите описание изображения здесь

  1. Создайте свой фрейм-буфер (вслепую и т. Д.)
  2. Очистить кадровый буфер до 0, 0, 0, 0
  3. Установите ваш видовой экран правильно. Это все основные вещи, которые я принял как должное в вопросе, но хочу включить сюда.
  4. Теперь визуализируйте вашу сцену в буфер кадров с помощью glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). Убедитесь, что сцена отсортирована (как обычно).
  5. Теперь свяжите включенный фрагментный шейдер. Это устранит ущерб, нанесенный значениям цвета изображения через функцию наложения.
  6. Визуализируйте текстуру на экране с помощью glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  7. Вернитесь к обычному рендерингу с обычным шейдером.

Код, который я включил в вопрос, остается в основном нетронутым, за исключением того, что я гарантирую, что я связываю шейдер, который я перечислю ниже, когда я делаю свою функцию «preDraw», которая специфична для моей собственной маленькой платформы, но в основном это «рисование на экран» позвоните для моей визуализации текстуры.

Я называю это «unblend» шейдером.

#version 330 core

smooth in vec4 color;
smooth in vec2 uv;

uniform sampler2D texture;

out vec4 colorResult;

void main(){
vec4 textureColor = texture2D(texture, uv.st);

textureColor/=sqrt(textureColor.a);

colorResult = textureColor * color;
}

Почему я делаю textureColor / = sqrt (textureColor.a)? Потому что оригинальный цвет изображен так:

resultR = r * a, resultG = g * a, resultB = b * a, resultA = a * a

Теперь, если мы хотим отменить это, нам нужно выяснить, что это такое. Самый простой способ найти это решить для «а» здесь:

результат A = a * a

Если a .25 при первоначальном рендеринге мы имеем:

результат A = .25 * .25

Или же:

результат A = 0,0625

Когда текстура выводится на экран, у нас больше нет «а». Мы знаем, каков результат A, это альфа-канал текстуры. Таким образом, мы можем sqrt (resultA) вернуть .25. Теперь с этим значением мы можем разделить, чтобы отменить умножение:

textureColor / = SQRT (textureColor.a);

И это все исправляет, отменяя смешение!

* РЕДАКТИРОВАТЬ: Ну … Вроде как. Там является неточность ловкости, в этом случае я могу показать это, отображая более чистый цвет, который не идентичен чистому цвету кадрового буфера. Некоторая альфа-информация, похоже, потеряна, вероятно, в каналах RGB. Это еще достаточно хорошо для меня, но я хотел посмотреть на скриншот, показывающий неточность перед выходом из системы. Если у кого-то есть решение, пожалуйста, предоставьте его!

Я открыл щедрость, чтобы довести этот ответ до канонического 100% правильного решения. Прямо сейчас, если я визуализирую более частично прозрачные объекты поверх существующей прозрачности, прозрачность накапливается иначе, чем справа, что приводит к осветлению окончательной текстуры за пределами того, что показано справа. Аналогично, при визуализации на не черном фоне ясно, что результаты существующего решения отличаются немного как показано выше.

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

введите описание изображения здесь

2

Для того, чтобы сделать это за один проход, вам нужна поддержка для отдельного цвета & альфа-смешивание функций. Сначала вы визуализируете текстуру, в которой вклад переднего плана хранится в альфа-канале (т.е. 1 = полностью непрозрачный, 0 = полностью прозрачный) и предварительно умноженное значение исходного цвета в цветовом канале RGB. Для создания этой текстуры выполните следующие операции:

  1. очистить текстуру до RGBA = [0, 0, 0, 0]
  2. установить смешивание цветового канала на src_color * src_alpha + dst_color * (1-src_alpha)
  3. установите смешивание альфа-канала на src_alpha * (1-dst_alpha) + dst_alpha
  4. визуализировать сцену в текстуру

Чтобы установить режим, указанный в 2) и 3), вы можете сделать: glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE) а также glBlendEquation(GL_FUNC_ADD)

Затем визуализируйте эту текстуру на сцене, установив смешивание цветов на:
src_color + dst_color * (1-src_alpha), т.е. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) а также glBlendEquation(GL_FUNC_ADD)

1

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

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

Для каждого из R, G, B результирующий цвет (old * (1-opacity)) + (new * opacity), Основной сценарий, который вы хотели бы эмулировать, — это рисование цвета непосредственно в конечном обратном буфере с непрозрачностью A.

Например, непрозрачность составляет 50%, а ваш зеленый канал имеет 0xFF, Результат должен быть 0x7F на черном фоне (включая неизбежную ошибку округления). Вы, вероятно, не можете предположить, что фон черный, поэтому ожидайте, что зеленый канал будет меняться между 0x7F а также 0xFF,

Вы хотели бы знать, как эмулировать этот результат, когда вы действительно рендеритесь в текстуру, а затем перенаправляете текстуру в задний буфер. Оказывается, что «расплывчатые предложения использовать« предварительно умноженную альфу »были правильными. В то время как ваше решение состоит в том, чтобы использовать шейдер для расслоения предыдущей операции наложения на последнем шаге, стандартное решение состоит в том, чтобы умножить цвета исходной текстуры исходного материала на альфа-канал (он же предварительно умноженный альфа). При компостировании промежуточной текстуры каналы RGB смешиваются без умножения на альфа. При рендеринге текстуры в задний буфер против каналов RGB смешиваются без умножения на альфа. Таким образом, вы аккуратно избегаете проблемы многократного умножения.

пожалуйста советоваться эти Ресурсы для лучшего понимания. Я и большинство других знакомы с этой техникой в ​​DirectX, поэтому вам, возможно, придется искать соответствующие флаги OGL.

0