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):
Normale ( textures[1]
, dovrebbe essere nello spazio di visualizzazione):
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):
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):
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.0f
non 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ì):
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 projectionInverted
viene 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' viewProjectionMatrix
oggetto for che è ok (vedo i vertici giusti nei punti giusti). Forse qualcosa con trasposizione?
Casuale ( textures[3]
, normale): è la trama del tutorial, solo nel .tga
formato:
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 float2
risultato viene visualizzato in rosso e verde):
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.Coleman
suggerito, ho prodotto la normale trama su schermo con textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5f
invece di solo textures[1].Sample(ObjSamplerState, textureCoordinates)
:
Inoltre, ho provato a rimuovere la *2.0f - 1.0f
parte da getNormal(...)
e mi ha dato un altro risultato per il componente SSAO:
È 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 - depth
in depth - 1
output nuova (e strana, immagino):
E il componente SSAO cambia in:
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_UNORM
a DXGI_FORMAT_R32G32B32A32_FLOAT
mi dà una trama normale più bella:
Inoltre, cambiando il bias da 0.0f
a 0.2f
e il raggio di campionamento da 10.0f
a 0.2f
mi ha dato:
Sembra meglio, no? Tuttavia, ho delle ombre attorno agli oggetti a sinistra e l'inversione a destra. Cosa può causare questo?
DXGI_FORMAT_R8G8B8A8_UNORM
. Avresti bisogno della SNORM
versione 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.0
quanto non funzionerebbe, i tuoi colori andrebbero da 0,5 a 1,5 (fissati a 1).
1 - depth
parte mi sembra strana. Penso che dovrebbe essere depth - 1
, altrimenti la tua gamma di profondità è invertita.