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.
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!