Подтвердить что ты не робот

Указатели на современную OpenGL shadow cubemapping?

Фон

Я работаю над 3D-игрой, используя С++ и современный OpenGL (3.3). Теперь я работаю над рендерингом освещения и тени, и я успешно реализовал направленное теневое отображение. После прочтения требований к игре я решил, что мне нужно будет отображать точную светлую тень. Проведя некоторое исследование, я обнаружил, что для создания всенаправленного теневого отображения я сделаю нечто похожее на направленное теневое отображение, но вместо этого с помощью кубкет.

У меня нет прежних знаний о кубмапах, но мое понимание их состоит в том, что куб-карта состоит из шести текстур, плавно прикрепленных. Я немного оглянулся, но, к сожалению, я изо всех сил пытался найти окончательный "учебник" по этому вопросу для современного OpenGL. Сначала я ищу учебники, которые объясняют это от начала до конца, потому что я серьезно пытался научиться из фрагментов исходного кода или просто понятий, но я пробовал.

Текущее понимание

Вот мое общее понимание идеи, минус технические. Пожалуйста, поправьте меня.

  • Для каждого точечного света создается фреймбуфер, например, направленное теневое отображение
  • Затем генерируется одна структура куба файла и привязывается к glBindTexture(GL_TEXTURE_CUBE_MAP, shadowmap).
  • Файл cubemap настроен со следующими атрибутами:

    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    

(это также похоже на направленное теневое преобразование)

  • Теперь glTexImage2D() повторяется шесть раз, один раз для каждого лица. Я так делаю:

     for (int face = 0; face < 6; face++) // Fill each face of the shadow cubemap
         glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT32F , 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    
  • Текстура прикрепляется к фреймбуферу с вызовом

    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowmap, 0);
    
  • Когда сцена должна быть визуализирована, она визуализируется в два прохода, например, в виде направленного теневого отображения.

  • Прежде всего, теневой фреймбуфер привязан, окно просмотра настроено на размер теневой карты (в данном случае 1024 на 1024).
  • Culling устанавливается на передние грани с помощью glCullFace(GL_FRONT)
  • Активная шейдерная программа переключается на шейдеры вершин и фрагментов тени, которые я буду предоставлять источникам дальнейшего down
  • Рассчитываются матрицы просмотра света для всех шести видов. Я делаю это, создавая вектор glm:: mat4 и push_back() матриц, например:

    // Create the six view matrices for all six sides
    for (int i = 0; i < renderedObjects.size(); i++) // Iterate through all rendered objects
    {
        renderedObjects[i]->bindBuffers(); // Bind buffers for rendering with it
    
        glm::mat4 depthModelMatrix = renderedObjects[i]->getModelMatrix(); // Set up model matrix
    
        for (int i = 0; i < 6; i++) // Draw for each side of the light
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, shadowmap, 0);
            glClear(GL_DEPTH_BUFFER_BIT); // Clear depth buffer
    
            // Send MVP for shadow map
            glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrices[i] * depthModelMatrix;
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "depthMVP"), 1, GL_FALSE, glm::value_ptr(depthMVP));
    
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightViewMatrix"), 1, GL_FALSE, glm::value_ptr(depthViewMatrices[i]));
            glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightProjectionMatrix"), 1, GL_FALSE, glm::value_ptr(depthProjectionMatrix));
            glDrawElements(renderedObjects[i]->getDrawType(), renderedObjects[i]->getElementSize(), GL_UNSIGNED_INT, 0);
        }
    }
    
  • Фреймбуфер по умолчанию привязан, и сцена выполняется нормально.

Вопрос

Теперь, к шейдерам. Здесь мое понимание иссякает. Я совершенно не уверен в том, что мне следует делать, мои исследования, похоже, конфликтуют с каждым, потому что это для разных версий. Я закончил мягко копировать и вставлять код из случайных источников и надеяться, что он достигнет чего-то другого, кроме черного экрана. Я знаю, что это ужасно, но, похоже, нет четких определений того, что делать. В каких пространствах я работаю? Нужен ли мне отдельный теневой шейдер, как я использовал в направленном точечном освещении? Какого черта я использую в качестве типа теневого куба? samplerCube? samplerCubeShadow? Как я могу правильно выбрать указанный кубкет? Надеюсь, что кто-то сможет его очистить и дать хорошее объяснение. Мое настоящее понимание шейдерной части:  - Когда сцена отображается в cubemap, вершинный шейдер просто принимает формулу depthMVP, которую я вычисляю в своем коде на С++, и преобразует им входные вершины.  - Фрагментный шейдер прохода cubemap просто присваивает единственное значение значению gl_FragCoord.z. (Эта часть не изменилась после того, как я применил направленное теневое отображение. Я предположил, что это будет одинаково для cubemapping, потому что шейдеры даже не взаимодействуют с cubemap - OpenGL просто выводит из них вывод в куб файл, так? фреймбуфера?)

  • Вершинный шейдер для нормальной рендеринга не изменяется.
  • В шейдере фрагмента для нормального рендеринга положение вершины преобразуется в световое пространство с матрицей проекции света и просмотра.
  • Это как-то используется в поиске текстуры cubemap.???
  • Как только глубина была получена с помощью магических средств, ее сравнивают с расстоянием света до вершины, подобно тому, как направленное теневое изображение. Если это меньше, эта точка должна быть затенена и наоборот.

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

