ПрограммированиеФорумГрафика

Мерцание CSM (Vulkan)

#0
(Правка: 15:45) 15:41, 1 окт 2025

Всем привет! Введение в ситуацию: Не так давно приступил к написанию своей игры. Т.к я до боли любопытный изобретатель велосипедов, мне хочется изучить вопрос максимально, то я решил делать игру с нуля. Сейчас занимаюсь созданием окружения - графики, в качестве API использую Vulkan.
Последней моей задачей было создание каскадных теней (CSM). Собственно сделать то я их сделал, четыре уровня и даже все вроде работает, однако при обновлении матрицы камеры происходит невнятное "мерцание" ландшафта (на видео) - и микрофризы. То есть тени как бы на миг прекращают отображаться, что то пересчитывается и в этот момент и происходит лаг.
Может кто то сталкивался с такой проблемой и сможет подсказать, где может крыться ошибка или хотя бы куда можно попробовать логи воткнуть, чтобы попытаться отловить причину?
Слои валидации включены - ошибок нет.

Запустить видео по клику - Как делать игрыЗапустить видео по клику - Как делать игры

Код:

void GameState::UpdateCascades() {
    static uint64_t lastUpdateTime = 0;
    static glm::vec3 lastCameraPos;
    static glm::vec3 lastSunDir;

    uint64_t currentTime = SDL_GetTicks64();
    glm::vec3 currentCameraPos = m_cameraController.GetPosition();
    glm::vec3 currentSunDir = m_timeSystem->GetSunDirection();

    // движении камеры > 2м, изменении солнца > 5, или каждые 200мс
    float cameraMoveDist = glm::distance(currentCameraPos, lastCameraPos);
    float sunAngleChange = glm::degrees(acos(glm::dot(currentSunDir, lastSunDir)));

    if (cameraMoveDist < 2.0f && sunAngleChange < 5.0f && (currentTime - lastUpdateTime < 200)) {
        return;
    }

    lastUpdateTime = currentTime;
    lastCameraPos = currentCameraPos;
    lastSunDir = currentSunDir;

    auto cam = m_cameraController;
    float nearClip = 0.1f;
    float farClip = 500.0f;

    VkExtent2D extent = m_app->GetRenderSystem()->GetSwapchainExtent();
    float aspect = static_cast<float>(extent.width) / static_cast<float>(extent.height);


    glm::mat4 view = cam.GetViewMatrix();
    glm::mat4 proj = glm::perspective(glm::radians(90.0f), aspect, nearClip, farClip);
    glm::mat4 invView = glm::inverse(view); 
    glm::mat4 invProj = glm::inverse(proj); 

    //сплиты каскадов
    float cascadeSplits[CASCADE_COUNT + 1];
    float splitLambda = 0.95f;
    for (int i = 0; i <= CASCADE_COUNT; ++i) {
        float fraction = static_cast<float>(i) / static_cast<float>(CASCADE_COUNT);
        float logSplit = nearClip * std::pow(farClip / nearClip, fraction);
        float uniformSplit = nearClip + (farClip - nearClip) * fraction;
        cascadeSplits[i] = splitLambda * logSplit + (1.0f - splitLambda) * uniformSplit;
        m_cascadesSplitsLinear[i] = cascadeSplits[i];
    }

    glm::vec3 sunDir = m_timeSystem->GetSunDirection();
    const float SHADOW_MAP_RESOLUTION = 2048.0f;

    for (int cascadeIndex = 0; cascadeIndex < CASCADE_COUNT; ++cascadeIndex) {
        float nearZ = cascadeSplits[cascadeIndex];
        float farZ = cascadeSplits[cascadeIndex + 1];

        // Углы фрустума
        glm::vec4 frustumCornersLS[8] = {
            glm::vec4(-1.0f, -1.0f, -1.0f, 1.0f),
            glm::vec4(1.0f, -1.0f, -1.0f, 1.0f),
            glm::vec4(1.0f,  1.0f, -1.0f, 1.0f),
            glm::vec4(-1.0f,  1.0f, -1.0f, 1.0f),
            glm::vec4(-1.0f, -1.0f,  1.0f, 1.0f),
            glm::vec4(1.0f, -1.0f,  1.0f, 1.0f),
            glm::vec4(1.0f,  1.0f,  1.0f, 1.0f),
            glm::vec4(-1.0f,  1.0f,  1.0f, 1.0f)
        };

        glm::vec3 frustumCornersWS[8];
        for (int i = 0; i < 8; ++i) {
            glm::vec4 viewPos = invProj * frustumCornersLS[i];
            if (viewPos.w != 0.0f) viewPos /= viewPos.w;
            viewPos.z = (i < 4) ? -nearZ : -farZ;
            glm::vec4 worldPos = invView * viewPos;
            frustumCornersWS[i] = glm::vec3(worldPos);
        }

        // Центр фрустума
        glm::vec3 centerWS(0.0f);
        for (const auto& corner : frustumCornersWS) {
            centerWS += corner;
        }
        centerWS /= 8.0f;

        // Матрица вида света
        glm::vec3 lightPos = centerWS - sunDir * 1000.0f;
        glm::mat4 lightView = glm::lookAt(lightPos, centerWS, glm::vec3(0.0f, 1.0f, 0.0f));

        // AABB в light space
        float minX = std::numeric_limits<float>::max();
        float maxX = std::numeric_limits<float>::lowest();
        float minY = std::numeric_limits<float>::max();
        float maxY = std::numeric_limits<float>::lowest();
        float minZ = std::numeric_limits<float>::max();
        float maxZ = std::numeric_limits<float>::lowest();

        for (const auto& corner : frustumCornersWS) {
            glm::vec4 lightSpaceCorner = lightView * glm::vec4(corner, 1.0f);
            minX = std::min(minX, lightSpaceCorner.x);
            maxX = std::max(maxX, lightSpaceCorner.x);
            minY = std::min(minY, lightSpaceCorner.y);
            maxY = std::max(maxY, lightSpaceCorner.y);
            minZ = std::min(minZ, lightSpaceCorner.z);
            maxZ = std::max(maxZ, lightSpaceCorner.z);
        }

        // СТАБИЛИЗАЦИЯ
        glm::mat4 shadowMatrix = glm::ortho(minX, maxX, minY, maxY, minZ, maxZ) * lightView;

        glm::vec4 shadowOrigin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
        shadowOrigin = shadowMatrix * shadowOrigin;
        shadowOrigin = shadowOrigin * (SHADOW_MAP_RESOLUTION / 2.0f);

        glm::vec4 roundedOrigin = glm::round(shadowOrigin);
        glm::vec4 roundOffset = roundedOrigin - shadowOrigin;
        roundOffset = roundOffset * (2.0f / SHADOW_MAP_RESOLUTION);
        roundOffset.z = 0.0f;
        roundOffset.w = 0.0f;

        // Применяем стабилизацию
        glm::mat4 shadowMatrixTemp = shadowMatrix;
        shadowMatrixTemp[3][0] += roundOffset.x;
        shadowMatrixTemp[3][1] += roundOffset.y;
        shadowMatrixTemp[3][2] += roundOffset.z;

        // стаб матрица для финального AABB
        minX = maxX = minY = maxY = minZ = maxZ = 0.0f;
        bool first = true;

        for (const auto& corner : frustumCornersWS) {
            glm::vec4 lightSpaceCorner = shadowMatrixTemp * glm::vec4(corner, 1.0f);

            if (first) {
                minX = maxX = lightSpaceCorner.x;
                minY = maxY = lightSpaceCorner.y;
                minZ = maxZ = lightSpaceCorner.z;
                first = false;
            }
            else {
                minX = std::min(minX, lightSpaceCorner.x);
                maxX = std::max(maxX, lightSpaceCorner.x);
                minY = std::min(minY, lightSpaceCorner.y);
                maxY = std::max(maxY, lightSpaceCorner.y);
                minZ = std::min(minZ, lightSpaceCorner.z);
                maxZ = std::max(maxZ, lightSpaceCorner.z);
            }
        }

        // Финальная проекция 
        const float zMargin = 10.0f;
        glm::mat4 finalLightProj = glm::ortho(minX, maxX, minY, maxY, minZ - zMargin, maxZ + zMargin);

        m_cascades[cascadeIndex].viewProj = finalLightProj * lightView;
        m_cascades[cascadeIndex].splitDepth = farZ;
    }
}

