Указатели на современный OpenGL shadow cubemapping?
фон
я работаю над 3D-игрой, используя C++ и современный OpenGL (3.3). Теперь я работаю над рендерингом освещения и тени, и я успешно реализовал направленное теневое отображение. После прочтения требований к игре я решил, что мне понадобится точечное световое теневое отображение. После некоторых исследований я обнаружил, что для всенаправленного теневого отображения я сделаю что-то похожее на направленное теневое отображение, но с помощью cubemap вместо.
у меня нет предыдущих знаний о cubemaps, но мое понимание их заключается в том, что cubemap-это шесть текстур, легко прикрепленных. Я немного осмотрелся, но, к сожалению, я изо всех сил пытался найти окончательный "учебник" по этому вопросу для современного OpenGL. Сначала я ищу учебники, которые объясняют это от начала до конца, потому что я серьезно пытался учиться на фрагментах исходного кода или просто концепциях, но я попытался.
текущая понимание
вот мое общее понимание идеи, без тонкостей. Пожалуйста, поправьте меня.
- для каждого точечного света настраивается фреймбуффер, например, направленная shadowmapping
- затем создается одна текстура cubemap и связывается с
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);
(это также похоже на курсовую shadowmapping)
-
теперь
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);
когда сцена должна быть отображена, она отображается в два прохода, как направленное теневое отображение.
- прежде всего, теневой фреймбуфер привязан, видовое окно настроено на размер shadowmap (1024 на 1024 в этом случае).
- отбраковка устанавливается на передние грани с
glCullFace(GL_FRONT)
- активная программа шейдеров переключается на вершинные и фрагментные теневые шейдеры, которые я предоставлю источниками далее вниз
-
вычисляются матрицы вида света для всех шести видов. Я делаю это, создавая вектор 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); } }
по умолчанию framebuffer связан, и сцена рисуется нормально.
вопрос
теперь к шейдерам. Здесь мое понимание иссякает. Я совершенно не уверен, что мне делать, мои исследования, похоже, конфликтуют друг с другом, потому что это для разных версий. Я закончил тем, что мягко копировал и вставлял код из случайных источников и надеялся, что он достигнет чего-то другого, кроме черного экрана. Я знаю, это ужасно, но, кажется, нет никакой ясности. определения того, что делать. В каких помещениях я работаю? Нужен ли мне отдельный теневой шейдер, как я использовал в направленном точечном освещении? Что, черт возьми, я использую в качестве типа для теневой cubemap? samplerCube? сэмплеркубешэдоу? Как правильно выбрать cubemap? Я надеюсь, что кто-то может прояснить это для меня и дать хорошее объяснение.
Мое нынешнее понимание части shader:
- Когда сцена визуализируется в cubemap, вершинный шейдер просто принимает форму depthMVP I вычисляется в моем коде C++ и преобразует входные вершины по ним.
- Шейдер фрагментов прохода cubemap просто присваивает значение single out gl_FragCoord.z
. (Эта часть не изменилась с тех пор, как я реализовал направленное теневое отображение. Я предположил, что это будет то же самое для cubemapping, потому что шейдеры даже не взаимодействуют с cubemap - OpenGL просто отображает вывод из них в cubemap, верно? Потому что это фреймбуфер?)
- вершинный шейдер для нормальный рендеринг не изменяется.
- в шейдере фрагментов для нормального рендеринга положение вершины преобразуется в пространство света с матрицей проекции и вида света.
- это как-то используется в поиске текстуры cubemap. ???
- после того, как глубина была извлечена с помощью магических средств, она сравнивается с расстоянием света до вершины, так же, как направленное теневое отображение. Если меньше, то эта точка должна быть затемнена, и наоборот.
это не очень понимание. Я не понимаю, как вершины преобразуются и используются для поиска cubemap, поэтому я собираюсь вставить источник для моих шейдеров, в надежде, что люди смогут это прояснить. Обратите внимание, что многие из этого кода слепое копирование и вставка, я ничего не изменил, чтобы не поставить под угрозу любое понимание.
теневой вершинный шейдер:
#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, потому что я исследовал его на своем ноутбуке, до которого я не могу добраться прямо сейчас, но это результат этих шейдеров:
это небольшой квадрат незатененного пространства, окруженный затененным пространством.
я очевидно, что здесь много неправильно, вероятно, сосредоточено на моих шейдерах, из-за отсутствия знаний по этому вопросу, потому что мне трудно учиться чему-либо, кроме учебников, и я очень сожалею об этом. Было бы замечательно, если бы кто-то мог пролить свет на это с четким объяснением того, что я делаю неправильно, почему это неправильно, как я могу это исправить и, возможно, даже какой-то код. Я думаю, что проблема может быть в том, что я работаю в неправильных пространствах.
1 ответов
Я надеюсь дать ответ на некоторые из ваших вопросов, но сначала необходимы некоторые определения:
что такое кубическая карта?
Это карта от вектора направления к паре [граней, 2d координат на этой грани], полученная путем проецирования вектора направления на гипотетический куб.
что такое текстура OpenGL cubemap?
Это набор из шести "образы".
что такое GLSL cubemap сэмплер?
это примитив сэмплера, из которого можно сделать выборку cubemap. Это означает, что он отбирается с использованием вектора направления вместо обычных текстурных координат. Затем аппаратное обеспечение проецирует вектор направления на гипотетический куб и использует полученную пару [face, 2d texture coordinate] для выборки правильного "изображения" в правой 2d-позиции.
что такое GLSL теневой сэмплер?
это примитив сэмплера, который ограниченная текстурой, содержащей значения глубины NDC-пространства, и при выборке с использованием теневых функций выборки возвращает "сравнение" между глубиной NDC-пространства (в том же пространстве теневой карты, очевидно) и глубиной NDC-пространства, хранящейся внутри ограниченной текстуры. Глубина для сравнения задается как дополнительный элемент в координатах текстуры при вызове функции выборки. Обратите внимание, что теневые пробоотборники предусмотрены для удобства использования и скорости, но это всегда возможно чтобы выполнить сравнение "вручную" в шейдере.
теперь вопросы:
OpenGL просто отображает [...] к кубической карте, верно?
нет, OpenGL отображает набор целей в текущем ограниченном фреймбуффере.
в случае cubemaps обычный способ визуализации в них:
- чтобы создать их и прикрепить каждый из их шести "изображений" к тому же framebuffer (на различном приложении очки, очевидно,)
- включить только одну цель за раз (Итак, вы визуализируете каждую грань cubemap индивидуально)
- чтобы отобразить то, что вы хотите на грани cubemap (возможно использование граневых матриц" вид "и" проекция")
точка-свет теневые карты
в дополнение ко всему сказанному о cubemaps, есть ряд проблем в использовании их для реализации точечно-световое теневое отображение и поэтому сравнение глубины оборудования редко используется.
вместо этого, что является общим pratice является следующее:
- вместо писать глубину NDC-космоса, напишите радиальное расстояние от точка света
- при запросе теневой карты (см. пример кода внизу):
- не используйте аппаратные сравнения глубины (используйте samplerCube вместо samplerCubeShadow)
- преобразовать точку протестировано в "пространстве куба" (которые вообще не включают проекцию)
- используйте вектор "куб-пространство" в качестве направления поиска для выборки cubemap
- сравните радиальное расстояние, выбранное из кубической карты, с радиальным расстоянием тестируемой точки
пример кода
// 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;