Как сделать эффект исчезновения в черном с OpenGL? - PullRequest
4 голосов
/ 25 июля 2011

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

Я объясню, как это будет работать:

Если я нарисую 1 белый пиксель и переместу его вокруг каждого кадра на один пиксель в каком-то направлении, каждый кадр пикселей экрана будет получать на одно значение R / G / B меньше (в диапазоне 0-255), таким образом, после 255 кадров белый пиксель будет полностью черным. Поэтому, если бы я переместил белый пиксель вокруг, я увидел бы градиентный след, переходящий от белого к черному равномерно, разница в 1 цветовая величина по сравнению с предыдущим цветом пикселя.

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

Edit2: Так как здесь есть некоторая путаница, я хотел бы сказать, что я могу уже делать этот вид эффекта, рисуя черный прозрачный квад по всей моей сцене. НО, это не работает, поскольку я хочу, чтобы это работало; существует ограничение на темноту, которую могут получить пиксели, поэтому некоторые пиксели всегда остаются «видимыми» (значение цвета выше нуля), поскольку: 1 * 0,9 = 0,9 -> снова округлено до 1 и т. д. Я могу «исправить» «это, делая след короче, но я хочу иметь возможность регулировать длину следа как можно больше, и вместо билинейной (если это правильное слово) интерполяции я хочу линейную (так что она всегда будет уменьшать -1 с каждого r, g, b в масштабе 0-255 вместо использования значения в процентах).

Edit3: Осталась некоторая путаница, поэтому давайте проясним: я хочу улучшить эффект, который достигается отключением GL_COLOR_BUFFER_BIT из glClear (), я не хочу видеть пиксели на моем экране FOREVER, поэтому я хочу сделать их темнее, нарисовав поверх моей сцены четырехугольник, который уменьшит значение цвета каждого пикселя на 1 (в масштабе 0-255).

Edit4: Я сделаю это просто, я хочу метод OpenGL для этого, эффект должен использовать как можно меньше энергии, памяти или полосы пропускания. Предполагается, что этот эффект будет работать без очистки пикселей экрана, поэтому, если я нарисую прозрачный квадрат над своей сценой, предыдущие пиксели станут темнее и т. д. Но, как объяснялось выше несколько раз, он работает не очень хорошо. Большие НО: 1) считывание пикселей с экрана, изменение их по одному в цикле for и затем загрузка обратно. 2) рендеринг моих объектов X раз с различными темностями, чтобы имитировать эффект следа. 3) умножение значений цвета не является вариантом, поскольку пиксели не станут черными, они останутся на экране навсегда при определенной яркости (см. Объяснение где-то выше).

Ответы [ 5 ]

9 голосов
/ 22 августа 2011

Если я нарисую 1 белый пиксель и переместу его вокруг каждого кадра на один пиксель в некотором направлении, каждый кадр пикселов экрана будет получать на одно значение R / G / B меньше (в диапазоне 0-255), таким образом после255 кадров белого пикселя будут полностью черными.Поэтому, если бы я переместил белый пиксель вокруг, я увидел бы градиентный след, идущий от белого к черному равномерно, с разницей в 1 цвет по сравнению с предыдущим цветом пикселя.

Прежде чем объяснить, как это сделать, яЯ хотел бы сказать, что визуальный эффект, к которому вы стремитесь, это ужасный визуальный эффект, и вы не должны его использовать.Вычитание значения из каждого из цветов RGB даст другой цвет , а не более темную версию того же цвета.Цвет RGB (255,128,0), если вы вычесть 1 из него 128 раз, станет (128, 0, 0).Первый цвет коричневый, второй темно-красный.Это не одно и то же.

Теперь, так как вы не очень хорошо объяснили это, я должен сделать некоторые предположения.Я предполагаю, что в том, что вы делаете, нет «объектов».Там нет государства.Вы просто рисуете вещи в произвольных местах, и вы не помните, что и где вы нарисовали, и не хотите помнить, что и где было нарисовано.

Чтобы делать то, что вы хотите, вам нужно два за кадромбуферы.Я рекомендую использовать FBO s и текстуры размером с экран для них.Основной алгоритм прост.Вы визуализируете изображение предыдущего кадра в текущее изображение, используя режим наложения, который «вычитает 1» из цветов, которые вы пишете.Затем вы рендерите новый материал, который вы хотите, к текущему изображению.Затем вы отображаете это изображение.После этого вы выбираете, какое изображение является предыдущим, а какое - текущим, и выполняете процесс заново.

Примечание. Следующий код примет на себя функциональность OpenGL 3.3.

Инициализация

Итак, сначала, во время инициализации (после инициализации OpenGL), вы должны создать текстуры размером с экран.Вам также понадобятся два буфера глубины размером с экран.

GLuint screenTextures[2];
GLuint screenDepthbuffers[2];
GLuint fbos[2]; //Put these definitions somewhere useful.

glGenTextures(2, screenTextures);
glGenRenderbuffers(2, screenDepthbuffers);
glGenFramebuffers(2, fbos);
for(int i = 0; i < 2; ++i)
{
  glBindTexture(GL_TEXTURE_2D, screenTextures[i]);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, SCREEN_WIDTH, SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glBindTexture(GL_TEXTURE_2D, 0);

  glBindRenderbuffer(GL_RENDERBUFFER, screenDepthBuffers[i]);
  glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCREEN_WIDTH, SCREEN_HEIGHT);
  glBindRenderbuffer(GL_RENDERBUFFER, 0);

  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[i]);
  glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, screenTextures[i], 0);
  glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, screenDepthBuffers[i]);
  if(glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    //Error out here.
  }
  glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

Рисование предыдущего кадра

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

