OpenGL OGLDev SSAO Tutorial Реализация фрагментного шейдера приводит к шуму

ПРЕДПОСЫЛКИ ЗАДАЧИ

Я пытаюсь реализовать SSAO после Учебник по OGLDev 45, основанный на Учебник Джона Чепмена. В учебнике OGLDev используется очень упрощенный метод, который выбирает случайные точки в радиусе вокруг положения фрагмента и увеличивает коэффициент АО в зависимости от того, сколько точек выборки имеют глубину, превышающую фактическую глубину поверхности, хранящуюся в этом месте (чем больше позиций вокруг фрагмента лежат перед ним тем больше окклюзия).

Используемый мной «движок» не имеет такого модульного отложенного затенения, как OGLDev, но в основном он сначала визуализирует все цвета экрана в фреймбуфер с вложением текстуры и вложением буфера рендеринга глубины. Чтобы сравнить глубины, позиции пространства просмотра фрагмента рендерятся в другой буфер кадра с прикрепленной текстурой. Затем эти текстуры подвергаются постобработке шейдером SSAO, и результат рисуется в четырехугольнике заполнения экрана. Обе текстуры сами по себе отлично отрисовываются в квадроцикле, и формы ввода шейдера, похоже, тоже в порядке, поэтому я не включил код движка.

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

#version 330 core

in vec2 texCoord;
layout(location = 0) out vec4 outColor;

const int RANDOM_VECTOR_ARRAY_MAX_SIZE = 128; // reference uses 64
const float SAMPLE_RADIUS = 1.5f; // TODO: play with this value, reference uses 1.5

uniform sampler2D screenColorTexture; // the whole rendered screen
uniform sampler2D viewPosTexture; // interpolated vertex positions in view space

uniform mat4 projMat;

// we use a uniform buffer object for better performance
layout (std140) uniform RandomVectors
{
    vec3 randomVectors[RANDOM_VECTOR_ARRAY_MAX_SIZE];
};

void main()
{
    vec4 screenColor = texture(screenColorTexture, texCoord).rgba;
    vec3 viewPos = texture(viewPosTexture, texCoord).xyz;

    float AO = 0.0;

    // sample random points to compare depths around the view space position.
    // the more sampled points lie in front of the actual depth at the sampled position,
    // the higher the probability of the surface point to be occluded.
    for (int i = 0; i < RANDOM_VECTOR_ARRAY_MAX_SIZE; ++i) {

        // take a random sample point.
        vec3 samplePos = viewPos + randomVectors[i];

        // project sample point onto near clipping plane
        // to find the depth value (i.e. actual surface geometry)
        // at the given view space position for which to compare depth
        vec4 offset = vec4(samplePos, 1.0);
        offset = projMat * offset; // project onto near clipping plane
        offset.xy /= offset.w; // perform perspective divide
        offset.xy = offset.xy * 0.5 + vec2(0.5); // transform to [0,1] range
        float sampleActualSurfaceDepth = texture(viewPosTexture, offset.xy).z;

        // compare depth of random sampled point to actual depth at sampled xy position:
        // the function step(edge, value) returns 1 if value > edge, else 0
        // thus if the random sampled point's depth is greater (lies behind) of the actual surface depth at that point,
        // the probability of occlusion increases.
        // note: if the actual depth at the sampled position is too far off from the depth at the fragment position,
        // i.e. the surface has a sharp ridge/crevice, it doesnt add to the occlusion, to avoid artifacts.
        if (abs(viewPos.z - sampleActualSurfaceDepth) < SAMPLE_RADIUS) {
            AO += step(sampleActualSurfaceDepth, samplePos.z);
        }
    }

    // normalize the ratio of sampled points lying behind the surface to a probability in [0,1]
    // the occlusion factor should make the color darker, not lighter, so we invert it.
    AO = 1.0 - AO / float(RANDOM_VECTOR_ARRAY_MAX_SIZE);

    ///
    outColor = screenColor + mix(vec4(0.2), vec4(pow(AO, 2.0)), 1.0);
    /*/
    outColor = vec4(viewPos, 1); // DEBUG: draw view space positions
    //*/
}

