I'm using a SSAO alghorithm called "SAO", seen here: Scalable Ambient Obscurance.
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:
Looks pretty OK overall.
now.. when I apply the chair's normal map:
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?