Я пытаюсь создать эффект 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
дал мне:
Это выглядит лучше, не так ли? Тем не менее, у меня есть тени вокруг объектов слева и инверсия справа. Что может вызвать это?
DXGI_FORMAT_R8G8B8A8_UNORM
. Вам потребуется SNORM
версия этого, иначе половина вашего векторного пространства будет ограничена до 0 . Теперь я действительно вижу, что об этом уже позаботились, умножив на 2 и вычтя отрицательный 1 как в нормальном, так и в позиционном коде. Тем не менее, я настоятельно рекомендую, чтобы перед выводом этих цветов на экран для визуализации вы выработали привычку * 0.5 + 0.5
(чтобы вы могли видеть отрицательные части вашего координатного пространства). Поскольку 0.5 + 1.0
это не будет работать, ваши цвета будут идти от 0,5 до 1,5 (ограничено до 1).
1 - depth
часть выглядит странно для меня. Я думаю, что так и должно быть depth - 1
, иначе ваш диапазон глубины перевернут.