Strano effetto SSAO (posizione errata / texture normali nello spazio di visualizzazione?)


8

Cerco di creare un effetto SSAO nel mio motore di gioco (DirectX 11, C ++), basato principalmente sul tutorial di gamedev.net di José María Méndez . Sfortunatamente, non copre il problema di creazione di texture (normali, posizione).

Nel primo passaggio creo la trama normale e quindi leggo anche il buffer di profondità come trama (è possibile poiché creo la vista delle risorse dello shader e separo il buffer di profondità nel secondo passaggio). Entrambi dovrebbero essere nello spazio di visualizzazione.

Non ho ancora implementato la sfocatura (lo farò più tardi), ma ora ho dei problemi. Credo che sia dovuto al calcolo errato delle trame o al metodo sbagliato di trasformare i dati da esse , piuttosto che a valori errati di formula o parametri .

Le mie ipotesi su cosa potrebbe andare storto:

  • calcolo della trama normale (nello spazio di visualizzazione) - ma sembra ok ,
  • calcolo della struttura della posizione (nello spazio di visualizzazione) e trasformazione della profondità in posizione,
  • calcolo della matrice di proiezione invertita,
  • qualcosa non è normalizzato o saturo e dovrebbe essere,
  • parametri (tuttavia non dovrebbero avere un tale impatto sull'output)?

Le mie trame

Diffuso ( textures[0], non proprio usato qui, solo per confronto): inserisci qui la descrizione dell'immagine

Normale ( textures[1], dovrebbe essere nello spazio di visualizzazione): inserisci qui la descrizione dell'immagine

Li ottengo nel vertex shader di primo passaggio (i pixel shader fanno semplicemente output.normal = input.normal):

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

Trama del buffer di profondità ( textures[2]è difficile vedere qualcosa a causa delle basse differenze nei valori, ma credo che sia ok):

inserisci qui la descrizione dell'immagine

Per visualizzarlo utilizzo:

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

Dopo la correzione del contrasto in un programma esterno (non lo uso shader, ma è meglio per gli occhi): inserisci qui la descrizione dell'immagine

La descrizione del buffer di profondità:

//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 il piano lontano / vicino (hanno un impatto sulla trama del buffer di profondità) sono impostati su nearPlane = 10.0f( 1.0fnon cambia troppo e gli oggetti non sono così vicini alla telecamera) e farPlane = 1000.0f.

Sulla base di esso creo la posizione nello spazio di visualizzazione (non lo salvo in trama, ma se l'output sullo schermo sembra così): inserisci qui la descrizione dell'immagine

Ogni pixel qui è calcolato da:

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 projectionInvertedviene trasferito nello shader con:

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

Credo che camera->getProjection()ritorni una buona matrice, poiché uso lo stesso per creare l' viewProjectionMatrixoggetto for che è ok (vedo i vertici giusti nei punti giusti). Forse qualcosa con trasposizione?

Casuale ( textures[3], normale): è la trama del tutorial, solo nel .tgaformato: inserisci qui la descrizione dell'immagine

La trama casuale è senza piastrelle (è ripetibile, quando si mette una trama accanto a un'altra). Se lo visualizzo con getRandom(uv)per l'intero schermo ottengo (il float2risultato viene visualizzato in rosso e verde): inserisci qui la descrizione dell'immagine

E il risultato: inserisci qui la descrizione dell'immagine

Sembra "un po 'di ombreggiatura" ma niente come il componente SSAO (non è ancora sfocato o mescolato con luce / diffusa comunque). Immagino sia più simile all'ombreggiatura phong che all'effettivo SSAO (nessuna sfumatura attorno agli "angoli profondi" ecc.)

Lo shader SSAO (secondo passaggio):

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

Fornisco quei parametri (stavo cercando di giocarci, cambiano la qualità del risultato ecc. Ma non l'effetto complessivo):

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)

Nota che ho commentato ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/; solo perché in questo modo riesco a vedere qualsiasi cosa (in altri casi le differenze nei toni dei componenti SSAO sono troppo piccole - ma questo può essere un problema con parametri errati).

modifica n. 1

Come @AndonM.Colemansuggerito, ho prodotto la normale trama su schermo con textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5finvece di solo textures[1].Sample(ObjSamplerState, textureCoordinates):

inserisci qui la descrizione dell'immagine

Inoltre, ho provato a rimuovere la *2.0f - 1.0fparte da getNormal(...)e mi ha dato un altro risultato per il componente SSAO:

inserisci qui la descrizione dell'immagine

È ancora sbagliato, non so se sia più vicino o più lontano da "buono". Forse, secondo @AndonM.Coleman(se l'ho capito) ha qualcosa a che fare con i formati dei buffer? I miei formati sono:

#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

Non voglio davvero usare formati più lenti se non è necessario.

modifica n. 2

Modifica della texture di posizione 1 - depthin depth - 1output nuova (e strana, immagino):

inserisci qui la descrizione dell'immagine

E il componente SSAO cambia in:

inserisci qui la descrizione dell'immagine

Nota che ho ancora ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/commentato l'originale per vedere qualcosa.

modifica n. 3

Cambiare il formato del buffer per la trama (e solo per esso) da DXGI_FORMAT_R8G8B8A8_UNORMa DXGI_FORMAT_R32G32B32A32_FLOATmi dà una trama normale più bella:

inserisci qui la descrizione dell'immagine

Inoltre, cambiando il bias da 0.0fa 0.2fe il raggio di campionamento da 10.0fa 0.2fmi ha dato:

inserisci qui la descrizione dell'immagine

Sembra meglio, no? Tuttavia, ho delle ombre attorno agli oggetti a sinistra e l'inversione a destra. Cosa può causare questo?


Qualcosa di strano per me in getRandom (), quando campionate una trama non ripetuta userete [0-1] coordinate UV. Dato che usi float2 (1980,1050), anche se moltiplichi / dividi il fatto che non vedo come otterrai valori [0-1] con tali formule ... O la trama del rumore è impostata in modalità ripetizione ?
VB_overflow,

1
Domanda stupida: i tuoi target di rendering sono in virgola mobile? Gli screenshot che hai mostrato con spazi di coordinate in essi hanno grandi spazi neri in cui tutte e tre le coordinate sono 0 o negative. Sono tutti bloccati a 0 utilizzando target di rendering tradizionali a punto fisso (UNORM) e sicuramente non otterrai SSAO accurati quando non puoi campionare correttamente la posizione / normale. Tecnicamente non hai bisogno di target di rendering in virgola mobile se ti preoccupi delle prestazioni: puoi usare SNORM o riscalare / compensare i colori manualmente.
Andon M. Coleman,

1
No, non puoi memorizzare valori negativi DXGI_FORMAT_R8G8B8A8_UNORM. Avresti bisogno della SNORMversione di quello, o metà del tuo spazio vettoriale sarà bloccato su 0 . In realtà ora vedo che questo viene risolto moltiplicando per 2 e sottraendo 1 negativo sia nel codice normale che in quello di posizione. Tuttavia, consiglio vivamente che prima di inviare questi colori allo schermo per la visualizzazione, prendi l'abitudine di farlo * 0.5 + 0.5(in modo da poter vedere le parti negative del tuo spazio di coordinate). Per 0.5 + 1.0quanto non funzionerebbe, i tuoi colori andrebbero da 0,5 a 1,5 (fissati a 1).
Andon M. Coleman,

1
Va tutto bene, in realtà. Il codice si occupa completamente di questo per te in questo momento. Le normali sono memorizzate nell'intervallo 0,0 - 1,0 nella trama, ma ridimensionate a -1,0 - 1,0 dopo il campionamento; anche la tua posizione. Tuttavia, la 1 - depthparte mi sembra strana. Penso che dovrebbe essere depth - 1, altrimenti la tua gamma di profondità è invertita.
Andon M. Coleman,

1
Non sono affatto un esperto, ma ... la tua consistenza normale non contiene alcun blu? AFAIUnderstand, ogni pixel della trama normale dovrebbe soddisfare questa equazione: r ^ 2 + g ^ 2 + b ^ 2 = 1 (cioè: lunghezza 1). Ho testato la tua immagine e contiene pixel neri puri.
Martijn Courteaux,

Risposte:


3

Per quanto ne so, l'output della tua posizione è errato in entrambi i casi. Se dovrebbe apparire così:inserisci qui la descrizione dell'immagine

O questo: inserisci qui la descrizione dell'immagine

A seconda se si utilizza il sistema di coordinate Mano sinistra o Mano destra. Dovresti creare un buffer di posizione e saltare cercando di calcolarlo dal profondo fino a quando non sai cosa funziona e cosa no. Crea un nuovo rendertarget nel passaggio GBuffer e genera la posizione in questo modo:

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

Il tuo ultimo screenshot sembra un problema normale, sei sicuro che le normali siano nello spazio visivo? In caso contrario, il muro a destra dovrebbe rimanere scuro anche se si gira la fotocamera di 180 gradi e il muro è a sinistra. Lo fa o è ancora scuro sul lato destro?

Se lo spostamento della vista fa apparire e scomparire leggermente le parti scure, è molto probabile che si tratti di un problema di proiezione. In questo modo: (è lo stesso angolo di una stanza) inserisci qui la descrizione dell'immagine

Prova a cambiare la tua matrice di proiezione da LH a RH o viceversa, potresti averla confusa.

Inoltre, assicurati di decomprimere le normali solo con "* 2.0f - 1.0f" se le hai anche memorizzate con "* 0.5f + 1.0f". Lo hai coperto nei commenti ma vale la pena controllarlo ancora una volta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.