Efeito SSAO estranho (posição incorreta / texturas normais no espaço da visualização?)

8

Tento criar um efeito SSAO no meu mecanismo de jogo (DirectX 11, C ++), baseado principalmente no tutorial gamedev.net de José María Méndez . Infelizmente, ele não cobre problemas de criação de textura (normais, posição).

Na primeira passagem, crio a textura normal e, em seguida, também leio o buffer de profundidade como textura (é possível desde que eu crie a visualização de recurso de sombreador e desassocie o buffer de profundidade na segunda passagem). Ambos devem estar no espaço de visualização.

Ainda não implementei o blur (farei isso mais tarde), mas tenho alguns problemas no momento. Acredito que seja devido ao cálculo incorreto das texturas ou ao método errado de transformar dados a partir deles , e não aos valores incorretos da fórmula ou dos parâmetros .

Minhas suposições do que poderia dar errado:

  • cálculo de textura normal (no espaço de visualização) - mas parece ok ,
  • cálculo da textura da posição (no espaço da vista) e transformação da profundidade em posição,
  • cálculo da matriz de projeção invertida,
  • algo não é normalizado ou saturado e deveria ser,
  • parâmetros (no entanto, eles não devem ter um impacto tão grande na produção)?

Minhas texturas

Difuso ( textures[0], não é realmente usado aqui, apenas para comparação): insira a descrição da imagem aqui

Normal ( textures[1], deve estar no espaço de visualização): insira a descrição da imagem aqui

Eu os pego no shader de vértice de primeira passagem (os shaders de pixel fazem exatamente output.normal = input.normal):

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

Textura de buffer de profundidade ( textures[2], é difícil ver alguma coisa por causa das baixas diferenças de valores, mas acredito que esteja tudo bem):

insira a descrição da imagem aqui

Para exibi-lo, eu uso:

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

Após a correção do contraste no programa externo (não o uso do shader, mas é melhor para os olhos): insira a descrição da imagem aqui

A descrição do buffer de profundidade:

//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;

E o plano distante / próximo (eles afetam a textura do buffer de profundidade) está definido como nearPlane = 10.0f( 1.0fnão muda muito e os objetos não estão tão próximos da câmera) e farPlane = 1000.0f.

Com base nisso, crio a posição no espaço da visualização (não a salvo na textura, mas se a exibir na tela, será assim): insira a descrição da imagem aqui

Cada pixel aqui é calculado por:

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;

E projectionInvertedé transferido para o shader com:

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);

Eu acredito que camera->getProjection()retorna uma boa matriz, pois eu uso a mesma para construir os viewProjectionMatrixobjetos que estão ok (eu vejo os vértices certos nos lugares certos). Talvez algo com transposição?

Aleatório ( textures[3], normal) - é a textura do tutorial, apenas no .tgaformato: insira a descrição da imagem aqui

A textura aleatória é sem telha (é repetível quando você coloca uma textura ao lado da outra). Se eu renderizá-lo getRandom(uv)para a tela inteira, recebo (o float2resultado exibido em vermelho e verde): insira a descrição da imagem aqui

E o resultado: insira a descrição da imagem aqui

Parece "algum sombreamento", mas nada como o componente SSAO (ainda não está borrado ou misturado com luz / difuso). Eu acho que é mais parecido com o sombreamento phong do que com o SSAO real (sem sombras em "cantos profundos" etc.)

O sombreador SSAO (segunda passagem):

//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);
}

Eu forneço esses parâmetros (eu estava tentando brincar com eles, eles alteram a qualidade do resultado etc., mas não o efeito geral):

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)

Observe que eu comentei ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;apenas porque dessa maneira eu posso ver qualquer coisa (em outro caso, as diferenças nos tons dos componentes do SSAO são muito pequenas - mas isso pode ser um problema com parâmetros incorretos).

editar # 1

Como @AndonM.Colemansugerido, eu produzo a textura normal para a tela, em textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5fvez de apenas textures[1].Sample(ObjSamplerState, textureCoordinates):

insira a descrição da imagem aqui

Além disso, tentei remover *2.0f - 1.0fparte getNormal(...)e isso me deu outro resultado para o componente SSAO:

insira a descrição da imagem aqui

Ainda está errado, não sei se está mais perto ou mais longe de "bom". Talvez, de acordo com @AndonM.Coleman(se eu o entendi), tem algo a ver com os formatos dos buffers? Meus formatos são:

#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

Eu realmente não quero usar formatos mais lentos, se não for necessário.

editar # 2