#1
15:54, 1 окт 2025

Tesmio
> происходит невнятное "мерцание" ландшафта (на видео) - и микрофризы
По видео не особо понятна проблема, особенно с такой текстурой, где там вообще тени?
Tesmio
> где может крыться ошибка
Я бы действовал методом исключения: просто коментишь кусок и смотришь пропала проблема или нет, потом сужаешь кусок в котором обнаружена проблема.

#2
16:05, 1 окт 2025

По видео не особо понятна проблема, особенно с такой текстурой, где там вообще тени?

Битрейт ютуба наверное съедает этот эффект. Но я бы описал это точнее так: на миллисекунду затенение "выключается", что воспринимается как вспышка ландшафта. Ровно на это же самое время "подвисает" экран (фриз).

#3
16:51, 1 окт 2025

Благослови бог нейросети, нашел причину.

Проблема заключалась в том, что перед рендером не копировал тени в юниформ-буффер.

#4
17:39, 1 окт 2025

Tesmio
как спрашивал ?

#5
(Правка: 14:22) 14:20, 3 окт 2025

Как спрашивал?

Ты на свете всех умнее, красивее и стройнее.
И ответик подскажи.

Странные новички, сразу в Вулкан лезут.
И наверное чужие примеры с гитхаба натырил.

#6
18:07, 3 окт 2025

ronniko
По крайней мере, чел пытается разбираться, что-то пробовать, а не тупо нанимать команду, как этот тут происходит по 100 раз на дню 😂

#7
(Правка: 21:23) 21:19, 4 окт 2025

Странные новички, сразу в Вулкан лезут.

ronniko, Ну конечно, надо с основ начинать. Надо же сначала ассемблер изучить для каждого существующего процессора, а то как можно вообще сразу лезть в высокоуровневые языки программирования. А еще лучше изучить как работает компьютер, ведь это так важно знать для того чтобы писать программы. Ну и наверное, еще и заодно надо знать весь курс электротехники до корки, и еще желательно по профильному направлению протереть штаны 6 лет в вузе, изучая мертвые и древние как г... мамонта подходы и алгоритмы.

И только после этого, потратив на все это нафиг на ненужное г.. лет 10, можно приступать к написанию программы Hello World. Иначе никак.

Мир изменился с появлением нейросетей, нужно это просто принять. Уже не нужно знать все что я перечислил выше (хотя я - знаю в основе, и я далеко не новичок, просто на этом форуме не был зарегистрирован ранее).
За меня это уже знает нейросеть, и знает гораздо лучше и больше. И с ее помощью я могу писать сразу то, что мне нужно, не тратя огромное количество времени на обучение этому всему. По сути нейросети заменяют мне ту самую команду разработчиков, о которой написал AMM1AK

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

#8
(Правка: 13:51) 12:56, 5 окт 2025

Мир изменился с появлением нейросетей, нужно это просто принять.

Вот поэтому Вулкан не нужон, нужно это просто принять.
Чей-то виртуал.

#9
14:34, 13 дек 2025

Tesmio
Куда ты не копировал?

ПрограммированиеФорумГрафика