Почему в opengl gl_FragCoord.z ​​отличается от буфера глубины?

Некоторое время я пытался найти способ прочитать значение глубины для конкретной координаты мыши (x, y). Все отлично работает на win10 с opengl 4.x, но не на opengl es 3.x

Мои подходы:

  1. glReadPixels() не работает с openGL для буфера глубины
  2. ray cast не подходит, так как работаю с большой моделью местности
  3. следующий метод был бы достаточным, но, к сожалению, слишком неточным, также на win10, но почему?

    #version 420
    uniform vec2 screenXy;
    uniform vec2 screenSize;
    
    out vec4 fragColor;
    
    void main(void) {
    
    if((int(gl_FragCoord.x) == int(screenXy.x)) && ((int(screenSize.y) - int(gl_FragCoord.y)) == int(screenXy.y))) {
        fragColor.r = gl_FragCoord.z;
        } else {
            fragColor = vec4(1, 1, 1, 1.0);
        }
    }
    

Я передаю координаты xy мыши фрагментному шейдеру (screenXy). Если щелкнутый пиксель находится в строке, я записываю значение глубины в цветовой буфер. Это работает, но значение gl_FragCoord.z ​​и значение из буфера глубины не совсем совпадают (я знаю, что это значение из буфера глубины правильное). Хотя gl_FragCoord.z ​​и значение буфера глубины плавающее, а так я думаю 32bit.

GLfloat zd; // from depth buffer
GLfloat zc[4]; // from color buffer
m_func->glReadPixels(xy.x(), m_pFbo->height() - xy.y(), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zd);
m_func->glReadPixels(xy.x(), m_pFbo->height() - xy.y(), 1, 1, GL_RGBA, GL_FLOAT, zc);

Причины:

  1. отклонение происходит через внутреннее преобразование типов, но где?
  2. потому что GL_DEPTH_TEST выполняется после того, как фрагментшейдер gl_FragCoord.z ​​не ближайший (к камере), а сохранен в буфере глубины. Таким образом, также не имеет смысла сохранять gl_FragCoord.z ​​в отдельном Frambuffer, потому что это неправильное значение.

Может быть, кто-нибудь поможет мне и разгадать узел, потому что я не могу найти другого объяснения?

Вот некоторые измеренные значения:

zc  0.984314
zd  0.985363

zc  0.552941
zd  0.554653

zc  1 -> extremly critical
zd  0.999181

person PowerNow    schedule 26.04.2018    source источник


Ответы (3)


Поскольку 0,984314 * 255,0 — это точно 251,0, я предполагаю, что внутренний формат цветовой плоскости — GL_RGBA8. Это означает, что на каждый цветовой канал приходится 1 байт, а zc может иметь только 256 различных значений, от 0,0 до 1,0 с шагом 1/256.

Если это поддерживается версией OpenGL ES, которую вы используете, то вы можете изменить формат хранения буфера рендеринга (например, GL_R32F - только канал красного цвета, но 32-битная плавающая точка).

Или вы можете закодировать глубину в 4 канала 4 * 8-битной цветовой плоскости:

vec4 PackDepth( in float depth )
{
    depth *= (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
    vec4 encode = fract( depth * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return vec4( encode.xyz - encode.yzw / 256.0, encode.w ) + 1.0/512.0;
}

....

fragColor = PackDepth(gl_FragCoord.z);

И вы можете расшифровать его после чтения значения:

GLfloat zc[4]; // from color buffer
m_func->glReadPixels(xy.x(), m_pFbo->height() - xy.y(), 1, 1, GL_RGBA, GL_FLOAT, zc);

float depth = zc[0] + zc[1]/256.0 + zc[2]/(256.0*256.0) + zc[3]/(256.0*256.0*256.0);
depth = depth * (255.0f/256.0f) * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
person Rabbid76    schedule 26.04.2018
comment
Просто чтобы ответить на комментарий, поддерживается ли он - буферы кадра с плавающей запятой являются только основными функциями только в OpenGL ES 3.2, но доступны через расширение на некоторых более ранних графических процессорах OpenGL ES 3.0. - person solidpixel; 26.04.2018
comment
@Rabbid76: До сих пор я пробовал 4 разных алгоритма decodeFloat32ToRgba8, к сожалению, все они с потерями. Единственный, который точен для небольших значений, это aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final, но если z близко к 1, то это тоже не работает. И GL_R32F не существует, только GL_RED (тоже opengl 4.x) и то только 8-битный. - person PowerNow; 27.04.2018
comment
К сожалению, недостаточно точно. Самый лучший по ссылке выше. Но результат не совсем понятен. Это работает на 80% точно независимо от размера значения. Потом вдруг какой-то перелив. Вот некоторые результаты: zc 0,485301 zd 0,485301 zc 0,999121 zd 0,999121 zc 1,00394 zd 0,998262 Всегда, если одно из значений rgba достигает 1. например. для последнего значения rgba = (1, 1, 1, 0). - person PowerNow; 27.04.2018

Я до сих пор не знаю точно, совпадает ли gl_FragCoord.z ​​с буфером глубины после теста глубины. С помощью счетчика вы можете сделать запрос

if(gl_FragCoord.z_last > gl_FragCoord.z)
    fragColor.z = gl_FragCoord.z;

так что в основном то, что делает тест глубины. но как сохранить gl_FragCoord.z_last как статический? Есть ли способ без потерь прочитать значение глубины в openGL 3.x? Судя по солидпикселю, плавающий фреймбуфер есть только в opengl 3.2.

person PowerNow    schedule 27.04.2018

Похоже, что значение в буфере глубины не совсем совпадает с gl_FragCoord.z. Я думаю, что следующий пример демонстрирует это.

#version 420

uniform vec4 color;
uniform vec2 screenXy;
uniform vec2 screenSize;

in vec2 vBC;

out vec4 fragColor;

void main(void) {

fragColor.r = gl_FragCoord.z;

}

C++

GLfloat zd;
GLubyte zc[4];
m_func->glReadPixels(xy.x(), m_pFbo->height() - xy.y(), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &zd);
m_func->glReadPixels(xy.x(), m_pFbo->height() - xy.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, zc);

Данные: zc0 = 255 (из gl_FragCoord.z); zd = 0,996561 (из буфера глубины)

Но должно быть 254, потому что 0,996561 * 255 = 254,123055.

person PowerNow    schedule 27.04.2018