🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

SSR reflections incorrectly positioned

Started by
0 comments, last by Msta303 3 years, 2 months ago

I have been trying to add a SSR post-processing effect into my engine for a while now, but it always seems to fail on the same thing: Reflections are not properly positioned below the object instead they are skewed and disappears from and to whilst moving the camera.

I'm using a g-buffer system which renders positions and normals in view-space (i have also tried to reconstruct the position from the depth buffer but it gives the same result). Shaders will be listed below.

I suspect that my normals might be wrong, but, I'm also using SSAO with the same buffers which works just fine.

I have read severals tutorials on this topic and tried them, but it always fails with this problem.

Screenshot 1: (reflections shown, but skewed)
Screenshot 2, rotated ~270 degrees, no reflections

G-buffer vertex shader:

#version 450 core

layout (location = 0) in vec3 in_position;
layout (location = 1) in vec3 in_normal;
layout (location = 2) in vec2 in_uv;
layout (location = 3) in vec3 in_tangent;
layout (location = 4) in vec3 in_bitangent;

out vec3 worldPosition;
out vec3 viewNormal;
out vec3 viewPosition;
out vec2 texCoord;
out mat3 TBN;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat3 normal_matrix;

void CalculateTBN(mat4 modelViewMatrix, vec3 tangent, vec3 bitangent, vec3 normal) {
    TBN = mat3(
        normalize(vec3(modelViewMatrix * vec4(tangent, 0.0))),
        normalize(vec3(modelViewMatrix * vec4(bitangent, 0.0))),
        normalize(vec3(modelViewMatrix * vec4(normal, 0.0)))
    );    
}

void main()
{
    vec4 position = vec4(in_position, 1.0);
    vec3 normal = in_normal;

    mat4 modelViewMatrix = view * model;
    CalculateTBN(modelViewMatrix, in_tangent, in_bitangent, normal);

    worldPosition = vec3(model * position);
	viewNormal = vec3(normalize(modelViewMatrix * vec4(normal, 1.0)));

    viewPosition = vec3(view * vec4(worldPosition, 1.0));
    texCoord = in_uv;    
    gl_Position = projection * view * model * position; 
}

G-buffer fragment shader:

#version 450 core

layout (location = 0) out vec3 g_position;
layout (location = 1) out vec3 g_normal;
layout (location = 2) out vec4 g_albedo;
layout (location = 3) out vec3 g_metallness_roughness;
layout (location = 4) out vec4 g_emissive;
layout (location = 5) out float g_depth;

in vec3 worldPosition; // Position in world space
in vec3 viewNormal; // Normal in view space
in vec3 viewPosition; // Position in view space
in vec2 texCoord; 
in mat3 TBN; 

vec2 uv = texCoord;

struct Material {
    float shininess;
    vec3 diffuse_color;
    bool is_solid;

    bool has_specular;
    bool has_normal;
    bool has_emissive;
    bool has_ao;
    bool has_metallic;
    bool has_roughness;
}; 

uniform Material material;
uniform bool force_solid = false;
uniform vec3 force_color = vec3(0.);
uniform float emissive_pow = 1.0;
uniform bool flip_uv = false;
uniform float mesh_transparency = 1.0;
uniform vec3 tint = vec3(0.); 

layout (binding = 0) uniform sampler2D albedoMap;
layout (binding = 1) uniform sampler2D normalMap;
layout (binding = 2) uniform sampler2D metallicMap;
layout (binding = 3) uniform sampler2D roughnessMap;
layout (binding = 4) uniform sampler2D emissiveMap;

float get_metallic(vec2 uv) {
    if (material.has_metallic) return texture(metallicMap, uv).r;
    return 1.;
}

float get_roughness(vec2 uv) {
    if (material.has_roughness) return texture(roughnessMap, uv).r;
    return 1.;
}

vec3 get_emissive(vec2 uv) {
    if (material.has_emissive) return texture(emissiveMap, uv).rgb * emissive_pow;
    return vec3(0.);
}

vec2 get_uv() {
    if (flip_uv) return vec2(uv.x, 1. - uv.y);
    return uv;
}

void main()
{
    vec3 viewNormal;
    bool use_sampler = material.has_normal;
    if (use_sampler) {
	    viewNormal = texture2D(normalMap, texCoord).rgb;
	    viewNormal = normalize(viewNormal * 2.0 - 1.0);
	    viewNormal = normalize(TBN * viewNormal);
    }
    else{
	    viewNormal = viewNormal;    
    }

    g_position = viewPosition;
    g_normal = viewNormal;
    g_albedo.rgb = texture(albedoMap, get_uv()).rgb;

    float spec = (g_albedo.r + g_albedo.g + g_albedo.b)/3.0;
    g_albedo.a = spec;
    g_metallness_roughness.r = get_metallic(get_uv());
    g_metallness_roughness.g = get_roughness(get_uv());
    g_emissive.rgb = get_emissive(get_uv());
    g_emissive.a = mesh_transparency;

    g_depth.r = gl_FragCoord.z;
}

The normal buffer is declared as RGB32F, same for the position buffer.

