12
\$\begingroup\$

I'm using a SSAO alghorithm called "SAO", seen here: http://graphics.cs.williams.edu/papers/SAOHPG12/

I'm currently having issues with "smooth" normals, for example when using normal/height maps.

To give an example, here is the SAO and the normals for a chair rendered with "raw" normals, i.e not using the accompanied normal map:

sao1

normal1

Looks pretty OK overall.

now.. when I apply the chairs normal map:

sao2

normal2

What could be wrong? Is there anything obvious wrong with the normals?

I'm using the SAO shader, with only very minor changes. The normal/position textures are in world space, and then converted to view space. I know this is not optimal, but for the sake of the alghorithm it should not matter.

#ifndef SSAO_PIXEL_HLSL
#define SSAO_PIXEL_HLSL

#include "Constants.h"
#include "Common.hlsl"

static const float gNumSamples = 11.0;
static const float gRadius = 0.2;
static const float gRadius2 = gRadius * gRadius;
static const float gProjScale = 500.0;
static const float gNumSpiralTurns = 7;
static const float gBias = 0.01;
static const float gIntensity = 1.0;


cbuffer SSAOCBuffer : register(CBUFFER_REGISTER_PIXEL)
{
    float4x4 gViewProjMatrix;
    float4x4 gProjMatrix;
    float4x4 gViewMatrix;
    float2 gScreenSize;
};

Texture2D gPositionTexture : register(TEXTURE_REGISTER_POSITION);
Texture2D gNormalTexture : register(TEXTURE_REGISTER_NORMAL);
SamplerState gPointSampler : register(SAMPLER_REGISTER_POINT);


float3 reconstructNormal(float3 positionWorldSpace)
{
    return normalize(cross(ddx(positionWorldSpace), ddy(positionWorldSpace)));
}

/** Read the camera - space position of the point at screen - space pixel ssP + unitOffset * ssR.Assumes length(unitOffset) == 1 */
float3 getOffsetPosition(int2 ssC, float2 unitOffset, float ssR) {
    // Derivation:
    //  mipLevel = floor(log(ssR / MAX_OFFSET));

    // TODO: mip levels
    int mipLevel = 0; //TODO: clamp((int)floor(log2(ssR)) - LOG_MAX_OFFSET, 0, MAX_MIP_LEVEL);

    int2 ssP = int2(ssR*unitOffset) + ssC;

    float3 P = gPositionTexture[ssP].xyz;

    P = mul(gViewMatrix, float4(P, 1.0)).xyz;

    return P;
}

float2 tapLocation(int sampleNumber, float spinAngle, out float ssR)
{
    // Radius relative to ssR
    float alpha = float(sampleNumber + 0.5) * (1.0 / gNumSamples);
    float angle = alpha * (gNumSpiralTurns * 6.28) + spinAngle;

    ssR = alpha;
    return float2(cos(angle), sin(angle));
}

float sampleAO(uint2 screenSpacePos, float3 originPos, float3 normal, float ssDiskRadius, int tapIndex, float randomPatternRotationAngle)
{
    float ssR;
    float2 unitOffset = tapLocation(tapIndex, randomPatternRotationAngle, ssR);
    ssR *= ssDiskRadius;

    // The occluding point in camera space
    float3 Q = getOffsetPosition(screenSpacePos, unitOffset, ssR);

    float3 v = Q - originPos;

    float vv = dot(v, v);
    float vn = dot(v, normal);

    const float epsilon = 0.01;
    float f = max(gRadius2 - vv, 0.0); 

    return f * f * f * max((vn - gBias) / (epsilon + vv), 0.0);
}

float4 ps_main(float4 position : SV_Position) : SV_Target0
{
    uint2 screenSpacePos = (uint2)position.xy;

    float3 originPos = gPositionTexture[screenSpacePos].xyz;
    originPos = mul(gViewMatrix, float4(originPos, 1.0)).xyz;
    float3 normal = gNormalTexture[screenSpacePos].xyz;//reconstructNormal(originPos);
    normal = mul(gViewMatrix, float4(normal, 0.0)).xyz;

    // Hash function used in the HPG12 AlchemyAO paper
    float randomPatternRotationAngle = (3 * screenSpacePos.x ^ screenSpacePos.y + screenSpacePos.x * screenSpacePos.y) * 10;
    float ssDiskRadius = -gProjScale * gRadius / originPos.z;

    float ao = 0.0;
    for (int i = 0; i < gNumSamples; i++)
    {
        ao += sampleAO(screenSpacePos, originPos, normal, ssDiskRadius, i, randomPatternRotationAngle);
    }

    float temp = gRadius2 * gRadius;
    ao /= temp * temp;

    float A = max(0.0, 1.0 - ao * gIntensity * (5.0 / gNumSamples));

    return A;
}

#endif

EDIT: update, using reconstructNormals() to reconstruct normals - for simpler models it looks okay (the chair) but the more advanced models like the alien looks really bad/blocky. Any way to fix this?

\$\endgroup\$
  • 6
    \$\begingroup\$ In general you should use geometry normal and not normalmap for SSAO. \$\endgroup\$ – JarkkoL Oct 14 '14 at 23:19
  • \$\begingroup\$ Why is that? I havn't found any sources on that. Also, the model importer I use, assimp (assimp.sourceforge.net), has an option to calculate "smooth" normals, with the same result as using a normal map. Are these not considered geometry normals? \$\endgroup\$ – KaiserJohaan Oct 15 '14 at 7:46
  • \$\begingroup\$ No, smooth normals are calculated by averaging triangle normals attached to a vertex. What you need is the actual triangle normals, which in your shader is calculated in the reconstructNormal() function. You need the actual geometry normals because SSAO is calculated using depth samples in depth buffer and their relation to given pixel. Using wrong normal will result wrong weighting for the samples, e.g. think of a depth sample on the same geometry plane as the pixel whose SSAO you are calculating. \$\endgroup\$ – JarkkoL Oct 15 '14 at 16:21
  • 1
    \$\begingroup\$ reconstructed normals looks really jagged/blocky on some advanced models, edited first post. Is it really viable to use that? \$\endgroup\$ – KaiserJohaan Oct 16 '14 at 17:10
  • 4
    \$\begingroup\$ Looks wrong that convex shape (head) has self-occlusion along the polygon edges. Could it be that your cross product in reconstructNormal() is wrong way around? \$\endgroup\$ – JarkkoL Oct 16 '14 at 23:13

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.