Alterar o 1 - depthpara depth - 1saída de novo (e estranho, eu acho) posição textura:

insira a descrição da imagem aqui

E o componente SSAO muda para:

insira a descrição da imagem aqui

Observe que ainda tenho o original ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/comentado para ver qualquer coisa.

editar # 3

Alterar o formato do buffer para a textura (e somente para ela) de DXGI_FORMAT_R8G8B8A8_UNORMpara DXGI_FORMAT_R32G32B32A32_FLOATfornece uma textura normal mais agradável:

insira a descrição da imagem aqui

Além disso, alterar o viés de 0.0fpara 0.2fe o raio da amostra de 10.0fpara 0.2fme deu:

insira a descrição da imagem aqui

Parece melhor, não é? No entanto, tenho sombras ao redor de objetos à esquerda e inversão à direita. O que pode causar isso?

PolGraphic
fonte
Algo me parece estranho em getRandom (), quando você provar uma textura não repetida, usará as coordenadas UV [0-1]. Dado que você usa float2 (1980, 1050), mesmo que você multiplique / divida, não vejo como você recuperará [0-1] valores com essas fórmulas ... Ou sua textura de ruído está definida para o modo de repetição ?
VB_overflow
11
Pergunta estúpida: seus destinos de renderização são de ponto flutuante? As capturas de tela que você mostra com espaços de coordenadas possuem grandes espaços em preto, onde todas as três coordenadas são 0 ou negativas. Eles estão todos fixados em 0 usando alvos de renderização de ponto fixo (UNORM) tradicionais e você definitivamente não obterá SSAO preciso quando não puder provar a posição / normal corretamente. Tecnicamente, você não precisa de destinos de renderização de ponto flutuante se estiver preocupado com o desempenho - você pode usar SNORM ou redimensionar / compensar as cores manualmente.
Andon M. Coleman
11
Não, você não pode armazenar valores negativos em DXGI_FORMAT_R8G8B8A8_UNORM. Você precisaria da SNORMversão disso ou metade do seu espaço vetorial será fixado em 0 . Na verdade, agora vejo que isso já foi resolvido multiplicando por 2 e subtraindo 1 negativo no código normal e de posição. No entanto, sugiro fortemente que, antes de imprimir essas cores na tela para visualização, você tenha o hábito de fazer * 0.5 + 0.5(para poder ver as partes negativas do seu espaço de coordenadas). Como 0.5 + 1.0isso não funcionaria, suas cores seriam de 0,5 a 1,5 (fixadas em 1).
Andon M. Coleman
11
Isso é tudo de bom, na verdade. O código cuida disso completamente para você agora. As normais são armazenados na faixa de 0,0 - 1,0 na textura, mas redimensionado para -1.0 - 1.0 depois amostrado; sua posição é também. No entanto, a 1 - depthparte parece estranha para mim. Eu acho que deveria ser depth - 1, caso contrário, seu alcance de profundidade é invertido.
Andon M. Coleman
11
Não sou especialista, mas ... sua textura normal não contém azul? AFAICompreenda, todo pixel da textura normal deve satisfazer esta equação: r ^ 2 + g ^ 2 + b ^ 2 = 1 (ou seja: comprimento 1). Testei sua imagem e ela contém pixels pretos puros.
Martijn Courteaux

Respostas:

3

Que eu saiba, sua saída de posição está errada nos dois casos. Se deve ficar assim:insira a descrição da imagem aqui

Ou isto: insira a descrição da imagem aqui

Dependendo se você estiver usando o sistema de coordenadas da mão esquerda ou direita. Você deve criar um buffer de posição e pular tentando calculá-lo em profundidade até saber o que está funcionando e o que não está. Crie um novo rendertarget no passe GBuffer e apenas produza a posição como esta:

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

Sua última captura de tela parece um problema normal. Tem certeza de que os normais estão no espaço de visualização? Caso contrário, a parede à direita deve permanecer escura, mesmo se você girar a câmera 180 graus e a parede estiver à esquerda. Faz ou ainda está escuro no lado direito?

Se o movimento da vista levemente fizer com que as partes escuras apareçam e desapareçam, provavelmente é um problema de projeção. Assim: (é o mesmo canto de uma sala) insira a descrição da imagem aqui

Tente mudar sua matriz de projeção de LH para RH ou vice-versa, você pode ter confundido.

Além disso, certifique-se de descompactar normais com "* 2.0f - 1.0f" se você também os armazenou com "* 0.5f + 1.0f". Você cobriu isso nos comentários, mas vale a pena conferir mais uma vez.

Stefan Agartsson
fonte