Я нашел несколько мест, где об этом спрашивали, но пока не нашел хорошего ответа.
Проблема: я хочу визуализировать в текстуру, а затем я хочу нарисовать эту визуализированную текстуру на экране одинаково как бы это выглядело, если бы я пропустил шаг рендеринга к текстуре и просто рендерил прямо на экран. В настоящее время я использую режим смешивания 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% для зеленого:
Итак, разбивка того, что я хочу знать:
Желаемое поведение заключается в том, что в конце этого шага конечный результат в пикселях будет таким же, как если бы я просто сделал это:
И, для полноты, вот мой код с моей первоначальной наивной попыткой (просто регулярное смешивание):
//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);
}
Основываясь на некоторых вычислениях и симуляциях, которые я провел, я нашел два довольно похожих решения, которые, кажется, делают свое дело. Один использует предварительно умноженные цвета в сочетании с одной (отдельной) функцией смешивания, другой работает без предварительно умноженных цветов, но требует изменения функции смешивания пару раз в процессе.
Этот подход работает с единственной функцией смешивания на протяжении всего процесса. Функция смешивания:
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)
вместо. Вы можете выполнить предварительное умножение в фрагментном шейдере.
Последовательность:
(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE)
,Этот подход не требует предварительного умножения цветов для любого шага. Недостатком является то, что функция смешивания должна быть переключена несколько раз во время процесса.
(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE)
,(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
,(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
,(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
,Я думаю, что Вариант 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 (Скриншот) Альфа.
Я достиг своей цели. Теперь позвольте мне поделиться этой информацией с Интернетом, поскольку она существует нигде еще что я мог найти.
Код, который я включил в вопрос, остается в основном нетронутым, за исключением того, что я гарантирую, что я связываю шейдер, который я перечислю ниже, когда я делаю свою функцию «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% правильного решения. Прямо сейчас, если я визуализирую более частично прозрачные объекты поверх существующей прозрачности, прозрачность накапливается иначе, чем справа, что приводит к осветлению окончательной текстуры за пределами того, что показано справа. Аналогично, при визуализации на не черном фоне ясно, что результаты существующего решения отличаются немного как показано выше.
Правильное решение будет идентичным во всех случаях. Мое существующее решение не может учитывать смешивание назначения при коррекции шейдера, только альфа-источник.
Для того, чтобы сделать это за один проход, вам нужна поддержка для отдельного цвета & альфа-смешивание функций. Сначала вы визуализируете текстуру, в которой вклад переднего плана хранится в альфа-канале (т.е. 1 = полностью непрозрачный, 0 = полностью прозрачный) и предварительно умноженное значение исходного цвета в цветовом канале RGB. Для создания этой текстуры выполните следующие операции:
Чтобы установить режим, указанный в 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)
Ваша проблема старше, чем OpenGL, или персональные компьютеры, или вообще любого живого человека. Вы пытаетесь смешать два изображения вместе, чтобы они выглядели так, как будто они не были смешаны вообще. Печатные машины сталкиваются с этой проблемой. Когда чернила наносятся на бумагу, получается сочетание цвета чернил и цвета бумаги.
Решение на бумаге такое же, как и в OpenGL. Вы должны изменить исходное изображение, чтобы контролировать свой конечный результат. Это достаточно легко понять, если вы изучите математику, использованную для смешивания.
Для каждого из R, G, B результирующий цвет (old * (1-opacity)) + (new * opacity)
, Основной сценарий, который вы хотели бы эмулировать, — это рисование цвета непосредственно в конечном обратном буфере с непрозрачностью A.
Например, непрозрачность составляет 50%, а ваш зеленый канал имеет 0xFF
, Результат должен быть 0x7F
на черном фоне (включая неизбежную ошибку округления). Вы, вероятно, не можете предположить, что фон черный, поэтому ожидайте, что зеленый канал будет меняться между 0x7F
а также 0xFF
,
Вы хотели бы знать, как эмулировать этот результат, когда вы действительно рендеритесь в текстуру, а затем перенаправляете текстуру в задний буфер. Оказывается, что «расплывчатые предложения использовать« предварительно умноженную альфу »были правильными. В то время как ваше решение состоит в том, чтобы использовать шейдер для расслоения предыдущей операции наложения на последнем шаге, стандартное решение состоит в том, чтобы умножить цвета исходной текстуры исходного материала на альфа-канал (он же предварительно умноженный альфа). При компостировании промежуточной текстуры каналы RGB смешиваются без умножения на альфа. При рендеринге текстуры в задний буфер против каналов RGB смешиваются без умножения на альфа. Таким образом, вы аккуратно избегаете проблемы многократного умножения.
пожалуйста советоваться эти Ресурсы для лучшего понимания. Я и большинство других знакомы с этой техникой в DirectX, поэтому вам, возможно, придется искать соответствующие флаги OGL.