Seltsamer SSAO-Effekt (falsche Position / normale Texturen im Ansichtsraum?)

8

Ich versuche, einen SSAO-Effekt in meiner Spiel-Engine (DirectX 11, C ++) zu erstellen, der hauptsächlich auf dem gamedev.net-Tutorial von José María Méndez basiert . Leider wird das Problem der Texturerzeugung (Normalen, Position) nicht behandelt.

Im ersten Durchgang erstelle ich die normale Textur und lese dann auch den Tiefenpuffer als Textur (dies ist möglich, da ich die Shader-Ressourcenansicht erstelle und den Tiefenpuffer im zweiten Durchgang auflöse). Beide sollten im Sichtbereich sein.

Ich habe Unschärfe noch nicht implementiert (ich werde es später tun), aber ich habe momentan einige Probleme. Ich glaube, es liegt eher an einer falschen Berechnung von Texturen oder einer falschen Methode zur Transformation von Daten , als an falschen Formel- oder Parameterwerten .

Meine Vermutungen, was schief gehen könnte:

  • normale Texturberechnung (im Ansichtsbereich) - aber es sieht in Ordnung aus ,
  • Berechnung der Positionstextur (im Ansichtsraum) und Transformation von Tiefe zu Position,
  • invertierte Projektionsmatrixberechnung,
  • etwas ist nicht normalisiert oder gesättigt und es sollte sein,
  • Parameter (sollten sie jedoch keinen solchen Einfluss auf die Ausgabe haben)?

Meine Texturen

Diffus (hier textures[0]nicht wirklich verwendet, nur zum Vergleich): Geben Sie hier die Bildbeschreibung ein

Normal ( textures[1]sollte im Sichtbereich sein): Geben Sie hier die Bildbeschreibung ein

Ich bekomme sie im First-Pass- Vertex-Shader (Pixel-Shader tun dies nur output.normal = input.normal):

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

Tiefenpuffertextur ( textures[2]aufgrund geringer Wertunterschiede ist nichts zu erkennen, aber ich glaube, dass dies in Ordnung ist):

Geben Sie hier die Bildbeschreibung ein

Um es anzuzeigen, benutze ich:

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

Nach der Kontrastkorrektur in einem externen Programm (ich benutze es nicht als Shader, aber es ist besser für die Augen): Geben Sie hier die Bildbeschreibung ein

Die Beschreibung des Tiefenpuffers:

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

Und die Fern- / Nah-Ebene (sie wirken sich auf die Tiefenpuffertextur aus) ist auf nearPlane = 10.0f( 1.0fändert sich nicht zu stark und Objekte befinden sich nicht so nah an der Kamera) und farPlane = 1000.0f.

Darauf basierend erstelle ich die Position im Ansichtsbereich (ich speichere sie nicht in der Textur, aber wenn ich sie auf dem Bildschirm ausgebe, sieht sie so aus): Geben Sie hier die Bildbeschreibung ein

Jedes Pixel hier wird berechnet durch:

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;

Und projectionInvertedwird an Shader übertragen mit:

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

Ich glaube, das camera->getProjection()gibt eine gute Matrix zurück, da ich dieselbe verwende, um die viewProjectionMatrixfür Objekte zu erstellen , was in Ordnung ist (ich sehe die richtigen Eckpunkte an den richtigen Stellen). Vielleicht etwas mit Transponieren?

Zufällig ( textures[3], normal) - es ist die Textur aus dem Tutorial, nur im .tgaFormat: Geben Sie hier die Bildbeschreibung ein

Die zufällige Textur ist kachellos (sie kann wiederholt werden, wenn Sie eine Textur neben eine andere legen). Wenn ich es getRandom(uv)für den gesamten Bildschirm rendere, wird es float2angezeigt (das Ergebnis wird rot und grün angezeigt): Geben Sie hier die Bildbeschreibung ein

Und das Ergebnis: Geben Sie hier die Bildbeschreibung ein

Es sieht aus wie "etwas Schattierung", aber nichts wie eine SSAO-Komponente (es ist ohnehin noch nicht verschwommen oder mit Licht / Diffus gemischt). Ich denke, es ähnelt eher der Phong-Schattierung als der tatsächlichen SSAO (keine Schattierungen um "tiefe Ecken" usw.)

Der SSAO-Shader (Second Pass):

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

Ich gebe diese Parameter an (ich habe versucht, mit ihnen zu spielen, sie ändern die Ergebnisqualität usw., aber nicht den Gesamteffekt):

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)

Beachten Sie, dass ich auskommentiert habe, ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;nur weil ich auf diese Weise alles sehen kann (in anderen Fällen sind die Unterschiede in den SSAO-Komponententönen zu gering - aber das kann ein Problem mit falschen Parametern sein).

bearbeiten # 1

Wie @AndonM.Colemanvorgeschlagen, gebe ich die normale Textur auf dem Bildschirm aus, textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5fanstatt nur textures[1].Sample(ObjSamplerState, textureCoordinates):

Geben Sie hier die Bildbeschreibung ein

Außerdem habe ich versucht, ein *2.0f - 1.0fTeil von zu entfernen, getNormal(...)und es gab mir ein weiteres Ergebnis für die SSAO-Komponente:

Geben Sie hier die Bildbeschreibung ein

Es ist immer noch falsch, ich weiß nicht, ob es näher oder weiter von "gut" entfernt ist. Vielleicht hat es laut @AndonM.Coleman(wenn ich ihn verstanden habe) etwas mit den Formaten der Puffer zu tun? Meine Formate sind:

#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

Ich möchte wirklich keine langsameren Formate verwenden, wenn dies nicht erforderlich ist.

bearbeiten # 2

Ändern der, 1 - depthum eine depth - 1neue (und seltsame, denke ich) Positionstextur auszugeben:

