Фон
Я работаю над 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, потому что я изучал его на своем ноутбуке, который я не могу получить прямо сейчас, но это результат этих шейдеров:
Это маленький квадрат незаметного пространства, окруженного затененным пространством.
Я, очевидно, делаю здесь много ошибок, вероятно, сосредоточен на моих шейдерах из-за недостатка знаний по этому предмету, потому что мне трудно учиться чему-либо, кроме учебников, и мне очень жаль это. Я в недоумении, было бы замечательно, если бы кто-то мог пролить свет на это с ясным объяснением того, что я делаю неправильно, почему это неправильно, как я могу это исправить и, может быть, даже какой-то код. Я думаю, что проблема может быть в том, что я работаю в неправильных пространствах.