I am trying to implement SSAO using DirectX11 but instead I got white screen with few black dots on model. My suspect is that kernel generation or usage might be wrong. I've tried changing order of TBN matrix and sample multiplication order in pixel shader and it doesn't change results in any way: Final buffer

In fact those "dots" are small models of model itself that appear on it instead of dark creases (Example with sphere model):Sphere model SSAO

My G-Buffers looks like this (Position, normal, tangent, bitangent): Position





//Create kernel for SSAO
std::uniform_real_distribution<float> randomFloats(0.0f, 1.0f);
std::default_random_engine generator;
XMFLOAT3 tmpSample;
for (int i = 0; i < SSAO_KERNEL_SIZE; i++)
    //Generate random vector3 ([-1, 1], [-1, 1], [0, 1])
    tmpSample.x = randomFloats(generator) * 2.0f - 1.0f;
    tmpSample.y = randomFloats(generator) * 2.0f - 1.0f;
    tmpSample.z = randomFloats(generator);

    //Normalize vector3
    XMVECTOR tmpVector;
    tmpVector.m128_f32[0] = tmpSample.x;
    tmpVector.m128_f32[1] = tmpSample.y;
    tmpVector.m128_f32[2] = tmpSample.z;

    tmpVector = XMVector3Normalize(tmpVector);

    tmpSample.x = tmpVector.m128_f32[0];
    tmpSample.y = tmpVector.m128_f32[1];
    tmpSample.z = tmpVector.m128_f32[2];

    //Multiply by random value all coordinates of vector3
    //float randomMultiply = randomFloats(generator);
    //tmpSample.x *= randomMultiply;
    //tmpSample.y *= randomMultiply;
    //tmpSample.z *= randomMultiply;

    //Scale samples so they are more aligned to middle of hemisphere
    float scale = float(i) / 64.0f;
    scale = lerp(0.1f, 1.0f, scale * scale);
    tmpSample.x *= scale;
    tmpSample.y *= scale;
    tmpSample.z *= scale;

    //Pass value to array
    m_ssaoKernel[i] = tmpSample;


Texture2D textures[3]; //position, normal, noise
SamplerState SampleType;


cbuffer KernelBuffer
    float3 g_kernelValue[64];

struct PixelInputType
    float4 positionSV : SV_POSITION;
    float2 tex : TEXCOORD0;
    float4x4 projection : TEXCOORD1;

const float2 noiseScale = float2(1280.0f / 4.0f, 720.0f / 4.0f);
const float radius = 0.5f;
const float bias = 0.025f;
// Pixel Shader
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
    float3 position = textures[0].Sample(SampleType, input.tex).xyz;
float3 normal = normalize(textures[1].Sample(SampleType, input.tex).rgb);
float3 randomVector = normalize(textures[2].Sample(SampleType, input.tex * noiseScale).xyz);

float3 tangent = normalize(randomVector - normal * dot(randomVector, normal));
float3 bitangent = cross(normal, tangent);
float3x3 TBN = { tangent, bitangent, normal };

float3 sample = float3(0.0f, 0.0f, 0.0f);
float4 offset = float4(0.0f, 0.0f, 0.0f, 0.0f);
float occlusion = 0.0f;

for (int i = 0; i < 64; i++)
    sample = mul(TBN, g_kernelValue[i]);
    sample = position + sample * radius;

    offset = float4(sample, 1.0f);
    offset = mul(input.projection, offset);
    offset.xyz /= offset.w;
    offset.xyz = offset.xyz * 0.5f + 0.5f;

    float sampleDepth = textures[0].Sample(SampleType, offset.xy).z;
    occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0);
occlusion = 1.0f - (occlusion / 64.0f);

return float4(occlusion, occlusion, occlusion, 1.0f);
  • 1
    $\begingroup$ First place I would look is at offset.xy used in textures[0].Sample, it looks like its has a high scale factor? Do you use texture wrapping or clamping when sampling the depth buffer, wrapping would explain the tiled small versions of your scene. Also note the values stored in the depth buffer are not in view space, so comparing sampleDepth to sample.z may not work as expected (did I see anothre post from you involving shadow mapping, that also makes the same assumption) , that is my understanding with GLSL, I haven't used HLSL before so I may be wrong. $\endgroup$
    – PaulHK
    Mar 28, 2019 at 3:17

1 Answer 1


Offset.xy is definatelly off because it just return XY coordinates from screenspace: enter image description here

Clamping solved 'small copies of model' on the model itself, thanks!

sampleDepth actually looks like this and indeed is not in viewspace and it may really explain those little models now: enter image description here

Using viewspace (offset = mul(projection, offset) ---> offset = mul(view, offset)) solves depth problem but final buffer is clear white. No effects are actually visible. What next step do you suggest? If depth comparing is correct now, what might be off? enter image description here

UPDATE: Comparision is working but in a wrong way. Below line results in just fully white buffer screen:

occlusion += (sampleDepth >= sample.z + bias ? 1.0 : 0.0);

Changing it to:

occlusion += (sampleDepth >= sample.z - bias ? 1.0 : 0.0);

Actually gives us comparision but it is really bold and doesn't include only holes and creases but whole model. Changing bias to small values like 0.001f doesn't help: enter image description here

Update #2: Directly comparing gives result presented below. However it doesn't seem satisfying enough to use as it seems to present rather edges than creases and holes:

occlusion += (sampleDepth == sample.z ? 1.0 : 0.0);

enter image description here

  • 1
    $\begingroup$ Next thing I would check is if your samples are being generated as you expect. Try reducing it to 1 sample that is somewhere at the edge of your sampling disc and output the view-space position of that sample. Would be useful to have side-by-side with your original view-space position rendering, they should look similar with the offset version looking slightly shifted in terms of position. How large are the disc sampling vectors compared to the size of your mesh ? $\endgroup$
    – PaulHK
    Apr 2, 2019 at 2:11
  • $\begingroup$ I've already made subtle modifications and tests and it turned out that by using around 30 samples and substracting by 10 I am getting best results (github.com/komilll/LEngine/blob/master/LEngine/ssaoShader.ps). However it was only a result of personal tests on given model and might not be general rule. I will try side-by-side comparision. Also does mesh size vs sampling size matters? Shouldn't there be a universal best sample size that doesn't collide with any mesh size? Could you elaborate on that, please? $\endgroup$ Apr 2, 2019 at 6:40
  • 1
    $\begingroup$ Your sampling offsets could be so large that you could miss your model completely so yes, it is related to the mesh size. If my understanding is correct the offsets are added in view coordinate space? I wanted to eliminate the possibility that you're taking samples from somewhere near or beyond the edge of your depth texture. $\endgroup$
    – PaulHK
    Apr 2, 2019 at 6:46
  • 2
    $\begingroup$ I finally got time to test it. THANK YOU. Everything works fine. I tweaked bias and radius values and after getting pleasable values I copied this values into my code and it works great. $\endgroup$ Apr 16, 2019 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.