ЧТО РАБОТАЕТ?

  • Текстура цвета фрагмента правильная.
  • Координаты текстуры соответствуют квадрату заполнения экрана, который мы рисуем, и преобразуются в [0, 1]. Они дают эквивалентные результаты как vec2 texCoord = gl_FragCoord.xy / textureSize(screenColorTexture, 0);
  • Матрица (перспективной) проекции — это та, которую использует камера, и она работает для этой цели. В любом случае, похоже, это не проблема.
  • Компоненты вектора случайной выборки находятся в диапазоне [-1, 1], как и предполагалось.
  • Текстура позиций пространства просмотра фрагмента выглядит нормально:

позиции фрагмента просмотра пространства

ЧТО СЛУЧИЛОСЬ?

Когда я устанавливаю коэффициент смешивания AO в нижней части фрагментного шейдера на 0, он плавно работает до ограничения fps (хотя вычисления все еще выполняются, по крайней мере, я предполагаю, что компилятор не оптимизирует это: D). Но когда AO смешивается, отрисовка кадра занимает до 80 мс (со временем становится все медленнее, как будто буферы заполняются), и результат действительно интересный и запутанный:

проблема с шумом ssao

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


person Nikole    schedule 21.05.2015    source источник
comment
Я полагаю, что этот вопрос слишком нишев, чтобы кто-либо мог ответить без простого «рабочего» примера и практической отладки. По моему опыту, перелистывание «изображений» происходит из-за того, что текстуры не связаны или используются случайные данные при загрузке текстуры. У меня всегда есть процедура, которая рисует на экране отдельные буферы (такие как буфер глубины или карта нормалей), что помогает мне визуально определить, все ли в порядке. Еще одна вещь, которую нужно сделать, — это сравнить значения с тем, что вы ожидаете, и выдать красный пиксель при ошибке и зеленый при прохождении. Также ваш компилятор, вероятно, очень строг с удалением неиспользуемого кода.   -  person Gerard    schedule 22.05.2015
comment
Спасибо за комментарий! как уже упоминалось, две используемые входные текстуры отлично работают при индивидуальном рендеринге. так что проблема не в этом, к сожалению... насчет компилятора: ему пришлось бы провести очень сложный лексический анализ, чтобы оптимизировать код, о котором я говорил выше, чтобы определить, что ничего не рисуется, когда коэффициент функции микширования равен 0.   -  person Nikole    schedule 22.05.2015
comment
пожалуйста, укажите причину отрицательного ответа на вопрос. критика полезна!   -  person Nikole    schedule 23.05.2015


Ответы (1)


Проблема оказалась связанной с выбранными типами текстур.

Текстура с дескриптором viewPosTexture должна быть явно определена как формат плавающей текстуры GL_RGB16F или GL_RGBA32F, а не просто GL_RGB. Что интересно, отдельные текстуры прорисовывались нормально, проблемы возникали только при комбинировании.

// generate screen color texture
// note: GL_NEAREST interpolation is ok since there is no subpixel sampling anyway
glGenTextures(1, &screenColorTexture);
glBindTexture(GL_TEXTURE_2D, screenColorTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, windowWidth, windowHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL);

// generate depth renderbuffer. without this, depth testing wont work.
// we use a renderbuffer since we wont have to sample this, opengl uses it directly.
glGenRenderbuffers(1, &screenDepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, screenDepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, windowWidth, windowHeight);

// generate vertex view space position texture
glGenTextures(1, &viewPosTexture);
glBindTexture(GL_TEXTURE_2D, viewPosTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, windowWidth, windowHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);

Медленное рисование может быть вызвано функцией GLSL mix. Будет исследовать дальше на этом.

Мерцание было связано с регенерацией и прохождением новых случайных векторов в каждом кадре. Простое прохождение достаточного количества случайных векторов решает проблему. В противном случае это может помочь размыть результат SSAO.

По сути, SSAO теперь работает! Теперь это просто более или менее явные баги.

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

person Nikole    schedule 23.05.2015