DirectX11: SSAO ломается при наклоне камеры вверх / вниз - PullRequest
0 голосов
/ 04 июля 2019

Я уже несколько недель пытаюсь заставить SSAO работать с моим графическим движком DirectX11 / C ++, и я просто не могу вспомнить больше ошибок, которые я мог бы допустить в этом коде.

Я следую этому учебному пособию по OpenGL и в основном имею ту же самую реализацию, используя DirectX с шейдерами HLSL (модель шейдера 5). У меня есть два кадровых буфера, один для данных о положении, один для нормалей, оба преобразуются в пространство просмотра и экспортируются в первом проходе шейдера:

Геометрия проходного вершинного шейдера:

struct VStoPS {
    float4 pos_ : SV_Position;
    float4 posView_ : POSITION1;
    float4 normalView_ : NORMAL1;
};

/********************** constant buffers ***********************/
cbuffer cbCamera_ {
    float4x4 matView_;
    float4x4 matProjection_;
};

cbuffer cbTransformations_ {
    float4x4 matModel_;
    float4x4 matNormalView_;
};

/*************************** main ******************************/
VStoPS vs_main(float3 pos : POSITION, float3 normal0 : NORMAL0, float2 texCoord0 : TEXCOORD0) {
    VStoPS output = (VStoPS) 0;

    output.posView_ = mul(matView_, mul(matModel_, float4(pos, 1.0)));
    output.normalView_ = normalize(mul(matNormalView_, float4(normal0, 0.0)));

    float4x4 viewProj = mul(matProjection_, matView_);
    float4x4 mvp = mul(viewProj, matModel_);
    output.pos_ = mul(mvp, float4(pos, 1.0));

    return output;
}

Геометрический проходной пиксельный шейдер:

/************************** structs ****************************/
struct VStoPS {
    float4 pos_ : SV_Position;
    float4 posView_ : POSITION1;
    float4 normalView_ : NORMAL1;
};

struct PS_Output {
    float4 positionView;
    float4 normalView;
};

/*************************** main ******************************/
PS_Output ps_main(VStoPS input) : SV_Target
{
    PS_Output output = (PS_Output)0;
    output.positionView = input.posView_;
    output.normalView = input.normalView_;
    return output;
}

Я вычисляю матрицу нормального вида следующим образом:

mat4 normalView = (viewMatrix * modelMatrix).getTransposed().getInverse();

Я построил пример ядра и его случайное вращение следующим образом (случайная функция возвращает число с плавающей точкой от 0,0 до 1,0):

// Build the main kernel with random samples
for (int i = 0; i < D3D_SSAO_SAMPLE_COUNT; i++)
{
    // Sample kernel is a hemisphere along the positive z axis
    vec3 sample(
        random() * 2.0f - 1.0f,
        random() * 2.0f - 1.0f,
        random()
    );

    // Put more samples closer to the origin of the hemisphere for better results
    float scale = lerp(0.1f, 1.0f, pow(static_cast<float>(i) / static_cast<float>(D3D_SSAO_SAMPLE_COUNT), 2));
    ssaoKernel_[i] = sample.getNormalized() * scale;
}

// Build random kernel rotations to reduce banding
for (int i = 0; i < D3D_SSAO_ROTATIONS_COUNT; i++)
{
    vec3 rotation(
        random() * 2.0f - 1.0f,
        random() * 2.0f - 1.0f,
        0.0f
    );

    ssaoKernelRotations_[i] = rotation.getNormalized();
}

А потом я передаю пропуск SSAO. Вершинный шейдер просто отображает полноэкранный квад, пиксельный шейдер выполняет настоящую работу SSAO следующим образом:

/************************** structs ****************************/
struct VStoPS {
    float4 pos_ : SV_Position;
    float2 texCoord0_ : TEXCOORD0;
};

/********************** constant buffers ***********************/
cbuffer cbSSAO_ {
    float3 samples_[32];
    float3 rotations_[9];
};

cbuffer cbGBufferCamera_ {
    float4x4 matCameraView_;
    float4x4 matCameraProjection_;
};

cbuffer cbScreenInfo_ {
    int screenWidth_;
    int screenHeight_;
};

