#opengl #shadow-mapping
#opengl #отображение теней
Вопрос:
Я пытаюсь реализовать отображение дисперсионных теней для направленных теней в моем движке рендеринга с помощью OpenGL.
Я прочитал несколько статей, таких как — https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps , https://graphics.stanford.edu /~mdfisher/Shadows.html чтобы развить это.
Основной поток алгоритма заключается в следующем:
- Сохраните глубину и глубину ^ 2 в текстуре глубины.
- Примените двухпроходное размытие по Гауссу с ядром 5 x 5 и 10 проходами.
- Выберите значение глубины, вычислите расстояние фрагмента от источника света и
- Поместите их в неравенство Чебышева, чтобы определить максимальную вероятность нахождения фрагмента в тени
- Используйте результат, чтобы сделать фрагмент темным.
Вот мой шейдер глубины для направленного света с матрицей ортографической проекции:
#version 440 core
uniform float farPlane;
uniform vec3 lightPos;
uniform mat4 directional_light_space_matrix;
in vec4 FragPos;
out vec2 depth;
void main()
{
vec4 FragPosLightSpace = directional_light_space_matrix * FragPos;
float d = FragPosLightSpace.z / FragPosLightSpace.w;
d = d * 0.5 0.5;
float m1 = d;
float m2 = d * d;
float dx = dFdx(depth.x);
float dy = dFdx(depth.y);
m2 = 0.25 * (dx * dx dy * dy);
depth.r = m1;
depth.g = m2;
}
Вот фрагмент фрагментного шейдера, который проверяет, насколько освещен фрагмент.
float linstep(float mi, float ma, float v)
{
return clamp ((v - mi)/(ma - mi), 0, 1);
}
float ReduceLightBleeding(float p_max, float Amount)
{
return linstep(Amount, 1, p_max);
}
float chebyshevUpperBound(float dist, vec2 moments)
{
float p_max;
if(dist <= moments.x)
{
return 1.0;
}
float variance = moments.y - (moments.x * moments.x);
variance = max(variance, 0.1);
float d = moments.x - dist;
p_max = variance / (variance d * d);
return ReduceLightBleeding(p_max, 1.0);
}
float CheckDirectionalShadow(float bias, vec3 lightpos, vec3 FragPos)
{
vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
projCoords = projCoords * 0.5 0.5;
vec2 closest_depth = texture(shadow_depth_map_directional, projCoords.xy).rg;
return chebyshevUpperBound(projCoords.z, closest_depth);
}
Here’s the Two Pass Gaussian Blur shader.
#version 440 core
layout (location = 0) out vec2 out_1;
in vec2 TexCoords;
uniform sampler2D inputTexture_1;
uniform bool horizontal;
float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(inputTexture_1,0);
vec2 o1 = texture(inputTexture_1, TexCoords).rg * weights[0];
if(horizontal)
{
for(int i=1; i<4; i )
{
o1 = texture(inputTexture_1, TexCoords vec2(tex_offset.x * i, 0.0)).rg * weights[i];
o1 = texture(inputTexture_1, TexCoords - vec2(tex_offset.x * i, 0.0)).rg * weights[i];
}
}
else
{
for(int i=1; i<4; i )
{
o1 = texture(inputTexture_1, TexCoords vec2(0.0, tex_offset.y * i)).rg * weights[i];
o1 = texture(inputTexture_1, TexCoords - vec2(0.0, tex_offset.y * i)).rg * weights[i];
}
}
out_1 = o1;
}
Я добавляю свой код генерации фреймбуфера для получения информации о том, как я сохраняю моменты.
// directional ----------------------------------------------------------------------------------------------------------------------------------------------
glGenFramebuffers(1, amp;directional_shadow_framebuffer);
glGenTextures(1, amp;directional_shadow_framebuffer_depth_texture);
glBindTexture(GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, shadow_map_width, shadow_map_height, 0, GL_RG, GL_FLOAT, NULL);
float border_color[] = { 0.0f,0.0f,0.0f,1.0f };
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
glBindFramebuffer(GL_FRAMEBUFFER, directional_shadow_framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture, 0);
glGenRenderbuffers(1, amp;directional_shadow_framebuffer_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, shadow_map_width, shadow_map_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
LOGGER->log(ERROR, "Renderer : createShadowMapBuffer", "Directional Shadow Framebuffer is incomplete!");
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ----------------------------------------------------------------------------------------------------------------------------------------------
Результаты вышеуказанных операций далеки от ожиданий. Вместо мягких теней полутени я получаю резкие тени, похожие на капли.
Вот как выглядит первый момент (глубина), а второй момент практически такой же, но темнее.
Я пробовал экспериментировать с минимальной дисперсией, размером ядра тени, гауссовскими выборками, проходами размытия.. но я не приблизился к решению.
У меня такое чувство, что, возможно, я делаю что-то не так с тем, как я установил параметры фильтрации текстур в коде генерации фреймбуфера, приведенном выше.
Мои последние вопросы :
- Является ли моя реализация VSM неверной?
- Почему я не вижу мягких полутеней?
- У меня не очень хорошее представление о том, как фильтруется моя текстура, что-то не так в коде генерации фреймбуфера?
Ответ №1:
Итак, я решил проблему.
Реализация в полном порядке, но минимальная дисперсия и параметр количества уменьшения светового потока требуют настройки.
Я обнаружил, что уменьшение параметра минимальной дисперсии еще больше смягчит тени, но значительно увеличит выделение света. Чтобы противостоять этому побочному эффекту, мы можем настроить значение p_max на значение 0, когда оно ниже определенного порога, в противном случае изменить масштаб между 0 и 1. Это именно то, что делает функция ReduceLightBleeding, которая также описана на том же сайте, ссылка на который приведена выше. Но увеличение параметра amount в ReduceLightBleeding приведет к тому, что тени будут выглядеть как капли, что видно на скриншотах, которые я опубликовал выше.
Мне удалось настроить минимальную дисперсию и уменьшить количество незначительных кровотечений, чтобы найти оптимальное место. Однако я никогда не мог полностью избавиться от этого артефакта.
Лучшей альтернативой отображению теней дисперсии является его расширение — карты теней экспоненциальной дисперсии.
Я не совсем понимаю математику, но мне все же удалось довольно легко ее реализовать. Проверьте этот вопрос на gamedev.stackexchange для подсказок — EVSM.
ESVM проделал отличную работу, уменьшив кровотечение до такой степени, что его можно либо не заметить, либо просто проигнорировать.