Всенаправленное отображение теней с глубиной куба

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

Теперь я хотел бы, если это возможно, изменить мою реализацию следующим образом:

  • 1) прикрепить текстуру глубинного куба к буферу глубины моих кадровых буферов вместо цветов.
  • 2) рендеринг только глубины, не пишите цвет в этом проходе
  • 3) в основном проходе считайте глубину из текстуры кубической карты, преобразуйте ее в расстояние и проверьте, перекрыт ли текущий фрагмент светом или нет.

Моя проблема возникает при преобразовании значения глубины из кубической карты в расстояние. Я использую вектор света для фрагмента (в мировом пространстве), чтобы получить значение глубины в кубической карте. На данный момент я не знаю, какая из шести граней используется, и какие координаты 2D-текстуры соответствуют значению глубины, которое я читаю. Тогда как я могу преобразовать это значение глубины в расстояние?

Вот фрагменты моего кода для иллюстрации:

Глубина текстуры:

glGenTextures(1, &TextureHandle);
glBindTexture(GL_TEXTURE_CUBE_MAP, TextureHandle);
for (int i = 0; i < 6; ++i)
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,
              Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Конструкция каркасных буферов:

for (int i = 0; i < 6; ++i)
{
    glGenFramebuffers(1, &FBO->FrameBufferID);
    glBindFramebuffer(GL_FRAMEBUFFER, FBO->FrameBufferID);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
            GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, TextureHandle, 0);
    glDrawBuffer(GL_NONE);
}

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

float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
{   
    float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
    ShadowVec = DepthValueToDistance(ShadowVec);
    if (ShadowVec * ShadowVec > dot(VertToLightWS, VertToLightWS))
        return 1.0;

    return 0.0;
}

Функция DepthValueToDistance - моя настоящая проблема.

2 ответа

Решение

Таким образом, решение состояло в том, чтобы преобразовать вектор света в фрагмент в значение глубины вместо преобразования глубины, считанной из кубической карты, в расстояние.

Вот модифицированный код шейдера:

float VectorToDepthValue(vec3 Vec)
{
    vec3 AbsVec = abs(Vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    const float f = 2048.0;
    const float n = 1.0;
    float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
    return (NormZComp + 1.0) * 0.5;
}

float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
{   
    float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
    if (ShadowVec + 0.0001 > VectorToDepthValue(VertToLightWS))
        return 1.0;

    return 0.0;
}

Объяснение по VectorToDepthValue(vec3 Vec):

LocalZComp соответствует тому, что было бы Z-компонентой данного Vec в соответствующий усечке кубической карты. Это на самом деле самый большой компонент Vec (например, если Vec.y является самым большим компонентом, мы посмотрим либо на Y+, либо на Y-грань кубической карты).

Если вы посмотрите на эту статью в Википедии, вы поймете математику сразу после (я сохранил ее в формальной форме для понимания), которая просто преобразует LocalZComp в нормализованное значение Z (между в [-1..1]), а затем сопоставить его с [0..1], который является фактическим диапазоном для значений буфера глубины. (при условии, что вы не изменили это). n а также f являются ближними и дальними значениями усечений, используемых для создания кубической карты.

ComputeShadowFactor затем просто сравните значение глубины из кубической карты со значением глубины, вычисленным из вектора фрагмента к свету (с именем VertToLightWS здесь), также добавьте небольшое смещение по глубине (которое отсутствовало в вопросе) и возвращает 1, если фрагмент не перекрыт светом.

Я хотел бы добавить больше деталей относительно деривации.

Пусть V будет направлением света на фрагмент.

Как уже говорил Бенлитц, значение Z в соответствующем усечении /"глазном пространстве" на стороне куба можно рассчитать, взяв максимум абсолютных значений компонентов V.

Z = max(abs(V.x),abs(V.y),abs(V.z))

Тогда, если быть точным, мы должны отрицать Z, потому что в OpenGL отрицательная ось Z указывает на усадку экрана / вида.

Теперь мы хотим получить "совместимое" значение буфера глубины этого -Z.

Глядя на матрицу перспектив OpenGL...

http://www.songho.ca/opengl/files/gl_projectionmatrix_eq16.png

(резервная ссылка)

... мы видим, что для любого однородного вектора, умноженного на эту матрицу, результирующее значение z полностью не зависит от компонент x и y вектора.

Таким образом, мы можем просто умножить эту матрицу на однородный вектор (0,0,-Z, 1) и получить вектор (компоненты):

x = 0
y = 0
z = (-Z * -(f+n) / (f-n)) + (-2*f*n / (f-n))
w = Z

Затем нам нужно сделать перспективное деление, поэтому мы делим z на w (Z), что дает нам:

z' = (f+n) / (f-n) - 2*f*n / (Z* (f-n))

Это z'находится в диапазоне нормализованных координат устройства (NDC) OpenGL [-1,1] и должно быть преобразовано в совместимый с буфером глубины диапазон [0,1]:

z_depth_buffer_compatible = (z' + 1.0) * 0.5

Дальнейшие заметки:

  • Возможно, имеет смысл выгрузить результаты (f+n), (fn) и (f*n) в виде шейдерных форм для сохранения вычислений.

  • V должен находиться в мировом пространстве, поскольку карта теневого куба обычно выровнена по оси в мировом пространстве, таким образом, часть "max (abs (Vx),abs(Vy),abs(Vz))" работает, только если V является мировым пространством вектор направления

Другие вопросы по тегам