Странный эффект SSAO (неправильная позиция / нормальные текстуры в пространстве вида?)

8

Я пытаюсь создать эффект SSAO в своем игровом движке (DirectX 11, C ++), основанный в основном на учебнике gamedev.net Хосе Мария Мендеса . К сожалению, это не охватывает проблему создания текстур (нормали, положение).

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

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

Мои догадки о том, что могло пойти не так:

  • нормальный расчет текстуры (в пространстве вида) - но выглядит нормально ,
  • расчет текстуры позиции (в пространстве вида) и преобразование глубины в позицию,
  • расчет матрицы обратной проекции,
  • что-то не нормализовано или не насыщено и должно быть,
  • параметры (однако они не должны оказывать такого влияния на результат)?

Мои текстуры

Diffuse ( textures[0]не используется здесь, просто для сравнения): введите описание изображения здесь

Нормальный ( textures[1]должен быть в поле зрения): введите описание изображения здесь

Я получаю их в первом проходе вершинного шейдера (пиксельные шейдеры делают просто output.normal = input.normal):

output.normal = float4(mul(input.normal, (float3x3)world), 1);
output.normal = float4(mul(output.normal, (float3x3)view), 1);

Текстура буфера глубины ( textures[2]трудно что-либо увидеть из-за низкой разницы в значениях, но я считаю, что это нормально):

введите описание изображения здесь

Для отображения я использую:

float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
depth = (depth + 1.0f) / 2.0f;
color.rgb = float3(depth, depth, depth);

После коррекции контрастности во внешней программе (я не использую шейдер, но лучше для глаз): введите описание изображения здесь

Описание буфера глубины:

//create depth stencil texture (depth buffer)
D3D11_TEXTURE2D_DESC descDepth;
ZeroMemory(&descDepth, sizeof(descDepth));
descDepth.Width = settings->getScreenSize().getX();
descDepth.Height = settings->getScreenSize().getY();
descDepth.MipLevels = 1;
descDepth.ArraySize = 1;
descDepth.Format = settings->isDepthBufferUsableAsTexture() ? DXGI_FORMAT_R24G8_TYPELESS : DXGI_FORMAT_D24_UNORM_S8_UINT; //true
descDepth.SampleDesc.Count = settings->getAntiAliasing().getCount(); //1
descDepth.SampleDesc.Quality = settings->getAntiAliasing().getQuality(); //0
descDepth.Usage = D3D11_USAGE_DEFAULT;
descDepth.BindFlags = settings->isDepthBufferUsableAsTexture() ? (D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE) : D3D11_BIND_DEPTH_STENCIL; //true
descDepth.CPUAccessFlags = 0;
descDepth.MiscFlags = 0;

А дальняя / ближняя плоскость (они влияют на глубину буфера текстуры) установлены на nearPlane = 10.0f( 1.0fне слишком сильно меняется и объекты не так близко к камере) и farPlane = 1000.0f.

На его основе я создаю позицию в пространстве вида (я не сохраняю ее в текстуре, но если вывести ее на экран, она будет выглядеть так): введите описание изображения здесь

Каждый пиксель здесь рассчитывается по:

float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
float3 screenPos = float3(textureCoordinates.xy* float2(2, -2) - float2(1, -1), 1 - depth);
float4 wpos = mul(float4(screenPos, 1.0f), projectionInverted);
wpos.xyz /= wpos.w;
return wpos.xyz;

И projectionInvertedпередается в шейдер с:

DirectX::XMFLOAT4X4 projection = camera->getProjection();
DirectX::XMMATRIX camProjection = XMLoadFloat4x4(&projection);
camProjection = XMMatrixTranspose(camProjection);
DirectX::XMVECTOR det; DirectX::XMMATRIX projectionInverted = XMMatrixInverse(&det, camProjection);
projectionInverted = XMMatrixTranspose(projectionInverted);
cbPerObj.projectionInverted = projectionInverted;
....
context->UpdateSubresource(constantBuffer, 0, NULL, &cbPerObj, 0, 0);
context->VSSetConstantBuffers(0, 1, &constantBuffer);
context->PSSetConstantBuffers(0, 1, &constantBuffer);