/********************** shader resources ***********************/
SamplerState sampler_;

Texture2D<float4> gPositionViewFramebuffer_;
Texture2D<float4> gNormalViewFramebuffer_;

/*************************** main ******************************/
float4 ps_main(VStoPS input) : SV_Target {
    const int kernelSize = 32;

    // Get the proper rotation vector for the current fragment
    const float w = (float) screenWidth_;
    const float h = (float) screenHeight_;
    const float2 noiseScale = float2(w / 3.0, h / 3.0);
    const float2 scaledCoordinates = input.texCoord0_ * noiseScale;
    const uint rotationIndex = (scaledCoordinates.x % 3) * (scaledCoordinates.y % 3);
    const float3 kernelRotationVector = normalize(rotations_[rotationIndex]);

    // Sample fragment position and normal from textures
    const float3 fragPos = gPositionViewFramebuffer_.Sample(sampler_, input.texCoord0_).xyz;
    const float3 normal = normalize(gNormalViewFramebuffer_.Sample(sampler_, input.texCoord0_).xyz);

    // Build a transformation matrix from tangent space to view space
    const float3 tangent = normalize(kernelRotationVector - normal * dot(kernelRotationVector, normal));
    const float3 bitangent = cross(normal, tangent);
    const float3x3 TBN = transpose(float3x3(tangent, bitangent, normal));

    // Calculate occlusion
    float occlusion = 0.0;
    const float radius = 0.5;

    for (int i = 0; i < kernelSize; i++)
    {
        // Transform the sample
        float3 currentSample = mul(TBN, samples_[i]);
        currentSample = fragPos + (currentSample * radius);

        // Get the respective depth value from the gBuffer at the same projected location
        float4 offset = float4(currentSample, 1.0);
        offset = mul(matCameraProjection_, offset);
        float2 coords = ((offset.xy / offset.w) + float2(1.0, 1.0)) / 2.0;

        float sampleDepth = gPositionViewFramebuffer_.Sample(sampler_, coords.xy).z;

        // Increase occlusion if the sample is actually occluded
        float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth));
        occlusion += (sampleDepth <= currentSample.z ? 1.0 : 0.0) * rangeCheck;
    }

    occlusion = 1.0 - (occlusion / ((float) kernelSize));
    return float4(occlusion, occlusion, occlusion, 1.0);
}

После этого я применяю фильтр размытия, чтобы исправить шум, создаваемый случайными векторами вращения. Результаты не выглядят слишком плохо, если смотреть прямо вперед:

Kinda looks right

Конечно, есть некоторые артефакты, они не супер чистые, но работоспособные. На самом деле перед камерой витает куб, но, поскольку вокруг него нет окклюзии, он даже не выскочит. Но как только я наклоняю камеру вверх, это происходит:

Slightly tilted upwards

По сути, вся сцена сжимается вдоль оси y, а куб отражается вдоль оси x. Я отлаживал это часами и не могу понять, что не так. Вот список возможных проблем, которые я исключил (в основном используя NSight):

  • Образцы ядра и векторы вращения правильно созданы и загружены в постоянные буферы; они на 100% правильные
  • Положение и нормальные данные, похоже, преобразуются должным образом, хотя я не уверен на 100%, как должны выглядеть кадровые буферы, содержащие данные
  • Нет предупреждений в рендерере или самих шейдерах, нет усечения данных или подобного
  • Я попытался транспонировать разные матрицы, поскольку DirectX использует макет основной строки в отличие от OpenGL, но это, похоже, ничего не меняет в проблеме
  • Матрицы загружаются в постоянные буферы должным образом, вот пример матриц вида и проекции, когда я наклоняю камеру вверх:

View and projection matrices when tilting the camera upwards

Я действительно в растерянности. Любая помощь или советы о том, что я мог бы попытаться исправить это, очень ценится. Я буду рад предоставить дополнительную информацию о коде, если это необходимо.

1 Ответ

1 голос
/ 10 июля 2019

Поскольку я не мог выяснить, в чем проблема, я реализовал это решение вместо , которое отлично работает и уже написано с учетом DirectX.Я отмечаю это как решенное.

...