Effet SSAO étrange (mauvaise position / textures normales dans l'espace de vue?)

8

J'essaie de créer un effet SSAO dans mon moteur de jeu (DirectX 11, C ++), basé principalement sur le tutoriel gamedev.net de José María Méndez . Malheureusement, il ne couvre pas le problème de création de texture (normales, position).

Dans la première passe, je crée la texture normale, puis je lis également le tampon de profondeur en tant que texture (c'est possible car je crée la vue des ressources du shader et délie le tampon de profondeur dans la deuxième passe). Les deux doivent être dans l'espace de vue.

Je n'ai pas encore implémenté le flou (je le ferai plus tard), mais j'ai quelques problèmes en ce moment. Je crois que cela est dû à un mauvais calcul des textures ou à une mauvaise méthode de transformation des données à partir de celles - ci , plutôt qu'à des valeurs de formule ou de paramètres incorrectes .

Mes suppositions de ce qui pourrait mal tourner:

  • calcul de la texture normale (dans l'espace de vue) - mais cela semble correct ,
  • calcul de la texture de la position (dans l'espace d'affichage) et transformation de la profondeur en position,
  • calcul de matrice de projection inversée,
  • quelque chose n'est pas normalisé ou saturé et devrait l'être,
  • paramètres (mais ils ne devraient pas avoir un tel impact sur la sortie)?

Mes textures

Diffuse ( textures[0], pas vraiment utilisé ici, juste pour comparaison): entrez la description de l'image ici

Normal ( textures[1], devrait être dans l'espace d'affichage): entrez la description de l'image ici

Je les reçois dans le premier passage du vertex shader (les pixel shaders font juste output.normal = input.normal):

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

Texture du tampon de profondeur ( textures[2]il est difficile de voir quoi que ce soit en raison de faibles différences de valeurs, mais je pense que c'est correct):

entrez la description de l'image ici

Pour l'afficher, j'utilise:

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

Après correction du contraste dans un programme externe (je ne l'utilise pas shader, mais c'est mieux pour les yeux): entrez la description de l'image ici

La description du tampon de profondeur:

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

Et le plan lointain / proche (ils ont un impact sur la texture du tampon de profondeur) sont définis sur nearPlane = 10.0f( 1.0fne change pas trop et les objets ne sont pas si proches de la caméra) et farPlane = 1000.0f.

Sur cette base, je crée la position dans l'espace de vue (je ne l'enregistre pas dans la texture, mais si je la renvoie sur l'écran, cela ressemble à ça): entrez la description de l'image ici

Chaque pixel ici est calculé par:

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;

Et projectionInvertedest transféré au shader avec:

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

Je crois que cela camera->getProjection()retourne une bonne matrice, car j'utilise la même pour construire les viewProjectionMatrixobjets for qui est ok (je vois les bons sommets aux bons endroits). Peut-être quelque chose de transposé?

Aléatoire ( textures[3], normal) - c'est la texture du tutoriel, juste au .tgaformat: entrez la description de l'image ici

La texture aléatoire est sans tuile (elle est répétable, lorsque vous mettez une texture à côté d'une autre). Si je le rends avec getRandom(uv)pour tout l'écran que j'obtiens (le float2résultat s'affiche en rouge et vert): entrez la description de l'image ici

Et le résultat: entrez la description de l'image ici

Il ressemble à "un peu d'ombrage" mais rien à voir avec le composant SSAO (il n'est pas encore flou ou mélangé avec de la lumière / diffuse de toute façon). Je suppose que c'est plus similaire à l'ombrage phong puis au SSAO réel (pas de nuances autour des "coins profonds", etc.)

Le shader SSAO (second passage):

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

Je fournis ces paramètres (j'essayais de jouer avec eux, ils changent la qualité du résultat, etc. mais pas l'effet global):

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)

Notez que j'ai commenté ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;juste parce que de cette façon je peux voir n'importe quoi (dans les autres cas, les différences dans les tonalités des composants SSAO sont trop minuscules - mais cela peut être un problème avec de mauvais paramètres).

modifier # 1

Comme @AndonM.Colemansuggéré, je produis la texture normale à l'écran au textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5flieu de simplement textures[1].Sample(ObjSamplerState, textureCoordinates):

entrez la description de l'image ici

De plus, j'ai essayé de supprimer une *2.0f - 1.0fpartie getNormal(...)et cela m'a donné un autre résultat pour le composant SSAO:

entrez la description de l'image ici

C'est toujours faux, je ne sais pas si c'est plus ou moins proche de "bon". Peut-être, selon @AndonM.Coleman(si je l'ai bien compris), cela a quelque chose à voir avec les formats des tampons? Mes formats sont:

#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

Je ne veux vraiment pas utiliser des formats plus lents si ce n'est pas nécessaire.

modifier # 2

Modification du 1 - depthà la depth - 1sortie nouvelle (et étrange, je suppose) texture de position:

entrez la description de l'image ici

Et le composant SSAO se transforme en:

entrez la description de l'image ici

Notez que j'ai toujours l'original ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/commenté pour voir quoi que ce soit.

modifier # 3

Modification du format de mémoire tampon pour la texture (et seulement pour elle) de DXGI_FORMAT_R8G8B8A8_UNORMla DXGI_FORMAT_R32G32B32A32_FLOATme donne une texture plus agréable normale:

entrez la description de l'image ici

De plus, changer le biais de 0.0fà 0.2fet le rayon de l'échantillon de 10.0fà 0.2fm'a donné:

entrez la description de l'image ici

Ça a l'air mieux, non? Cependant, j'ai des ombres autour des objets à gauche et l'inversion à droite. Qu'est-ce qui peut provoquer ça?

PolGraphic
la source
Quelque chose me semble étrange dans getRandom (), lorsque vous échantillonnez une texture non répétée, vous utiliserez les coordonnées UV [0-1]. Étant donné que vous utilisez float2 (1980,1050), même si vous multipliez / divisez cela, je ne vois pas comment vous récupérerez les valeurs [0-1] avec de telles formules ... Ou votre texture de bruit est définie pour être en mode répétition ?
VB_overflow
1
Question stupide: vos cibles de rendu sont-elles à virgule flottante? Les captures d'écran que vous avez montrées avec des espaces de coordonnées ont de grands espaces noirs où les trois coordonnées sont 0 ou négatives. Ils sont tous fixés à 0 à l' aide de cibles de rendu traditionnelles à virgule fixe (UNORM) et vous n'obtiendrez certainement pas de SSAO précis lorsque vous ne pouvez pas échantillonner correctement la position / normale. Techniquement, vous n'avez pas besoin de cibles de rendu à virgule flottante si vous êtes préoccupé par les performances - vous pouvez utiliser SNORM ou redimensionner / décaler les couleurs manuellement.
Andon M. Coleman
1
Non, vous ne pouvez pas stocker de valeurs négatives dans DXGI_FORMAT_R8G8B8A8_UNORM. Vous auriez besoin de la SNORMversion de cela, ou la moitié de votre espace vectoriel sera limitée à 0 . En fait, je vois maintenant que cela est déjà réglé en multipliant par 2 et en soustrayant le négatif 1 dans le code normal et le code de position. Cependant, je suggère fortement qu'avant de sortir ces couleurs à l'écran pour la visualisation, vous avez l'habitude de le faire * 0.5 + 0.5(afin que vous puissiez voir les parties négatives de votre espace de coordonnées). Quant à 0.5 + 1.0cela ne fonctionnerait pas, vos couleurs passeraient de 0,5 à 1,5 (fixées à 1).
Andon M. Coleman
1
C'est très bien, en fait. Le code s'occupe complètement de cela pour vous en ce moment. Les normales sont enregistrées dans la plage ,0 - 1,0 dans la texture, mais rééchelonnées de -1,0 - 1,0 après échantillonné; votre position l'est aussi. Cependant, la 1 - depthpartie me semble étrange. Je pense que cela devrait être le depth - 1cas, sinon votre plage de profondeur est inversée.
Andon M. Coleman
1
Je ne suis pas du tout expert, mais ... votre texture normale ne contient pas de bleu? AFAI Comprenez que chaque pixel de la texture normale doit satisfaire cette équation: r ^ 2 + g ^ 2 + b ^ 2 = 1 (c'est-à-dire: longueur 1). J'ai testé votre image et elle contient des pixels noirs purs.
Martijn Courteaux

Réponses:

3

À ma connaissance, la sortie de votre position est erronée dans les deux cas. Si devrait ressembler à ceci:entrez la description de l'image ici

Ou ca: entrez la description de l'image ici

Selon que vous utilisez le système de coordonnées gauche ou droit. Vous devez créer un tampon de position et éviter d'essayer de le calculer à partir de la profondeur jusqu'à ce que vous sachiez ce qui fonctionne et ce qui ne fonctionne pas. Créez un nouveau cible de rendu dans la passe GBuffer et sortez simplement la position comme ceci:

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

Votre dernière capture d'écran ressemble à un problème normal, êtes-vous sûr que les normales sont dans l'espace d'affichage? Sinon, le mur de droite doit rester sombre même si vous tournez la caméra de 180 degrés et que le mur est à gauche. Est-ce ou est-il encore sombre du côté droit?

Si le déplacement de la vue fait légèrement apparaître et disparaître les parties sombres, il s'agit très probablement d'un problème de projection. Comme ça: (c'est le même coin d'une pièce) entrez la description de l'image ici

Essayez de changer votre matrice de projection de LH à RH ou vice versa, vous pourriez l'avoir mélangé.

En outre, assurez-vous de ne décompresser les normales qu'avec "* 2.0f - 1.0f" si vous les avez également stockées avec "* 0.5f + 1.0f". Vous en avez parlé dans les commentaires, mais cela vaut la peine d'être vérifié une fois de plus.

Stefan Agartsson
la source