Geben Sie hier die Bildbeschreibung ein

Und die SSAO-Komponente ändert sich zu:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass ich das Original immer noch ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/auskommentiert habe, um etwas zu sehen.

edit # 3

Wenn ich das Pufferformat für die Textur (und nur dafür) von DXGI_FORMAT_R8G8B8A8_UNORMauf DXGI_FORMAT_R32G32B32A32_FLOATändere, bekomme ich eine schönere normale Textur:

Geben Sie hier die Bildbeschreibung ein

Das Ändern der Vorspannung von 0.0fnach 0.2fund des Probenradius von 10.0fnach 0.2fgab mir außerdem Folgendes:

Geben Sie hier die Bildbeschreibung ein

Es sieht besser aus, nicht wahr? Ich habe jedoch Schatten um Objekte auf der linken Seite und eine Umkehrung davon auf der rechten Seite. Was kann das verursachen?

PolGraphic
quelle
In getRandom () fühlt sich etwas seltsam an. Wenn Sie eine nicht wiederholte Textur abtasten, verwenden Sie [0-1] UV-Koordinaten. Vorausgesetzt, Sie verwenden float2 (1980, 1050), selbst wenn Sie multiplizieren / dividieren, sehe ich nicht, wie Sie mit solchen Formeln [0-1] -Werte zurückerhalten ... Oder Ihre Rauschtextur ist auf Wiederholungsmodus eingestellt ?
VB_overflow
1
Dumme Frage: Sind Ihre Renderziele Gleitkomma? Die Screenshots, die Sie mit Koordinatenräumen gezeigt haben, haben große schwarze Räume, in denen alle drei Koordinaten 0 oder negativ sind. Sie werden alle mit herkömmlichen UNORM-Renderzielen (Fixed Point) auf 0 geklemmt, und Sie erhalten definitiv keine genaue SSAO, wenn Sie die Position / Normalität nicht richtig abtasten können. Sie benötigen technisch gesehen keine Gleitkomma-Renderziele, wenn Sie sich mit der Leistung befassen. Sie können SNORM verwenden oder die Farben manuell neu skalieren / versetzen.
Andon M. Coleman
1
Nein, Sie können keine negativen Werte in speichern DXGI_FORMAT_R8G8B8A8_UNORM. Sie würden die SNORMVersion davon benötigen , oder die Hälfte Ihres Vektorraums wird auf 0 geklemmt . Ich sehe jetzt tatsächlich, dass dies bereits durch Multiplizieren mit 2 und Subtrahieren von negativem 1 sowohl im Normal- als auch im Positionscode behoben ist. Ich empfehle jedoch dringend, dass Sie es sich zur Gewohnheit machen, bevor Sie diese Farben zur Visualisierung auf dem Bildschirm ausgeben * 0.5 + 0.5(damit Sie die negativen Teile Ihres Koordinatenraums sehen können). Da 0.5 + 1.0dies nicht funktionieren würde, würden Ihre Farben 0,5 bis 1,5 (auf 1 geklemmt) gehen.
Andon M. Coleman
1
Das ist eigentlich alles gut. Der Code erledigt dies jetzt vollständig für Sie. Die Normalen werden in der Textur im Bereich von 0,0 bis 1,0 gespeichert , aber nach der Abtastung auf -1,0 bis 1,0 neu skaliert. Ihre Position ist auch. Der 1 - depthTeil sieht für mich jedoch seltsam aus. Ich denke es sollte sein depth - 1, sonst ist dein Tiefenbereich invertiert.
Andon M. Coleman
1
Ich bin überhaupt kein Experte, aber ... Ihre normale Textur enthält kein Blau? AFAIUnterständlich sollte jedes Pixel der normalen Textur diese Gleichung erfüllen: r ^ 2 + g ^ 2 + b ^ 2 = 1 (dh: Länge 1). Ich habe Ihr Bild getestet und es enthält reine schwarze Pixel.
Martijn Courteaux

Antworten:

3

Meines Wissens ist Ihre Positionsausgabe in beiden Fällen falsch. Wenn sollte so aussehen:Geben Sie hier die Bildbeschreibung ein

Oder dieses: Geben Sie hier die Bildbeschreibung ein

Abhängig davon, ob Sie das Koordinatensystem für die linke oder rechte Hand verwenden. Sie sollten einen Positionspuffer erstellen und den Versuch überspringen, ihn aus der Tiefe zu berechnen, bis Sie wissen, was funktioniert und was nicht. Erstellen Sie ein neues Rendertarget im GBuffer-Pass und geben Sie die Position wie folgt aus:

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

Ihr letzter Screenshot sieht aus wie ein normales Problem. Sind Sie sicher, dass sich die Normalen im Ansichtsbereich befinden? Wenn nicht, sollte die Wand rechts dunkel bleiben, auch wenn Sie die Kamera um 180 Grad drehen und die Wand links ist. Ist es oder ist es auf der rechten Seite noch dunkel?

Wenn durch einfaches Verschieben der Ansicht die dunklen Bereiche angezeigt und ausgeblendet werden, liegt höchstwahrscheinlich ein Projektionsproblem vor. So: (es ist die gleiche Ecke eines Raumes) Geben Sie hier die Bildbeschreibung ein

Versuchen Sie, Ihre Projektionsmatrix von links nach rechts oder umgekehrt umzuschalten. Möglicherweise ist sie durcheinander.

Stellen Sie außerdem sicher, dass Sie Normalen nur mit "* 2.0f - 1.0f" dekomprimieren, wenn Sie sie auch mit "* 0.5f + 1.0f" gespeichert haben. Sie haben das in den Kommentaren behandelt, aber es lohnt sich, es noch einmal zu überprüfen.

Stefan Agartsson
quelle