ДляДля этого нам нужно иметь концепцию предыдущего и текущего FBO.Это делается с помощью двух переменных: currIndex и prevIndex.Эти значения являются индексами в наших массивах GLuint для текстур, буферов рендеринга и FBO.Они должны быть инициализированы (во время инициализации, а не для каждого кадра) следующим образом:

currIndex = 0;
prevIndex = 1;

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

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

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[currIndex]);
glClearColor(...);
glClearDepth(...);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

glActiveTexture(GL_TEXTURE0 + 0);
glBindTexture(GL_TEXTURE_2D, screenTextures[prevIndex]);
glUseProgram(BlenderProgramObject); //The shader will be talked about later.

RenderFullscreenQuadWithTexture();

glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);

Функция RenderFullscreenQuadWithTexture делает именно то, что говорит: визуализирует квадратор размера экрана, используя текущую связанную текстуру.Программный объект BlenderProgramObject - это шейдер GLSL, который выполняет нашу операцию смешивания.Он извлекает из текстуры и смешивает.Опять же, я предполагаю, что вы знаете, как настроить шейдер и т. Д.

Фрагментный шейдер будет иметь основную функцию, которая выглядит примерно так:

shaderOutput = texture(prevImage, texCoord) - (1.0/255.0);

Опять же, яНастоятельно советуем это:

shaderOutput = texture(prevImage, texCoord) * (0.05);

Если вы не знаете, как использовать шейдеры, то вам следует научиться.Но если вы не хотите, то вы можете получить тот же эффект, используя функцию glTexEnv .И если вы не знаете, что это такое, я предлагаю обучение шейдерам ;в конечном итоге все намного проще.

Ничья как обычно

Теперь вы просто отрисовываете все как обычно.Только не отвязывай FBO;мы по-прежнему хотим визуализировать его.

Отображение визуализированного изображения на экране

Обычно для отображения результатов вашего рендеринга вы используете вызов swapbuffer.Но так как мы рендерились в FBO, мы не можем этого сделать.Вместо этого мы должны сделать что-то другое.Мы должны скопировать наше изображение в буфер и затем поменять местами буферы.

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[currIndex]);
glBlitFramebuffer(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, SCREEN_WDITH, SCREEN_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
//Do OpenGL swap buffers as normal

Переключить изображения

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

std::swap(currIndex, prevIndex);

И все готово.

6 голосов
/ 21 августа 2011

Возможно, вы захотите отобразить черный прямоугольник с альфа-каналом, переходящим от 1,0 до 0,0, используя glBlendFunc (GL_ONE, GL_SRC_ALPHA).

Редактировать в ответ на ваш комментарий (ответ не помещается в комментарии):

Вы не можете замирать отдельные пиксели в зависимости от их возраста с помощью простой операции перехода от черного к черному.Обычно цель рендеринга не «запоминает», что было нарисовано в предыдущих кадрах.Я мог бы придумать способ сделать это, чередуя рендеринг с одной из пары FBO и используя для этого их альфа-канал, но вам нужен там шейдер.Итак, что вы должны сделать - это сначала визуализировать FBO, содержащее пиксели в их прежних позициях, уменьшив их альфа-значение на единицу, отбросив их при альфа == 0, в противном случае затемните их при уменьшении альфа-канала, а затем визуализируйте пиксели в их текущих позициях с помощьюальфа == 255.

Если у вас есть только движущиеся пиксели:

render FBO 2 to FBO 1, darkening each pixel in it by a scale (skip during first pass)
render moving pixels to FBO 1
render FBO 1 to FBO 2 (FBO 2 is the "age" buffer)
render FBO 2 to screen

Если вы хотите изменить какую-либо сцену (т.е. иметь сцену и движущиеся пиксели в ней):

set glBlendFunc (GL_ONE, GL_ZERO)
render FBO 2 to FBO 1, reducing each alpha > 0.0 in it by a scale (skip during first pass)
render moving pixels to FBO 1
render FBO 1 to FBO 2 (FBO 2 is the "age" buffer)
render the scene to screen
set glBlendFunc (GL_ONE, GL_SRC_ALPHA)
render FBO 2 to screen

На самом деле шкала должна быть (плавать) / 255.0 / 255.0, чтобы компоненты одинаково исчезали (а не тот, который начинался с более низкого значения, становился нулевым раньше, чем другие).

Если вытолько с несколькими движущимися пикселями, вы можете повторно визуализировать пиксель на всех предыдущих позициях до 255 «тиков» назад.

Так как вам все равно нужно повторно визуализировать каждый из пикселей, просто визуализируйте каждый с помощьюправильный градиент цвета: чем темнее, тем старше пиксель.Если у вас очень много пикселей, может подойти двойной подход FBO.

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

2 голосов
/ 21 августа 2011

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

1 голос
/ 24 августа 2011

Именно в таких ситуациях я не могу использовать чистый OpenGL.Я не уверен, есть ли в вашем проекте место для этого (чего может не быть, если вы используете другой оконный API) или стоит дополнительная сложность, но добавление 2D-библиотеки, такой как SDL, которая работает с OpenGL, позволит вамдля прямой работы с пикселями поверхности дисплея разумным образом, а также с пикселями в целом, что, как правило, не делает OpenGL нелегким.

Тогда все, что вам нужно сделать, - это пробежать по пикселям поверхности дисплеядо того, как OpenGL отобразит свою геометрию, и вычтите 1 из каждого компонента RGB.

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

1 голос
/ 25 июля 2011

Это драйверы, сама Windows не поддерживает OpenGL или только младшую версию, я думаю 1.5 Все новые версии поставляются с драйверами от ATI или NVIDIA, Intel и т. Д. Вы используете разные карты? Какую версию OpenGL вы эффективно используете?

...