#opengl #glsl #framebuffer #render-to-texture #multisampling
#opengl #glsl #фреймбуфер #визуализация в текстуру #мультисэмплинг
Вопрос:
Реализуя некоторый эффект, я получаю 1 кадровый буфер, связанный с 1 текстурой, в котором хранится моя финальная сцена. Затем эта текстура применяется к полноэкранному режиму.
Результат — это то, что я ожидаю от эффекта, но я заметил, что края на визуализируемой таким образом сцене не были гладкими — предположительно, потому, что множественная выборка не применялась во время переходов от рендеринга к фреймбуферу, как это происходит при рендеринге непосредственно в экранный буфер.
Итак, мой вопрос
Как я могу применить / использовать множественную выборку к этой окончательной текстуре, чтобы ее содержимое отображало плавные края?
РЕДАКТИРОВАТЬ: я удалил оригинальную версию своего кода здесь, в которой использовался классический фреймбуфер текстура без множественной выборки. Ниже приводится последнее, следуя предложениям в комментариях.
На данный момент я также сосредоточусь на том, чтобы заставить работать подход glBlitFramebuffer!
Итак, мой код теперь выглядит так:
// Unlike before, finalTexture is multi-sampled, thus created like this:
glGenFramebuffers(1, amp;finalFrame);
glGenTextures(1, amp;finalTexture);
glBindFramebuffer(GL_FRAMEBUFFER, finalFrame);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, finalTexture);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA, w, h, GL_TRUE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D_MULTISAMPLE,
finalTexture,
0);
// Alternative using a render buffer instead of a texture.
//glGenRenderbuffers(1, amp;finalColor);
//glBindRenderbuffer(GL_RENDERBUFFER, finalColor);
//glRenderbufferStorageMultisample(GL_RENDERBUFFER, 8, GL_RGBA, w, h);
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, finalColor);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Then I introduced a new frame buffer to resolve the multi-sampling:
// This one's not multi-sampled.
glGenFramebuffers(1, amp;resolveFrame);
glGenTextures(1, amp;resolveTexture);
glBindFramebuffer(GL_FRAMEBUFFER, resolveFrame);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
resolveTexture,
0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Now a lot of code to produce a glowing effect, things like:
// 1. Generate 1 frame buffer with 2 color attachments (textures) - no multisampling
// 2. Render the 3D scene to it:
// - texture 0 receives the entire scene
// - texture 1 receives glowing objects only
// 3. Generate 2 frame buffers with 1 color attachment (texture) each - no multisampling
// - we can call them Texture 2 and texture 3
// 4. Ping-pong Render a fullscreen textured quad on them
// - On the first iteration we use texture 1
// - Then On each following iteration we use one another's texture (3,2,3...)
// - Each time we apply a gaussian blur
// 5. Finally sum texture 0 and texture 3 (holding the last blur result)
// - For this we create a multi-sampled frame buffer:
// - Created as per code here above: finalFrame amp; **finalTexture**
// - To produce the sum, we draw a full screen texured quad with 2 sampler2D:
// - The fragment shader then computes texture0 texture3 on each pixel
// - finalTexture now holds the scene as I expect it to be
// Then I resolve the multi-sampled texture into a normal one:
glBindFramebuffer(GL_READ_FRAMEBUFFER, finalFrame);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFrame);
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
// And the last stage: render onto the screen:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, resolveTexture);
drawFullScreenQuad( ... );
Результирующий результат правильный, что означает, что я могу видеть сцену с желаемым эффектом свечения… Но нет очевидной множественной выборки! 🙁
Примечание: я начинаю задаваться вопросом, правильно ли я использую множественную выборку на правильном этапе — я буду экспериментировать с этим — но есть ли шанс, что я должен использовать его при рендеринге начальной 3D-сцены в первый раз, на начальных FBO? (те, на которые я ссылаюсь в комментариях, и я не хотел публиковать здесь, чтобы избежать путаницы: s)
Я добавил более подробные комментарии о том, что происходит перед этим последним этапом с буферами кадров final amp; resolve.
Комментарии:
1. Использование текстуры / буфера визуализации с несколькими выборками в качестве вложения FBO, а затем копирование его в буфер кадров по умолчанию с
glBlitFramebuffer()
помощью, звучит разумно. Чтобы узнать, почему у вас это не сработало, нам нужно будет посмотреть код, который вы пробовали.2. Спасибо, Reto, я скоро добавлю код, который я использовал для glBlitFramebuffer. В то же время вы, возможно, указали на мое недопонимание: я пытался использовать мою текстуру без множественной выборки и «Блит» в текстуру с несколькими выборками, а затем отобразить мою текстуру с текстурой в полноэкранном режиме с последней текстурой: я никогда не делал Блит непосредственно в буфер кадров по умолчанию с источникомбуфер кадров, который был многократно выбран.
3. @RetoKoradi Готово — надеюсь, это поможет. Кроме того, просто для ясности: правильно ли я говорю, что использование sampler2DMS и texelFetch в шейдере — это совершенно другой подход, т.Е. Это был бы другой способ, чем glBlitFramebuffer, для решения моей проблемы?
4. Это сработает, но, как правило, довольно медленно. Не то, чтобы блит фреймбуфера был быстрым каким-либо образом, это просто медленнее, чем явная выборка n-многих выборок в шейдере, а затем их усреднение для самостоятельного решения. Для копий fbo-> fbo с идентичным хранилищем изображений обычно быстрее использовать шейдеры и растягивать текстурированный квадрат, чем блиттинг, но это не одна из таких ситуаций 😉
5. @AndonM. Коулман, я вижу, спасибо за ваш ответ 🙂 Возвращаясь к моему приведенному выше коду, есть идеи, почему я не вижу никакого результата? Продолжая читать по этому вопросу, я обнаружил, что для fbo с несколькими выборками мне на самом деле не нужно использовать текстуру с несколькими выборками, но вместо этого можно использовать буфер рендеринга MS (потому что я не использую текстуру MS напрямую). К сожалению, даже это не работает… : ( Я также обновлю свой пост кодом буфера рендеринга MS.
Ответ №1:
У вас есть: «шаг 5. Наконец, суммируйте текстуру 0 и текстуру 3 (удерживая последний результат размытия) — для этого мы создаем буфер кадров с несколькими выборками «. Но таким образом мультисэмплинг будет применяться только к полноэкранному режиму.
«если я использую множественную выборку на нужном этапе», то ответ на ваш вопрос отрицательный, вам нужно использовать множественную выборку на другом этапе при рендеринге сцены.
У меня очень похожая настройка с фреймбуферами (тот, который используется для рендеринга сцены, является мультисэмплированным), двумя выходными текстурами (для информации о цвете и для бликов, которые позже будут размыты для достижения свечения) и фреймбуферами для пинг-понга. Я также использую решение glBlitFramebuffer (также я использую 2 блит-вызова для каждого цветового вложения, каждый из которых будет входить в собственную текстуру), не нашел никакого способа заставить его визуализироваться непосредственно в фреймбуфер с прикрепленной текстурой.
Если вам нужен какой-то код, это решение, которое сработало для меня (хотя оно на C #):
// ----------------------------
// Initialization
int BlitFrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, BlitFrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// create texture 0
int ColorTextureHandle0 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle0);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); // can use nearest for min and mag filter also
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
// for HRD use PixelInternalFormat.Rgba16f and PixelType.Float. Otherwise PixelInternalFormat.Rgba8 and PixelType.UnsignedByte
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, ColorTextureHandle0, 0);
// create texture 1
int ColorTextureHandle1 = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, ColorTextureHandle1);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba16f, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, TextureTarget.Texture2D, ColorTextureHandle1, 0);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
int FrameBufferHandle = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// need to setup this for 2 color attachments:
GL.DrawBuffers(2, new [] {DrawBuffersEnum.ColorAttachment0, DrawBuffersEnum.ColorAttachment1});
// render buffer 0
int RenderBufferHandle0 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, RenderbufferTarget.Renderbuffer, RenderBufferHandle0);
// render buffer 1
int RenderBufferHandle1 = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.Rgba16f, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment1, RenderbufferTarget.Renderbuffer, RenderBufferHandle1);
// depth render buffer
int DepthBufferHandle = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, DepthBufferHandle);
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, 8, RenderbufferStorage.DepthComponent24, Width, Height);
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, DepthBufferHandle);
// check FBO error
var error = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (error != FramebufferErrorCode.FramebufferComplete) {
throw new Exception($"OpenGL error: Framwbuffer status {error.ToString()}");
}
// unbind FBO
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
// ----------------------------
// Later for each frame
GL.BindFramebuffer(FramebufferTarget.Framebuffer, FrameBufferHandle);
// render scene ...
// blit data from FrameBufferHandle to BlitFrameBufferHandle
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, FrameBufferHandle);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, BlitFrameBufferHandle);
// blit color attachment0
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// blit color attachment1
GL.ReadBuffer(ReadBufferMode.ColorAttachment1);
GL.DrawBuffer(DrawBufferMode.ColorAttachment1);
GL.BlitFramebuffer(
0, 0, Width, Height,
0, 0, Width, Height,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest
);
// after that use textures ColorTextureHandle0 and ColorTextureHandle1 to render post effects using ping-pong framebuffers ...
Ответ №2:
Только что сам реализовал эффект цветения, столкнулся с теми же сглаженными краями на результирующем изображении и столкнулся с точно такими же проблемами. Поэтому делюсь своим опытом здесь.
Наложение псевдонимов происходит, когда вы визуализируете линии с помощью OpenGL — например, ребра треугольника или многоугольника, поскольку OpenGL рисует «диагональные» (или просто не прямые) линии на экране, используя довольно простые (но быстрые) алгоритмы.
При этом, если вы хотите сгладить что-то — это будет 3D-фигура, а не текстура — в конце концов, это просто обычное изображение.
Не по теме: чтобы исправить сглаживание на изображении, вы должны применить аналогичную технику, но вам нужно будет выяснить, где находятся «края» на изображении, а затем следовать тому же алгоритму для каждого пикселя «края». «Edge» (в кавычках), поскольку с точки зрения изображения это просто обычные пиксели, а ребро — это просто дополнительный контекст, который мы, люди, привязываем к этим пикселям.
Если убрать это с нашего пути, то использование двух вложений изображений на самом деле является хорошей оптимизацией — вам не нужно дважды отображать всю сцену в разных фреймбуферах. Но вы заплатите цену за копирование данных из каждого вложения фреймбуфера с несколькими выборками в отдельную текстуру без множественной выборки для последующей обработки.
Немного не по теме: с точки зрения производительности, я думаю, что это точно то же самое (или в пределах очень малого порога) — рендеринг всей сцены дважды в два отдельных фреймбуфера с двумя отдельными вложениями с несколькими выборками (в качестве входных данных для последующей обработки), а затем копирование каждого из них отдельно вдве отдельные текстуры без множественной выборки.
Итак, последний шаг, прежде чем вы сможете применить свою (любую) постобработку к сцене с несколькими выборками, — это преобразовать каждый результат рендеринга с несколькими выборками в текстуру без множественной выборки, чтобы ваши шейдеры работали с plain sampler2D
.
Это было бы что-то похожее на это:
glBindFramebuffer(GL_READ_FRAMEBUFFER, bloomFBOWith2MultisampledAttachments);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, temporaryFBOWith1NonMultisampledAttachment);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
// bloomFBOWith2MultisampledAttachments is still bound
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, blurFramebuffer1);
// THIS IS IMPORTANT
glReadBuffer(GL_COLOR_ATTACHMENT1);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glBlitFramebuffer(0, 0, windowWidth, windowHeight, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
Учитывая, что вы визуализируете свою сцену для двух вложений в одном фреймбуфере, вам нужно будет скопировать из каждого из этих вложений с несколькими выборками в текстуры без нескольких выборок и использовать их для аддитивного рендеринга и размытия, соответственно.
Если вы не возражаете против беспорядочного кода и использования globjects
абстракции API для OpenGL, вот все мое решение bloom со сглаживанием.
И несколько скриншотов:
На первом скриншоте для визуализации не используется фреймбуфер, поэтому линии действительно гладкие.
Второй скриншот — это первая реализация эффекта цветения (доступна как отдельный проект CMake).
Сглаживание более заметно на больших расстояниях, поэтому на третьих скриншотах сцена выглядит немного лучше — края выглядят действительно как ступени.
На последних двух скриншотах показан эффект цветения с применением сглаживания.
Обратите внимание, что у lantern есть только текстура с низким разрешением, отсюда и сглаженные линии, в то время как края бумаги сглажены сглаживанием.