Я считаю, что camera->getProjection()возвращает хорошую матрицу, так как я использую ту же самую для построения viewProjectionMatrixдля объектов, что в порядке (я вижу правильные вершины в правильных местах). Может быть, что-то с транспонированием?

Random ( textures[3], normal) - это текстура из учебника, только в .tgaформате: введите описание изображения здесь

Случайная текстура не содержит плиток (она повторяется, когда вы кладете одну текстуру рядом с другой). Если я отображаю его getRandom(uv)на весь экран ( float2результат отображается красным и зеленым): введите описание изображения здесь

И результат: введите описание изображения здесь

Это выглядит как «некоторое затенение», но не похоже на компонент SSAO (он все равно не размыт или не смешан со светом / рассеянным). Я предполагаю, что это больше похоже на затенение по Фонгу, чем на реальный SSAO (без «глубоких углов» и т. Д.)

Шейдер SSAO (второй проход):

//based on: http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/a-simple-and-practical-approach-to-ssao-r2753

Texture2D textures[4]; //color, normal (in view space), position (depth), random
SamplerState ObjSamplerState;

cbuffer cbPerObject : register(b0) {
    float4 notImportant;
    float4 notImportant2;
    float2 notImportant3;
    float4x4 projectionInverted;
};

cbuffer cbPerShader : register(b1) {
    float4 parameters; //randomSize, sampleRad, intensity, scale
    float4 parameters2; //bias
};


struct VS_OUTPUT {
    float4 Pos : SV_POSITION;
    float2 textureCoordinates : TEXCOORD;
};

float3 getPosition(float2 textureCoordinates) {
    float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
    float3 screenPos = float3(textureCoordinates.xy* float2(2, -2) - float2(1, -1), 1 - depth);
    float4 wpos = mul(float4(screenPos, 1.0f), projectionInverted);
    wpos.xyz /= wpos.w;
    return wpos.xyz;
}

float3 getNormal(in float2 textureCoordinates) {
    return normalize(textures[1].Sample(ObjSamplerState, textureCoordinates).xyz * 2.0f - 1.0f);
}

float2 getRandom(in float2 textureCoordinates) {
    return normalize(textures[3].Sample(ObjSamplerState, float2(1980,1050)/*screenSize*/ * textureCoordinates / parameters[0]/*randomSize*/).xy * 2.0f - 1.0f);
}

float doAmbientOcclusion(in float2 tcoord, in float2 textureCoordinates, in float3 p, in float3 cnorm) {
    float3 diff = getPosition(tcoord + textureCoordinates) - p;
    const float3 v = normalize(diff);
    const float d = length(diff)*parameters[3]/*scale*/;
    return max(0.0, dot(cnorm, v) - 0.2f/*bias*/)*(1.0 / (1.0 + d))*parameters[2]/*intensity*/;
}

