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):
Normal ( textures[1]
, devrait être dans l'espace d'affichage):
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):
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):
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.0f
ne 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):
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 projectionInverted
est 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 viewProjectionMatrix
objets 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 .tga
format:
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 float2
résultat s'affiche en rouge et vert):
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.Coleman
suggéré, je produis la texture normale à l'écran au textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5f
lieu de simplement textures[1].Sample(ObjSamplerState, textureCoordinates)
:
De plus, j'ai essayé de supprimer une *2.0f - 1.0f
partie getNormal(...)
et cela m'a donné un autre résultat pour le composant SSAO:
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 - 1
sortie nouvelle (et étrange, je suppose) texture de position:
Et le composant SSAO se transforme en:
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_UNORM
la DXGI_FORMAT_R32G32B32A32_FLOAT
me donne une texture plus agréable normale:
De plus, changer le biais de 0.0f
à 0.2f
et le rayon de l'échantillon de 10.0f
à 0.2f
m'a donné:
Ç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?
DXGI_FORMAT_R8G8B8A8_UNORM
. Vous auriez besoin de laSNORM
version 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.0
cela ne fonctionnerait pas, vos couleurs passeraient de 0,5 à 1,5 (fixées à 1).1 - depth
partie me semble étrange. Je pense que cela devrait être ledepth - 1
cas, sinon votre plage de profondeur est inversée.Réponses:
À ma connaissance, la sortie de votre position est erronée dans les deux cas. Si devrait ressembler à ceci:
Ou ca:
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:
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)
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.
la source