Hi all,
I am trying to compute bent normals to use for my lighting calculations such as ambient occlusion. The way I understand it is that bent normals point in a direction that represents the "average unoccluded direction" of ambient light around a point on a surface.
For this I am ray marching some directions around the current fragment and checking for intersections. For each direction I am checking if there is an intersection seach step, if not, we increment a counter and if there is we decrement it. To know if this direction never intersected, the counter should matcht the number of steps taken. If there is no intersection for a particular direction we compute the bent normal and the weighting using lambert cosine term.
I am struggling to find some view-space bent normal outputs or resources to verify if my approach is correct and would appreciate any insight or feedback. Thanks in advance.
This is my current output.
vec3 ComputeBentNormal(vec3 samplePos, vec3 sampleDir)
{
RANDOMVALUE = (uv.x * uv.y) * debugRenderer.stepCount;
RANDOMVALUE += fract(debugRenderer.time) * debugRenderer.stepCount;
const int numSteps = debugRenderer.stepCount;
float stepSize = debugRenderer.maxDistance / float(numSteps);
vec3 WorldNormal = normalize(texture(gBuffNormal, uv).xyz);
vec4 viewSpaceNormal = ubo.view * vec4(WorldNormal, 0.0);
viewSpaceNormal = normalize(viewSpaceNormal);
// Convert pos and dir to screen space
vec3 screenPos = worldToScreen(samplePos);
vec3 screenDir = normalize(worldToScreen(samplePos + sampleDir) - screenPos) * stepSize;
vec3 rayPos = screenPos + screenDir * RANDOMVALUE; // Apply jitter
vec3 bentNormal = vec3(0.0);
float totalVisibility = 0.0;
int stepsTaken = 0;
for(int i = 0; i < numSteps; i++)
{
rayPos += screenDir;
if(clamp(rayPos.xy, 0.0, 1.0) != rayPos.xy) break;
rayPos = BinarySearch(rayPos, screenDir);
// Fetch depth at current screen position
float sceneDepth = texture(depthTex, rayPos.xy).x;
float sampleDepth = rayPos.z;
if((sampleDepth - sceneDepth) > 0 && (sampleDepth - sceneDepth) < debugRenderer.thickness)
{
// We intersected
occlusion += 1.0;
stepsTaken -= 1; // reduce this since we intersected while marching
break;
}
// no intersection? increment counter
stepsTaken += 1;
}
// this would mean we stepped numSteps times and didnt intersect anything
if(stepsTaken == numSteps)
{
// Accumulate bent normal
vec4 viewSpaceDir = normalize(ubo.view * vec4(sampleDir, 0.0));
float NdotL = max(dot(viewSpaceNormal.xyz, viewSpaceDir.xyz), 0.0);
bentNormal = bentNormal + viewSpaceDir.xyz * NdotL;
totalVisibility += NdotL;
}
// Normalize bent normal
if (totalVisibility > 0.0) {
bentNormal /= totalVisibility;
bentNormal = normalize(bentNormal);
}
bentNormal;
}
vec4 BentNormal()
{
vec3 WorldPos = texture(gBuffPosition, uv).xyz;
vec3 WorldNormal = normalize(texture(gBuffNormal, uv).xyz);
vec3 bentNormal = vec3(0.0);
// March rays in screen space
float NUM_DIRECTIONS = debugRenderer.numDirections;
for (int i = 0; i < NUM_DIRECTIONS; i++)
{
// Sample random direction
vec2 RandomVals = randomVec2(uv * float(i));
vec3 SampleRandomDirection = CosWeightedHemisphere(WorldNormal, RandomVals);
// Use the compute bent normal
bentNormal =+ ComputeBentNormal(WorldPos, SampleRandomDirection);
}
bentNormal = normalize(bentNormal); // Normalize the bent normal
return vec4(vec3(bentNormal), 1.0);
}