float4 main(VS_OUTPUT input) : SV_TARGET0{

    float2 textureCoordinates = input.textureCoordinates;

    //SSAO

    const float2 vec[4] = { float2(1,0),float2(-1,0), float2(0,1),float2(0,-1) };

    float3 p = getPosition(textureCoordinates);
    float3 n = getNormal(textureCoordinates);
    float2 rand = getRandom(textureCoordinates);

    float ao = 0.0f;
    float rad = parameters[1]/*sampleRad*/ / p.z;

    //**SSAO Calculation**//
    int iterations = 4;
    for (int j = 0; j < iterations; ++j) {
        float2 coord1 = reflect(vec[j], rand)*rad;
        float2 coord2 = float2(coord1.x*0.707 - coord1.y*0.707, coord1.x*0.707 + coord1.y*0.707);

        ao += doAmbientOcclusion(textureCoordinates, coord1*0.25, p, n);
        ao += doAmbientOcclusion(textureCoordinates, coord2*0.5, p, n);
        ao += doAmbientOcclusion(textureCoordinates, coord1*0.75, p, n);
        ao += doAmbientOcclusion(textureCoordinates, coord2, p, n);
    }
    //ao /= (float)iterations*4.0;
    //ao /= (float)parameters[1]/*sampleRad*/;
    ao = 1 - (ao * parameters[2]/*intensity*/);

    //ao = saturate(ao);
    //**END**//

    //Do stuff here with your occlusion value ??ao??: modulate ambient lighting, write it to a buffer for later //use, etc.

    //SSAO end

    color = ao; //let's just output the SSAO component for now
    return float4(color.rgb, 1.0f);
}

Я предоставляю эти параметры (я пытался поиграть с ними, они меняют качество результата и т. Д., Но не общий эффект):

getShader()->setParameter(0, 64.0f); //randomSize
getShader()->setParameter(1, 10.0f); //sampleRad
getShader()->setParameter(2, 1.0f); //intensity
getShader()->setParameter(3, 0.5f); //scale
getShader()->setParameter(4, 0.0f); //bias (also 0.2f sometimes)

Обратите внимание, что я прокомментировал ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;только потому, что таким образом я могу видеть все (в другом случае различия в тонах компонентов SSAO слишком малы - но это может быть проблемой с неправильными параметрами).

редактировать # 1

Как и @AndonM.Colemanпредполагалось, я выводил на экран обычную текстуру textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5fвместо textures[1].Sample(ObjSamplerState, textureCoordinates):

введите описание изображения здесь

Кроме того, я попытался удалить *2.0f - 1.0fчасть из, getNormal(...)и это дало мне другой результат для компонента SSAO:

введите описание изображения здесь

Это все еще неправильно, я не знаю, ближе это или дальше от "хорошо". Может быть, согласно @AndonM.Coleman(если я его понял), это как-то связано с форматами буферов? Мои форматы:

#define BUFFER_FORMAT_SWAP_CHAIN DXGI_FORMAT_R8G8B8A8_UNORM

//depth buffer
//I don't use it: #define BUFFER_FORMAT_DEPTH DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_USABLE_AS_TEXTURE DXGI_FORMAT_R24G8_TYPELESS
//I don't use it: #define BUFFER_FORMAT_DEPTH_VIEW DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_VIEW_AS_TEXTURE DXGI_FORMAT_D24_UNORM_S8_UINT
#define BUFFER_FORMAT_DEPTH_SHADER_RESOURCE_VIEW DXGI_FORMAT_R24_UNORM_X8_TYPELESS

#define BUFFER_FORMAT_TEXTURE DXGI_FORMAT_R8G8B8A8_UNORM

Я действительно не хочу использовать более медленные форматы, если в этом нет необходимости.

редактировать № 2

Изменение 1 - depthдля depth - 1вывода новой (и странной, я полагаю) текстуры позиции:

введите описание изображения здесь

И компонент SSAO изменяется на:

введите описание изображения здесь

Обратите внимание, что я все еще ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/закомментировал оригинал , чтобы что-то увидеть.

редактировать № 3

Изменение формата буфера для текстуры (и только для нее) с DXGI_FORMAT_R8G8B8A8_UNORMна DXGI_FORMAT_R32G32B32A32_FLOATдает мне более приятную текстуру:

введите описание изображения здесь

Кроме того, изменение смещения от 0.0fк 0.2fи радиус выборки от 10.0fк 0.2fдал мне:

введите описание изображения здесь

Это выглядит лучше, не так ли? Тем не менее, у меня есть тени вокруг объектов слева и инверсия справа. Что может вызвать это?

