Карты Cascaded Shadow не совсем подходят

Ok. Итак, я всю последнюю неделю возился с тенями в своем игровом движке. Я в основном реализовал каскадные карты теней (CSM), но у меня возникла небольшая проблема с затенением, которую я просто не могу решить.

Единственный источник света в этой сцене - это направленный свет (солнце), указывающий на {-0,1 -0,25 -0,65}. Я вычисляю 4 набора границ усеченной пирамиды для четырех разбиений моих CSM с помощью этого кода:

// each projection matrix calculated with same near plane, different far
Frustum make_worldFrustum(const glm::mat4& _invProjView) {
    Frustum fr; glm::vec4 temp;
    temp = _invProjView * glm::vec4(-1, -1, -1, 1);
    fr.xyz = glm::vec3(temp) / temp.w;
    temp = _invProjView * glm::vec4(-1, -1,  1, 1);
    fr.xyZ = glm::vec3(temp) / temp.w;
    ...etc 6 more times for ndc cube
    return fr;
}

Для света я получаю такую ​​матрицу вида:

glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});

Затем я создаю каждую орто-матрицу из границ каждой пирамиды:

lightMatVec.clear();
for (auto& frus : cam.frusVec) {
    glm::vec3 arr[8] {
        glm::vec3(viewMat * glm::vec4(frus.xyz, 1)),
        glm::vec3(viewMat * glm::vec4(frus.xyZ, 1)),
        etc...
    };

    glm::vec3 minO = {INFINITY, INFINITY, INFINITY};
    glm::vec3 maxO = {-INFINITY, -INFINITY, -INFINITY};
    for (auto& vec : arr) {
        minO = glm::min(minO, vec);
        maxO = glm::max(maxO, vec);
    }

    glm::mat4 projMat = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, minO.z, maxO.z);
    lightMatVec.push_back(projMat * viewMat);
}

У меня есть 4 слоя TEXTURE_2D_ARRAY, привязанные к 4 буферам кадра, в которые я рисую сцену с помощью очень простого вершинного шейдера (фрагмент отключен или сквозной альфа-канал).

Затем я рисую финальную сцену. Вершинный шейдер выводит четыре текстовых координаты тени:

out vec3 slShadcrd[4];
// stuff
for (int i = 0; i < 4; i++) {
    vec4 sc = WorldBlock.skylMatArr[i] * vec4(world_pos, 1);
    slShadcrd[i] = sc.xyz / sc.w * 0.5f + 0.5f;
}

И фрагментный шейдер, который определяет разбиение для использования с:

int csmIndex = 0;
for (uint i = 0u; i < CameraBlock.csmCnt; i++) {
    if (-view_pos.z > CameraBlock.csmSplits[i]) index++;
    else break;
}

И сэмплируем массив карты теней с помощью этой функции:

float sample_shadow(vec3 _sc, int _csmIndex, sampler2DArrayShadow _tex) {
    return texture(_tex, vec4(_sc.xy, _csmIndex, _sc.z)).r;
}

И это сцена, которую я получаю (с каждым разделением слегка окрашенным и наложенными 4 глубинными слоями): Выглядит хорошо Большой! Выглядит неплохо.

Но если я поверну камеру немного вправо: Не очень хорошоТогда тени начнут исчезать (и в зависимости от угол, появляющийся там, где их не должно быть).

У меня включен GL_DEPTH_CLAMP, так что проблема не в этом. Я отбраковываю передние грани, но отключение этого значения не имеет значения.

Что мне не хватает? Я чувствую, что это проблема с одним из моих прогнозов, но все они мне кажутся правильными. Спасибо!

РЕДАКТИРОВАТЬ: нарисованы все четыре усеченных элемента света. Все они есть, но только z меняется относительно камеры (см. Комментарий ниже): light frustums

РЕДАКТИРОВАТЬ: Возможно, более полезно, так выглядят усеченные пирамиды, когда я обновляю их только один раз, когда камера находится в (0,0,0) и направлена ​​вперед (0,1,0). Также на этот раз я нарисовал их с тестированием глубины. усеченные пирамиды откуда-то еще

ВАЖНОЕ РЕДАКТИРОВАНИЕ: похоже, что эта проблема напрямую связана с матрицей обзора источника света, в настоящее время:

glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});

Изменение значений для глаза и цели, похоже, влияет на искаженные тени. Но я не знаю, что мне на самом деле следует установить? Это должно быть легко для кого-то, кто понимает лучше меня: D


person Jagoly    schedule 04.03.2015    source источник
comment
Я не вижу в коде ничего очевидного. Может быть, нарисуйте усеченные каскады, чтобы посмотреть, правильно ли они выглядят (цвет на земле не дает достаточно информации).   -  person Jerem    schedule 04.03.2015
comment
(Под рисованием усеченного конуса я подразумеваю просто коробку из проволочного каркаса.)   -  person Jerem    schedule 04.03.2015
comment
Какую усеченную пирамиду вы хотите нарисовать? Я просто настраиваю код для рисования усеченных окончательных матриц теней, и все они выглядят одинаково с точки зрения камеры, но действительно имеют разные значения. Это потому, что единственные значения, изменяющиеся относительно камеры, - это значения Z? Так и должно быть?   -  person Jagoly    schedule 04.03.2015
comment
Кроме того, я просто нарисовал усеченные области обзора камеры, и они просто образуют контур вокруг области просмотра, что кажется правильным.   -  person Jagoly    schedule 04.03.2015


Ответы (1)


Решил! Это действительно была проблема с матрицей обзора источника света! Все, что мне нужно было сделать, это заменить camPos центральной точкой каждой усеченной вершины! Это означает, что для каждой световой матрицы разделения требовалась своя матрица просмотра. Поэтому я просто создаю каждую матрицу вида вот так ...

glm::mat4 viewMat = glm::lookAt(frusCentre, frusCentre+lightDir, {0,0,1});

А получить frusCentre просто ...

glm::vec3 calc_frusCentre(const Frustum& _frus) {
    glm::vec3 min(INFINITY, INFINITY, INFINITY);
    glm::vec3 max(-INFINITY, -INFINITY, -INFINITY);
    for (auto& vec : {_frus.xyz, _frus.xyZ, _frus.xYz, _frus.xYZ,
                      _frus.Xyz, _frus.XyZ, _frus.XYz, _frus.XYZ}) {
        min = glm::min(min, vec);
        max = glm::max(max, vec);
    }
    return (min + max) / 2.f;
}

И бац! Все работает шедеврально!

РЕДАКТИРОВАТЬ (последний!): То, что у меня было, было не совсем правильным. Матрица представления на самом деле должна быть:

glm::lookAt(frusCentre-lightDir, frusCentre, {0,0,1});
person Jagoly    schedule 05.03.2015
comment
Интересно ... Я никогда раньше не рассматривал возможность использования INFINITY для инициализации min. Я всегда боялся, что он не будет поддерживаться во всех системах, поэтому вместо этого использовал FLT_MAX. Однако INFINITY - более приятное решение, потому что математика с его участием даст NAN, делая очевидным, что что-то не инициализировано. Я только что проверил, и INFINITY определяется как FLT_MAX в системах, которые не поддерживают битовые шаблоны с плавающей запятой Infinity и Quiet NaN, что делает его идеальным для использования во всех системах; +1 за то, что заставили меня этому научиться;) - person Andon M. Coleman; 05.03.2015
comment
Учитывая, сколько я узнал от вас, я чувствую себя прекрасно: D - person Jagoly; 05.03.2015