Shadow vertex shader:

#version 150

in vec3 position;

uniform mat4 depthMVP;

void main()
{
    gl_Position = depthMVP * vec4(position, 1);
}

Шейдер фрагмента тени:

#version 150

out float fragmentDepth;

void main()
{
    fragmentDepth = gl_FragCoord.z;
}

Стандартный вершинный шейдер:

#version 150

in vec3 position;
in vec3 normal;
in vec2 texcoord;

uniform mat3 modelInverseTranspose;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

out vec3 fragnormal;
out vec3 fragnormaldirection;
out vec2 fragtexcoord;
out vec4 fragposition;
out vec4 fragshadowcoord;

void main()
{
    fragposition = modelMatrix * vec4(position, 1.0);
    fragtexcoord = texcoord;
    fragnormaldirection = normalize(modelInverseTranspose * normal);
    fragnormal = normalize(normal);
    fragshadowcoord = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);


    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}

Стандартный шейдер фрагмента:

#version 150

out vec4 outColour;

in vec3 fragnormaldirection;
in vec2 fragtexcoord;
in vec3 fragnormal;
in vec4 fragposition;
in vec4 fragshadowcoord;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrixInversed;

uniform mat4 lightViewMatrix;
uniform mat4 lightProjectionMatrix;

uniform sampler2D tex;
uniform samplerCubeShadow shadowmap;

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)) // To avoid self shadowing, I guess
        return 1.0;

    return 0.7;
}

void main()
{
    vec3 light_position = vec3(0.0, 0.0, 0.0);
    vec3 VertToLightWS = light_position - fragposition.xyz;
    outColour = texture(tex, fragtexcoord) * ComputeShadowFactor(shadowmap, VertToLightWS);
}

Я не могу вспомнить, откуда пришел код функции ComputerShadowFactor и VectorToDepthValue, потому что я изучал его на своем ноутбуке, который я не могу получить прямо сейчас, но это результат этих шейдеров:

Result from these shaders

Это маленький квадрат незаметного пространства, окруженного затененным пространством.

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

4b9b3361

Ответ 1

Надеюсь дать ответ на некоторые ваши вопросы, но сначала потребуются некоторые определения:

Что такое файл куба?

Это отображение от вектора направления к паре [face, 2d координат на этой грани], полученное проецированием вектора направления на гипотетический куб.

Что такое текстура куба OpenGL?

Это набор из шести "изображений".

Что такое сэмплер кэширования GLSL?

Это примитив сэмплера, из которого можно выполнить выборку cubemap. Это означает, что он отбирается с использованием вектора направления вместо обычных координат текстуры. Затем оборудование проецирует вектор направления на гипотетический куб и использует полученную пару [face, 2d texture coord] для выбора правильного "изображения" в правой 2-й позиции.

Что такое сэмплер теней GLSL?

Это примитив сэмплера, который ограничен текстурой, содержащей значения глубины в пространстве NDC, и, когда они были выбраны с использованием функций выборочной выборки, возвращает "сравнение" между глубиной пространства NDC (в том же пространстве теневая карта, очевидно) и глубину пространства NDC, хранящуюся внутри ограниченной текстуры. Глубина для сравнения определяется как дополнительный элемент в координатах текстуры при вызове функции выборки. Обратите внимание, что теневые пробоотборники предусмотрены для удобства использования и скорости, но всегда можно выполнить сравнение "вручную" в шейдере.


Теперь, для ваших вопросов:

OpenGL просто отображает [...] в куб файл, не так ли?

Нет, OpenGL визуализирует набор целей в текущем ограниченном фреймбуфере.

В случае кубов, обычный способ визуализации в них:

  • чтобы создать их и прикрепить каждое из своих шести "изображений" к тому же framebuffer (в разных точках подключения, очевидно)
  • чтобы включить только один из объектов за раз (так, вы визуализируете каждую грань куба-карты отдельно)
  • для рендеринга того, что вы хотите на лицевой панели cubemap (возможно, с использованием граничных представлений, ориентированных на лицо) и матриц проекции.

Карты теневой тени

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

Вместо этого, то, что является общим пратисом, следующее:

  • вместо записи глубины в пространстве NDC, пишите радиальное расстояние от точечный свет
  • при запросе теневой карты (см. пример кода внизу):
    • не используйте сравнения аппаратной глубины (используйте samplerCube вместо samplerCubeShadow)
    • преобразует точку, подлежащую тестированию, в "пространство куба" (которая вообще не включает проекцию)
    • используйте вектор "кубический пробел" в качестве направления поиска для образца cubemap
    • сравните радиальное расстояние, отобранное из карты cub с радиальным расстоянием тестируемой точки.

Пример кода

// sample radial distance from the cubemap
float radial_dist = texture(my_cubemap, cube_space_vector).x;

// compare against test point radial distance
bool shadowed = length(cube_space_vector) > radial_dist;