And the SSR shader is declared like this:
(based on http://imanolfotia.com/blog/update/2017/03/11/ScreenSpaceReflections.html)

#version 450 core

layout (location = 0) uniform sampler2D gAlbedo;
layout (location = 1) uniform sampler2D gPosition;
layout (location = 2) uniform sampler2D gNormal;
layout (location = 3) uniform sampler2D gMetallicRoughness;

out vec4 FragColor;

uniform mat4 invView;
uniform mat4 projection;
uniform mat4 invProjection;
uniform mat4 view;
uniform float near = 0.1;
uniform float far = 100.0;
uniform vec2 resolution = vec2(1440.0, 810.0);
uniform vec3 cameraPos;

float Near = near;
float Far = far;

in vec2 TexCoords;
vec2 TexCoord = TexCoords;
vec2 texCoord = TexCoords;

uniform int raymarch_iterations = 60;
uniform float raymarch_step_size = 0.25;
uniform float raymarch_min_steps = 0.1;
uniform int numBinarySearchSteps = 10;

uniform vec3 skyColor = vec3(0.0);
uniform int binarySearchCount = 20;
uniform float LLimiter = 0.9;

// SSR based on tutorial by Imanol Fotia
// http://imanolfotia.com/blog/update/2017/03/11/ScreenSpaceReflections.html
#define GetPosition(texCoord) texture(gPosition, texCoord).xyz

vec2 BinarySearch(inout vec3 dir, inout vec3 hitCoord, inout float dDepth) {
    float depth;

    vec4 projectedCoord;
 
    for (int i = 0; i < binarySearchCount; i++) {
        projectedCoord = projection * vec4(hitCoord, 1.0);
        projectedCoord.xy /= projectedCoord.w;
        projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
 
        depth = GetPosition(projectedCoord.xy).z;
 
        dDepth = hitCoord.z - depth;

        dir *= 0.5;

        if (dDepth > 0.0) {
            hitCoord += dir;
        } else {
            hitCoord -= dir;
        }
    }

    projectedCoord = projection * vec4(hitCoord, 1.0);
    projectedCoord.xy /= projectedCoord.w;
    projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
 
    return vec2(projectedCoord.xy);
}

vec2 RayCast(vec3 dir, inout vec3 hitCoord, out float dDepth) {
    dir *= raymarch_step_size;
    
    for (int i = 0; i < raymarch_iterations; i++) {
        hitCoord += dir;

        vec4 projectedCoord = projection * vec4(hitCoord, 1.0);
        projectedCoord.xy /= projectedCoord.w;
        projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5; 

        float depth = GetPosition(projectedCoord.xy).z;

        dDepth = hitCoord.z - depth;

        if ((dir.z - dDepth) < 1.2 && dDepth <= 0.0) {
            return BinarySearch(dir, hitCoord, dDepth);
        }
    }

    return vec2(-1.0);
}

#define Scale vec3(.8, .8, .8)
#define k 19.19

vec3 Hash(vec3 a) {
    a = fract(a * Scale);
    a += dot(a, a.yxz + k);
    return fract((a.xxy + a.yxx)*a.zyx);
}

// source: https://www.standardabweichung.de/code/javascript/webgl-glsl-fresnel-schlick-approximation
#define fresnelExp 15.0

float Fresnel(vec3 direction, vec3 normal) {
    vec3 halfDirection = normalize(normal + direction);
    
    float cosine = dot(halfDirection, direction);
    float product = max(cosine, 0.0);
    float factor = 1.0 - pow(product, fresnelExp);
    
    return factor;
}

void main() {
    float reflectionStrength = 1. - texture(gMetallicRoughness, texCoord).r; // metallic in r component
    if (reflectionStrength == 0.0) {
        FragColor = vec4(0., 0., 0., 1.); 
        return;
    }

    vec3 normal = texture(gNormal, texCoord).xyz;
    vec3 viewPos = GetPosition(texCoord);

    vec3 worldPos = vec3(vec4(viewPos, 1.0) * inverse(view));
    vec3 jitt = Hash(worldPos) * texture(gMetallicRoughness, texCoord).g; // roughness in g component

    vec3 reflected = normalize(reflect(normalize(viewPos), normalize(normal)));

    vec3 hitPos = viewPos;
    float dDepth; 
    vec2 coords = RayCast(jitt + reflected * max(-viewPos.z, raymarch_min_steps), hitPos, dDepth);

    float L = length(GetPosition(coords) - viewPos);
    L = clamp(L * LLimiter, 0, 1);
    float error = 1 - L;

    float fresnel = Fresnel(reflected, normal);
    
    vec3 color = texture(gAlbedo, coords.xy).rgb * error * fresnel;

    if (coords.xy != vec2(-1.0)) {
        vec3 res = mix(texture(gAlbedo, texCoord), vec4(color, 1.0), reflectionStrength).rgb;
        FragColor = vec4(res, 1.0);
        return;
    }
    
    vec3 rescol = mix(texture(gAlbedo, texCoord), vec4(skyColor, 1.0), reflectionStrength).rgb;
    FragColor = vec4(rescol, 1.0);
}

If you have faced the same situation, can lead me to some example that can give me more information here, please let me know since I have fighted with this one for over two weeks now. All help is highly appreciated!

Thanks in advance!

This topic is closed to new replies.

Advertisement