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):
Normal ( textures[1]
sollte im Sichtbereich sein):
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):
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):
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):
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 projectionInverted
wird 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 viewProjectionMatrix
fü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 .tga
Format:
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 float2
angezeigt (das Ergebnis wird rot und grün angezeigt):
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.Coleman
vorgeschlagen, gebe ich die normale Textur auf dem Bildschirm aus, textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5f
anstatt nur textures[1].Sample(ObjSamplerState, textureCoordinates)
:
Außerdem habe ich versucht, ein *2.0f - 1.0f
Teil von zu entfernen, getNormal(...)
und es gab mir ein weiteres Ergebnis für die SSAO-Komponente:
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 - depth
um eine depth - 1
neue (und seltsame, denke ich) Positionstextur auszugeben:
Und die SSAO-Komponente ändert sich zu:
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_UNORM
auf DXGI_FORMAT_R32G32B32A32_FLOAT
ändere, bekomme ich eine schönere normale Textur:
Das Ändern der Vorspannung von 0.0f
nach 0.2f
und des Probenradius von 10.0f
nach 0.2f
gab mir außerdem Folgendes:
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?
DXGI_FORMAT_R8G8B8A8_UNORM
. Sie würden die SNORM
Version 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.0
dies nicht funktionieren würde, würden Ihre Farben 0,5 bis 1,5 (auf 1 geklemmt) gehen.
1 - depth
Teil sieht für mich jedoch seltsam aus. Ich denke es sollte sein depth - 1
, sonst ist dein Tiefenbereich invertiert.