PolGraphic
источник
Что-то странное для меня в getRandom (), когда вы выбираете неповторяющуюся текстуру, вы будете использовать [0-1] UV-координаты. Учитывая, что вы используете float2 (1980,1050), даже если вы умножаете / делите, я не вижу, как вы получите значения [0-1] с такими формулами ... Или ваша текстура шума установлена ​​в режим повтора ?
VB_overflow
1
Глупый вопрос: ваши цели рендеринга с плавающей точкой? На скриншотах, которые вы показали с координатными пространствами, есть большие черные области, где все три координаты равны 0 или отрицательны. Все они привязаны к 0 с использованием традиционных целей рендеринга с фиксированной точкой (UNORM), и вы точно не получите точный SSAO, если не сможете правильно сэмплировать положение / нормаль. Технически вам не нужны цели рендеринга с плавающей запятой, если вы беспокоитесь о производительности - вы можете использовать SNORM или изменить масштаб / смещение цветов вручную.
Андон М. Коулман
1
Нет, вы не можете хранить отрицательные значения в DXGI_FORMAT_R8G8B8A8_UNORM. Вам потребуется SNORMверсия этого, иначе половина вашего векторного пространства будет ограничена до 0 . Теперь я действительно вижу, что об этом уже позаботились, умножив на 2 и вычтя отрицательный 1 как в нормальном, так и в позиционном коде. Тем не менее, я настоятельно рекомендую, чтобы перед выводом этих цветов на экран для визуализации вы выработали привычку * 0.5 + 0.5(чтобы вы могли видеть отрицательные части вашего координатного пространства). Поскольку 0.5 + 1.0это не будет работать, ваши цвета будут идти от 0,5 до 1,5 (ограничено до 1).
Andon M. Coleman
1
Это все хорошо, на самом деле. Код полностью позаботится об этом прямо сейчас. Нормы сохраняются в текстуре в диапазоне 0.0 - 1.0 , но после выборки изменяются до -1.0 - 1.0 ; ваша позиция тоже. Однако эта 1 - depthчасть выглядит странно для меня. Я думаю, что так и должно быть depth - 1, иначе ваш диапазон глубины перевернут.
Andon M. Coleman
1
Я вообще не эксперт, но ... ваша обычная текстура не содержит синего цвета? AFAIПонятно, каждый пиксель нормальной текстуры должен удовлетворять этому уравнению: r ^ 2 + g ^ 2 + b ^ 2 = 1 (то есть: длина 1). Я проверил ваше изображение, и оно содержит чисто черные пиксели.
Мартин Курто

Ответы:

3

Насколько мне известно, ваша позиция выводится неправильно в обоих случаях. Если должно выглядеть так:введите описание изображения здесь

Или это: введите описание изображения здесь

В зависимости от того, используете ли вы левую или правую систему координат. Вы должны создать буфер позиции и пропустить попытку вычислить его по глубине, пока не узнаете, что работает, а что нет. Создайте новую цель визуализации в проходе GBuffer и просто выведите позицию следующим образом:

output.Target3 = float4(input.PosV.z, input.PosV.z, input.PosV.z, 1.0f);

Ваш последний снимок экрана выглядит как нормальная проблема. Вы уверены, что нормали находятся в поле зрения? Если нет, то стена справа должна оставаться темной, даже если вы поверните камеру на 180 градусов, а стена будет слева. Есть ли это или все еще темно на правой стороне?

Если при небольшом перемещении вида темные участки появляются и исчезают, скорее всего, это проблема проецирования. Как это: (это тот же угол комнаты) введите описание изображения здесь

Попробуйте переключить матрицу проекции с LH на RH или наоборот, возможно, вы перепутали ее.

Кроме того, убедитесь, что вы распаковываете нормали только с «* 2.0f - 1.0f», если вы также сохранили их с «* 0.5f + 1.0f». Вы говорили об этом в комментариях, но стоит проверить еще раз.

Стефан Агартсон
источник