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

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

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

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

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

    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).
  • Отбор устанавливается на передние грани с glCullFace(GL_FRONT)
  • Активная шейдерная программа переключается на вершинные и фрагментные теневые шейдеры, которые я предоставлю в дальнейшем источниках
  • Матрицы освещения для всех шести видов рассчитываются. Я делаю это, создавая вектор glm :: mat4’s и 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? Как правильно выбрать образец кубической карты? Я надеюсь, что кто-то может прояснить это для меня и дать хорошее объяснение.
Мое текущее понимание шейдерной части:
— Когда сцена визуализируется в кубическую карту, вершинный шейдер просто берет унифицированную глубину MVP, которую я рассчитал в своем коде C ++, и преобразует входные вершины по ним.
— Фрагментный шейдер прохода кубической карты просто назначает единственное значение для gl_FragCoord.z, (Эта часть не изменилась по сравнению с тем моментом, когда я реализовал направленное отображение теней. Я предполагал, что это будет то же самое для кубического отображения, потому что шейдеры даже не взаимодействуют с кубической картой — OpenGL просто выводит выходные данные из них в кубическую карту, верно? Потому что это кадровый буфер?)

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

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

#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, потому что я исследовал его на своем ноутбуке, к которому сейчас не могу добраться, но это результат этих шейдеров:

Результат от этих шейдеров

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

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

18

Решение

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

Что такое кубическая карта?

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

Что такое текстура кубической карты OpenGL?

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

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

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

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

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


Теперь по вашим вопросам:

OpenGL просто рендерит […] кубическую карту, верно?

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

В случае кубических карт обычный способ рендеринга в них:

  • создать их и прикрепить каждое из их шести «изображений» к одному
    кадровый буфер (в разных точках крепления, очевидно)
  • включить только одну цель одновременно (Итак, вы визуализируете в каждой грани куба индивидуально)
  • визуализировать то, что вы хотите в лице кубической карты (возможно использование специфичных для лица матриц вида и проекции)

Точечные карты света

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

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

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

Образец кода

// 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;
11

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]