Saya mencoba membuat efek SSAO di mesin gim saya (DirectX 11, C ++), terutama berdasarkan pada tutorial gamedev.net oleh José María Méndez . Sayangnya, itu tidak mencakup masalah menciptakan tekstur (normal, posisi).
Pada pass pertama saya membuat tekstur normal dan kemudian saya juga membaca buffer kedalaman sebagai tekstur (itu mungkin karena saya membuat tampilan sumber daya shader dan melepaskan ikatan buffer kedalaman di pass kedua). Keduanya harus dalam ruang tampilan.
Saya belum menerapkan blur (saya akan melakukannya nanti), tetapi saya memiliki beberapa masalah sekarang. Saya percaya itu karena kesalahan perhitungan tekstur atau metode yang salah dalam mentransformasikan data dari data tersebut , dan bukannya nilai formula atau parameter yang salah .
Tebakan saya tentang apa yang bisa salah:
- perhitungan tekstur normal (dalam ruang tampilan) - tetapi terlihat ok ,
- perhitungan tekstur posisi (dalam ruang tampilan) & transformasi kedalaman ke posisi,
- perhitungan matriks proyeksi terbalik,
- sesuatu tidak dinormalisasi atau jenuh dan seharusnya,
- parameter (namun mereka seharusnya tidak memiliki dampak pada output)?
Tekstur saya
Diffuse ( textures[0]
, tidak benar-benar digunakan di sini, hanya untuk perbandingan):
Normal ( textures[1]
, harus berada dalam ruang tampilan):
Saya mendapatkannya di pass vertex shader pertama (pixel shaders do just output.normal = input.normal
):
output.normal = float4(mul(input.normal, (float3x3)world), 1);
output.normal = float4(mul(output.normal, (float3x3)view), 1);
Tekstur buffer kedalaman ( textures[2]
, sulit untuk melihat apa pun karena perbedaan nilai yang rendah, tapi saya yakin tidak apa-apa):
Untuk menampilkannya saya gunakan:
float depth = textures[2].Sample(ObjSamplerState, textureCoordinates).r;
depth = (depth + 1.0f) / 2.0f;
color.rgb = float3(depth, depth, depth);
Setelah koreksi kontras dalam program eksternal (saya tidak menggunakannya shader, tetapi lebih baik untuk mata):
Deskripsi buffer kedalaman:
//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;
Dan pesawat jauh / dekat (mereka memiliki dampak pada tekstur buffer kedalaman) diatur ke nearPlane = 10.0f
( 1.0f
tidak berubah terlalu banyak dan objek tidak begitu dekat dengan kamera) dan farPlane = 1000.0f
.
Berdasarkan itu saya membuat posisi di ruang tampilan (saya tidak menyimpannya ke tekstur, tetapi jika saya output ke layar terlihat seperti itu):
Setiap piksel di sini dihitung dengan:
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;
Dan projectionInverted
ditransfer ke shader dengan:
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);
Saya percaya bahwa camera->getProjection()
mengembalikan matriks yang baik, karena saya menggunakan yang sama untuk membangun viewProjectionMatrix
objek yang ok (saya melihat simpul yang tepat di tempat yang tepat). Mungkin sesuatu dengan transpos?
Acak ( textures[3]
, normal) - tekstur dari tutorial, hanya dalam .tga
format:
Tekstur acak adalah ubin-kurang (itu berulang, ketika Anda meletakkan satu tekstur di sebelah yang lain). Jika saya merendernya getRandom(uv)
untuk seluruh layar yang saya dapatkan ( float2
hasilnya ditampilkan sebagai merah dan hijau):
Sepertinya "beberapa bayangan" tetapi tidak seperti komponen SSAO (belum buram atau dicampur dengan cahaya / difus pula). Saya kira itu lebih mirip dengan phong shading kemudian ke SSAO yang sebenarnya (tidak ada nuansa "sudut dalam" dll.)
Shader SSAO (lulus kedua):
//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);
}
Saya memberikan parameter tersebut (saya mencoba untuk bermain dengan mereka, mereka mengubah kualitas hasil, dll. Tapi bukan efek keseluruhan):
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)
Perhatikan bahwa saya berkomentar ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/;
hanya karena cara itu saya bisa melihat apa pun (dalam hal lain perbedaan dalam nada komponen SSAO terlalu kecil - tetapi itu bisa menjadi masalah dengan parameter yang salah).
edit # 1
Seperti yang @AndonM.Coleman
disarankan, saya menampilkan tekstur normal ke layar dengan textures[1].Sample(ObjSamplerState, textureCoordinates) *0.5f + 0.5f
bukan hanya textures[1].Sample(ObjSamplerState, textureCoordinates)
:
Juga, saya mencoba untuk menghapus *2.0f - 1.0f
bagian dari getNormal(...)
dan itu memberi saya hasil lain untuk komponen SSAO:
Itu masih salah, saya tidak tahu apakah itu lebih dekat atau lebih jauh dari "baik". Mungkin, menurut @AndonM.Coleman
(jika saya memahaminya) itu ada hubungannya dengan format buffer? Format saya adalah:
#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
Saya benar-benar tidak ingin menggunakan format yang lebih lambat jika tidak perlu.
edit # 2
Mengubah 1 - depth
untuk depth - 1
menampilkan tekstur posisi baru (dan aneh, saya kira):
Dan komponen SSAO berubah menjadi:
Perhatikan bahwa saya masih memiliki ao /= (float)iterations*4.0; ao /= (float)parameters[1]/*sampleRad*/
komentar asli untuk melihat apa pun.
edit # 3
Mengubah format buffer untuk tekstur (dan hanya untuk itu) dari DXGI_FORMAT_R8G8B8A8_UNORM
untuk DXGI_FORMAT_R32G32B32A32_FLOAT
memberi saya tekstur normal yang lebih baik:
Juga, mengubah bias dari 0.0f
ke 0.2f
dan sampel radius dari 10.0f
ke 0.2f
memberi saya:
Terlihat lebih baik, bukan? Namun, saya memiliki bayangan di sekitar objek di sebelah kiri dan inversinya di sebelah kanan. Apa yang menyebabkannya?
DXGI_FORMAT_R8G8B8A8_UNORM
. Anda membutuhkan SNORM
versi itu, atau setengah dari ruang vektor Anda akan dijepit ke 0 . Saya benar-benar melihat sekarang bahwa ini sudah diatasi dengan mengalikan 2 dan mengurangi 1 negatif baik dalam kode normal dan posisi. Namun, saya sangat menyarankan bahwa sebelum mengeluarkan warna-warna ini ke layar untuk visualisasi, Anda membuat kebiasaan * 0.5 + 0.5
(sehingga Anda dapat melihat bagian negatif dari ruang koordinat Anda). Sedangkan untuk 0.5 + 1.0
itu tidak akan berhasil, warna Anda akan menjadi 0,5 hingga 1,5 (dijepit ke 1).
1 - depth
bagian itu terlihat aneh bagi saya. Saya pikir itu harus depth - 1
, jika rentang kedalaman Anda terbalik.