Skip to main content
GameDev.net gamedev.net
🔒 Locked

Screen-Space-Reflections - OpenGL/Glsl - Any tips/help?

Started by AntRWells Mar 15, 2019 at 11:12 PM 16 replies 16.5k views
Original Post
AntRWells
AntRWells

Hi,

I am trying to add a SSR post-processor to my engine, and although I've sound several samples on the net, one reason or another stops me from getting them working

within my engine. here is the basic shader I use, along with some questions. I'm sure the shader would work, I'm just not sure i'm passing the correct data,

as the tutorials/samples never included C++ code, just the shader code.



out vec3 color;

uniform mat4 invView;
uniform mat4 proj;
uniform mat4 invProj;
uniform mat4 view;



uniform sampler2D tDepth;
uniform sampler2D tNorm;
uniform sampler2D tFrame;


noperspective in vec2 UV;

const float step = 0.1;
const float minRayStep = 0.1;
const float maxSteps = 30;
const int numBinarySearchSteps = 5;
const float reflectionSpecularFalloffExponent = 3.0;

float Metallic;

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


vec3 CalcViewPosition(in vec2 TexCoord)
{
    // Combine UV & depth into XY & Z (NDC)
    vec3 rawPosition                = vec3(TexCoord, texture(tDepth, TexCoord).r);

    // Convert from (0, 1) range to (-1, 1)
    vec4 ScreenSpacePosition        = vec4( rawPosition * 2 - 1, 1);

    // Undo Perspective transformation to bring into view space
    vec4 ViewPosition               = invProj * ScreenSpacePosition;

    // Perform perspective divide and return
    return                          ViewPosition.xyz / ViewPosition.w;
}




vec3 PositionFromDepth(float depth) {
    float z = depth * 2.0 - 1.0;

    vec4 clipSpacePosition = vec4(UV * 2.0 - 1.0, z, 1.0);
    vec4 viewSpacePosition = invProj * clipSpacePosition;

    // Perspective division
    viewSpacePosition /= viewSpacePosition.w;

    return viewSpacePosition.xyz;
}

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

    vec4 projectedCoord;
 
    for(int i = 0; i < numBinarySearchSteps; i++)
    {

        projectedCoord = proj * vec4(hitCoord, 1.0);
        projectedCoord.xy /= projectedCoord.w;
        projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
 
        depth = texture(tDepth, projectedCoord.xy).z;

 
        dDepth = hitCoord.z - depth;

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

        projectedCoord = proj * vec4(hitCoord, 1.0);
        projectedCoord.xy /= projectedCoord.w;
        projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
 
    return vec3(projectedCoord.xy, depth);
}

vec4 RayMarch(vec3 dir, inout vec3 hitCoord, out float dDepth)
{

    dir *= step;
 
 
    float depth;
    int steps;
    vec4 projectedCoord;

 
    for(int i = 0; i < maxSteps; i++)
    {
        hitCoord += dir;
 
        projectedCoord = proj * vec4(hitCoord, 1.0);
        projectedCoord.xy /= projectedCoord.w;
        projectedCoord.xy = projectedCoord.xy * 0.5 + 0.5;
 
        depth = texture(tDepth, projectedCoord.xy).z;
        if(depth > 1000.0)
            continue;
 
        dDepth = hitCoord.z - depth;

        if((dir.z - dDepth) < 1.2)
        {
            if(dDepth <= 0.0)
            {   
                vec4 Result;
                Result = vec4(BinarySearch(dir, hitCoord, dDepth), 1.0);

                return Result;
            }
        }
        
        steps++;
    }
 
    
    return vec4(projectedCoord.xy, depth, 0.0);
}

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}


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

void main(){

    vec3 viewNormal = vec3(texture(tNorm,UV) * invView);
    vec3 viewPos = CalcViewPosition(UV);
    vec3 albedo = texture(tFrame,UV).rgb;

    Metallic = 0.4f;



    float spec = 0.2f;


   vec3 F0 = vec3(0.04); 
    F0      = mix(F0, albedo, Metallic);
    vec3 Fresnel = fresnelSchlick(max(dot(normalize(viewNormal), normalize(viewPos)), 0.0), F0);


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

  vec3 hitPos = viewPos;
    float dDepth;
 
    vec3 wp = vec3(vec4(viewPos, 1.0) * invView);
    vec3 jitt = mix(vec3(0.0), vec3(hash(wp)), spec);
    vec4 coords = RayMarch((vec3(jitt) + reflected * max(minRayStep, -viewPos.z)), hitPos, dDepth);
 
    


    vec2 dCoords = smoothstep(0.2, 0.6, abs(vec2(0.5, 0.5) - coords.xy));
 
      float screenEdgefactor = clamp(1.0 - (dCoords.x + dCoords.y), 0.0, 1.0);

    float ReflectionMultiplier = pow(Metallic, reflectionSpecularFalloffExponent) * 
                screenEdgefactor * 
                -reflected.z;
 
    // Get color
    vec3 SSR = texture(tFrame, coords.xy).rgb;// * clamp(ReflectionMultiplier, 0.0, 0.9) * Fresnel;  



    color = SSR;


}

So my questions are,


1) Matrixes passed.

uniform mat4 invView; - This is the View matrix, this means the world matrix of the camera? Before or after inverstion?
uniform mat4 proj; - The project matrix? I.e the Perspective matrix
uniform mat4 invProj; - inverse of above?
uniform mat4 view; - view matrix not inverted?


uniform sampler2D tDepth; - the depth of the scene, rendered from camera position? 0.1 0 = minZ 1.0 = maxZ
uniform sampler2D tNorm; - the world normals of rendered from camera? I.e 0.5 + normal * 0.5;?
uniform sampler2D tFrame; - the final color buffer to read the reflected pixel from?


I've tried several shaders, tried re-implemented here and there based on other shaders, but the final result is always very off, i mean just not working at all?


If you're not sure what's wrong with the above shader could you link me to a working shader, prefably with c++ code so I can understand it fully, not just the final shader?


Cheers.


Thanks, Antony.
AntRWells
AntRWells

Cheers looks good - Can you link me to the code that sets up the data for the inputs? I.e the position/normals. I understand it's in view space, but I'm not exactly sure how to set that up? (I.e how to define the matrixes, and how to render those maps.)

Thanks, Antony.
congard
congard

To create matrices used library glm.

Short algorithm:

1. In the main rendering pass (that is when we draw meshes) we use MRT to draw several textures at once in a single pass:


layout(location = 0) out vec4 fragColor;
layout(location = 1) out float dofBuffer;
layout(location = 2) out vec3 normalBuffer;
layout(location = 3) out vec2 ssrValuesBuffer;
layout(location = 4) out vec3 positionBuffer;
...
in vec4 v_Position; // Position for this fragment in world space
in vec3 v_Normal; // Interpolated normal for this fragment
...
in mat4 vmMatrix; // view model matrix
...
    fragPos = vec3(vmMatrix * v_Position); // Transform the vertex into eye space
    norm = vec3(normalize(vmMatrix * vec4(v_Normal, 0)));
    ...
    normalBuffer = norm;
    positionBuffer = fragPos;
    ssrValuesBuffer.r = texture(material.reflectionStrength, v_Texture).r;
    ssrValuesBuffer.g = texture(material.jitter, v_Texture).r;

Source: https://gitlab.com/congard/algine/blob/master/src/resources/shaders/fragment_shader.glsl

2. Next, simply bind the rendered textures to SSR shader

You may also not need ssrValuesBuffer at all, so you can simply omit this moment.

Here how can I create view and projection matrix:


glm::vec3 cameraPos(-9, 8, 7);
glm::vec3 cameraLookAt(0, 4, 0);
glm::vec3 cameraUp(0, 1, 0);

glm::mat4 projectionMatrix = glm::perspective(glm::radians(90.0f), (float) WIN_W / (float) WIN_H, 1.0f, 32.0f);
glm::mat4 viewMatrix = glm::lookAt(cameraPos, cameraLookAt, cameraUp);

I hope this will be useful for you.

AntRWells
AntRWells

ok thanks that helps, just to be clear though could you answer these questions? It's what's causing me problems.


1. When rendering the position buffer, how do I derieve the values that become the rgb of each pixel?

i.e - out = vec3(worldX,worldY,worldZ) - i ask because it can only be 0..1 so i'm not sure how to convert the world position to this. I've heard it can be done in view space - but i'm not sure how to convert a world coord into view space.


2. similar question, how do I convert the normal of each fragment, into something that can be packed into a rgb and also then used in the SSR shader


if all of the above is done within your code, I'll just go through that.


Cheers.

Thanks, Antony.
congard
congard

It is not at all necessary for us to store values within 0..1, since we do not display them on the screen, but use them in further stages of rendering. For normalBuffer and positionBuffer, you must also use GL_RGB16F

AntRWells
AntRWells

oh ok i never thought that was possible. so I could for example set the rgb to - col.r = 200; and when used later as data it would still be 200? ok that should be enough for me to try, thanks for your help.

Thanks, Antony.
AntRWells
AntRWells

One more question for anyone - based on congards code, the positions and normals need to be in view space. While I understand the concept, how do I actually convert them from world space? either in shader or c++?

Thanks, Antony.
AntRWells
AntRWells

Ok that sounds easy enough, thanks for the link too.

Thanks, Antony.
AntRWells
AntRWells

Hey,

I've got it working almost, but there are several morphing artifacts. it retains it's overall integrity as I move the camera around, so I assume the data is correct, but still these artifacts get worse or better depending on angle.

And also I removed any of the code that causes to mix well, so the reflection is just full bright, so the artifacts are more easily visible.

Any ideas what I could change to fix it? Here is the shader, mostly based on yours congard, but changed to work for my engine.


#version 330 core

//in vec2 UV;

out vec3 color;

uniform mat4 proj,view;




uniform sampler2D tFrame;
uniform sampler2D tNorm;
uniform sampler2D tPos;

in vec2 UV;

uniform vec3 ssr_skyColor = vec3(0.0);
uniform int ssr_binarySearchCount = 10;
uniform int ssr_rayMarchCount = 30; // 60
uniform float ssr_step = 0.05; // 0.025
uniform float ssr_LLimiter = 0.1;
uniform float ssr_minRayStep = 0.2;


#define getPosition(UV) texture(tPos, UV).xyz

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

    vec4 projectedCoord;
 
    for(int i = 0; i < ssr_binarySearchCount; i++) {
        projectedCoord = proj * 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 = proj * 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 *= ssr_step;
    
    for (int i = 0; i < ssr_rayMarchCount; i++) {
        hitCoord += dir;

        vec4 projectedCoord = proj * 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);
}

#define fresnelExp 5.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 = 0.7f;

    vec3 normal = texture(tNorm, UV).xyz;
    vec3 viewPos = getPosition(UV);

    vec3 worldPos = vec3(vec4(viewPos, 1.0) * inverse(view));
//    vec3 jitt = hash(worldPos) * texture(ssrValuesMap, texCoord).g;


    // Reflection vector
    vec3 reflected = normalize(reflect(normalize(viewPos), normalize(normal)));

    // Ray cast
    vec3 hitPos = viewPos;
    float dDepth; 
    vec2 coords = rayCast(reflected * max(-viewPos.z, ssr_minRayStep), hitPos, dDepth);

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

    float fresnel = fresnel(reflected, normal);
    
    vec3 fc = texture(tFrame, coords.xy).rgb;// * error * fresnel;

    // fragColor = vec3(fresnel);
    // return;

    if (coords.xy != vec2(-1.0)) {
        color = fc;
       return;
    }


    color = texture(tFrame,UV).rgb*2;



}


reflectError1.jpg

Thanks, Antony.
congard
congard

Do not forget that screen space effects are solid information lost. The artifacts are really strange. How exactly do you "set up" textures (ie, what format, GL_RGB16/32F or something else?)? Also try to increase ssr_rayMarchCount and ssr_binarySearchCount, reduce ssr_step. Experience shows that these values should change depending on the complexity of your scene. And in general, I think that cubemap reflections or planar reflections would be good for such a scene since there are a lot of "lower" parts

AntRWells
AntRWells

Yeah I understand that but the engine is a part of a unity like ide, so it's just a nice feature that can be turned on/off(I.e not specific to the above scene)

And yeah here is a bunch of code that you might see the problem in?

This is the framebuffer(Rendertarget) and yeah I use RGB16f for both normal and position map. the color map is just normal rgb.


  public class FrameBufferColor
    {
        public int FBO = 0;
        public Texture2D BB;
        public TextureDepth DB;
        public int IW, IH;
        public int DRB = 0;

        public FrameBufferColor ( int w, int h,TextureFormat format = TextureFormat.Normal)
        {
            IW = w;
            IH = h;
            FBO = GL.GenFramebuffer ( );
            GL.BindFramebuffer ( FramebufferTarget.Framebuffer, FBO );
            BB = new Texture2D ( w, h, false,format );
            DB = new TextureDepth ( w, h );
            DRB = GL.GenRenderbuffer ( );
            GL.BindRenderbuffer ( RenderbufferTarget.Renderbuffer, DRB );
            GL.RenderbufferStorage ( RenderbufferTarget.Renderbuffer, RenderbufferStorage.DepthComponent, w, h );
            GL.FramebufferRenderbuffer ( FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, RenderbufferTarget.Renderbuffer, DRB );
            GL.FramebufferTexture ( FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, BB.ID, 0 );
            DrawBuffersEnum db = DrawBuffersEnum.ColorAttachment0;
            GL.DrawBuffers ( 1, ref db );
            if ( GL.CheckFramebufferStatus ( FramebufferTarget.Framebuffer ) != FramebufferErrorCode.FramebufferComplete )
            {
                Console.WriteLine ( "Framebuffer failure." );
              
            }
            Console.WriteLine ( "Framebuffer success." );
            GL.BindFramebuffer ( FramebufferTarget.Framebuffer, 0 );
            GL.BindRenderbuffer ( RenderbufferTarget.Renderbuffer, 0 );
        }

        public void Bind ( )
        {
            GL.BindFramebuffer ( FramebufferTarget.Framebuffer, FBO );
            GL.Viewport ( 0, 0, IW, IH );
            AppInfo.RW = IW;
            AppInfo.RH = IH;
            GL.ClearColor ( 0, 0, 0, 0 );
            GL.Clear ( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
        }
        public void BindFree()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, FBO);
            GL.ClearColor(0, 0, 0, 0);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
        }
        public void ReleaseFree()
        {
            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
        }
        public void Release ( )
        {
            GL.BindFramebuffer ( FramebufferTarget.Framebuffer, 0 );
            GL.Viewport ( 0, 0, AppInfo.W, AppInfo.H );
            AppInfo.RW = AppInfo.W;
            AppInfo.RH = AppInfo.H;
        }
    }


Here is the class that is used to create the effect.


    public class SSRCompositer : Compositer
    {

        public SSRCompositer() : base(3)
        {

            Name = "SSRPP";

            InputFrame = new FrameTypes.FrameColor();

            Types[1] = new FrameTypes.FramePositionMap();



            Types[0] = new FrameTypes.FrameNormalMap();


            Types[2] = new FrameTypes.FrameEffect();

            dynamic f2 = Types[2];

            f2.FX = new VESSR();
            

            Types[2].TexBind.Add(InputFrame);
            Types[2].TexBind.Add(Types[0]);
            Types[2].TexBind.Add(Types[1]);

            OutputFrame = Types[2];
            Blend = FrameBlend.Solid;

        }
        
    }

    public class VESSR : Effect.Effect3D
    {
        public float Blur = 0.5f;

        public VESSR() : base("", "data/Shader/vsSSR.glsl", "data/Shader/fsSSR.glsl")
        {
        }

        public override void SetPars()
        {

            //          SetMat("pMatrix", Scene.SceneGraph3D.LastCam.ProjMat);
            //            SetMat("InvPMatrix", Scene.SceneGraph3D.LastCam.ProjMat.Inverted());
     
            SetMat("proj", Scene.SceneGraph3D.LastCam.ProjMat);

            SetMat("view", Scene.SceneGraph3D.LastCam.CamWorld);


            SetTex("tFrame", 0);
            SetTex("tNorm", 1);
            SetTex("tPos", 2);

        }
    }

As you can see, it renders the normal layer, then position, then color, then passes it to the SSR effect/shader. which has proj/view unjiforms, and the 3 textures.

Here is the normal shaders that generate the normal buffers.


-Vertex
#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoords;
layout(location = 2) in vec3 aNormal;
layout(location = 3) in vec3 aBiTangent;
layout(location = 4) in vec3 aTangent;

// Values that stay constant for the whole mesh.
uniform mat4 view;
uniform mat4 model;
uniform mat4 proj;
uniform mat4 view2;



out vec3 fVert;
out vec3 fNorm;

void main(){

	
	fVert = vec3(model * vec4(aPos,1.0));


	mat4 normalMatrix = view * model;
    	vec3 N = vec3(normalize(normalMatrix * vec4(aNormal,0.0)));
    fNorm = N;


		gl_Position = proj * view * model* vec4(aPos,1.0);

	
}

-Fragment

#version 330 core

uniform mat4 model;
uniform vec3 camP;
uniform float minZ;
uniform float maxZ;




in vec3 fVert;
in vec3 fNorm;

// Ouput data
out vec3 color;

void main(){
 


    color = fNorm;
}

Here is the position map generator.


-vertex

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aTexCoords;
layout(location = 2) in vec3 aNormal;
layout(location = 3) in vec3 aBiTangent;
layout(location = 4) in vec3 aTangent;

// Values that stay constant for the whole mesh.
uniform mat4 view;
uniform mat4 model;
uniform mat4 proj;



out vec3 fVert;
out vec3 fNorm;

void main(){

	
     mat4 tMat = view * model;

     vec3 fP = vec3(tMat * vec4(aPos,1.0));

    fVert = fP;


		gl_Position = proj * view * model* vec4(aPos,1.0);

	
}

-fragment

#version 330 core

uniform mat4 model;
uniform vec3 camP;
uniform float minZ;
uniform float maxZ;




in vec3 fVert;
in vec3 fNorm;

// Ouput data
out vec3 color;

void main(){
 
 

    color = fVert;
}


And here is the SSR shader. mostly based on yours with a few changes.


-vertex

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vP;

// Output data ; will be interpolated for each fragment.
out vec2 UV;

void main(){
	gl_Position =  vec4(vP,1);
	UV = (vP.xy+vec2(1,1))/2.0;
}

-fragment
  
#version 330 core

//in vec2 UV;

out vec3 color;

uniform mat4 proj,view;




uniform sampler2D tFrame;
uniform sampler2D tNorm;
uniform sampler2D tPos;

in vec2 UV;


uniform vec3 ssr_skyColor = vec3(0.0);
uniform int ssr_binarySearchCount = 10;
uniform int ssr_rayMarchCount = 30; // 60
uniform float ssr_step = 0.05; // 0.025
uniform float ssr_LLimiter = 0.1;
uniform float ssr_minRayStep = 0.2;

uniform vec3 bloom_vecThreshold = vec3(0.2126, 0.7152, 0.0722);
uniform float bloom_brightnessThreshold = 0.3;



#define getPosition(UV) texture(tPos, UV).xyz

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

    vec4 projectedCoord;
 
    for(int i = 0; i < ssr_binarySearchCount; i++) {
        projectedCoord = proj * 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 = proj * 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 *= ssr_step;
    
    for (int i = 0; i < ssr_rayMarchCount; i++) {
        hitCoord += dir;

        vec4 projectedCoord = proj* 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 5.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 = 0.7f;

    vec3 normal = vec3(texture(tNorm, UV));
    vec3 viewPos = getPosition(UV);

    vec3 worldPos = vec3(vec4(viewPos, 1.0) * inverse(view));
//    vec3 jitt = hash(worldPos) * texture(ssrValuesMap, texCoord).g;


    // Reflection vector
    vec3 reflected = normalize(reflect(normalize(viewPos), normalize(normal)));

    float spec = 0.7;

   vec3 hitPos = viewPos;
    float dDepth; 
    vec2 coords = rayCast(reflected * max(-viewPos.z, ssr_minRayStep), hitPos, dDepth);

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

    float fresnel = fresnel(reflected, normal);
    
    vec3 c2 = texture(tFrame, coords.xy).rgb;// * error * fresnel;

    // fragColor = vec3(fresnel);
    // return;
    vec3 fragCol = vec3(0.0);

    if (coords.xy != vec2(-1.0)) {
        fragCol = c2;
        color = fragCol;
        return;
    }
    
    fragCol = mix(texture(tFrame, UV), vec4(ssr_skyColor, 1.0), reflectionStrength).rgb;



    color = texture(tFrame,UV).rgb;



}
  

Just in case, here is the texture code.


    public enum LoadMethod
    {
        Single, Multi
    }

    public enum TextureFormat
    {
        Normal,RGB16F,RGB32F
    }

    public class Texture2D : TextureBase
    {
        public static Dictionary<string, Texture2D> Lut = new Dictionary<string, Texture2D>();
        public static byte[] TmpStore = null;
        public bool Alpha = false;
        public bool Binded = false;
        public bool Loaded = false;
        public Mutex LoadMutex = new Mutex();
        public Thread LoadThread = null;
        public byte[] pixs = null;
        public byte[] RawData;
        public Bitmap TexData = null;
        private readonly bool PreLoaded = false;
        private FileStream nf;

        private BinaryReader nr;

        public Texture2D ( int w, int h, bool alpha = false )
        {
            GenTex ( w, h, alpha );
        }

        public Texture2D( int w,int h, bool alpha,TextureFormat format)
        {

            GenTex(w, h, alpha, format);

        }

        public Texture2D ( )
        {
        }

        public Texture2D ( int w, int h, byte [ ] dat, bool alpha = true )
        {
            GL.ActiveTexture ( TextureUnit.Texture0 );
            Alpha = alpha;
            W = w;
            H = h;
            ID = GL.GenTexture ( );
            GL.BindTexture ( TextureTarget.Texture2D, ID );
            if ( alpha )
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, w, h, 0, PixelFormat.Rgba, PixelType.UnsignedByte, dat );
            }
            else
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, w, h, 0, PixelFormat.Rgb, PixelType.UnsignedByte, dat );
            }
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, ( int ) TextureWrapMode.Repeat );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, ( int ) TextureWrapMode.Repeat );

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ( int ) TextureMinFilter.LinearMipmapLinear );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ( int ) TextureMagFilter.Linear );
            GL.GenerateMipmap ( GenerateMipmapTarget.Texture2D );
            GL.PixelStore ( PixelStoreParameter.PackAlignment, 4 * 4 );
            pixs = dat;
        }
        public bool smallDone = false, bigDone = false;
        public static List<Texture2D> LoadingTexs = new List<Texture2D>();
        //public Thread LoadThread = null;
        //public Mutex LoadMutex = null;
        public int sW, sH;
        public int bW, bH;
        int wS = 0;
        public static void UpdateLoading()
        {
            foreach(var tex in LoadingTexs)
            {
                if (tex.smallDone)
                {
                    if (!tex.bigDone)
                    {
                        GL.Enable(EnableCap.Texture2D);
                        GL.BindTexture(TextureTarget.Texture2D, tex.ID);

                        if (tex.Alpha) 
                        {
                            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, tex.sW, tex.sH, 0, PixelFormat.Rgba, PixelType.UnsignedByte, tex.SmallRawData);
                        }
                        else
                        {
                            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, tex.sW, tex.sH, 0, PixelFormat.Rgb, PixelType.UnsignedByte, tex.SmallRawData);
                        }

                        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

                        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                        GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    //    GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);

                        GL.BindTexture(TextureTarget.Texture2D, 0);
                    }
                    tex.smallDone = false;
                }
             
                if (tex.bigDone)
                {
                  

                    GL.Enable(EnableCap.Texture2D);
                    GL.BindTexture(TextureTarget.Texture2D, tex.ID);

                    if (tex.Alpha)
                    {
                        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, tex.bW, tex.bH, 0, PixelFormat.Rgba, PixelType.UnsignedByte, tex.RawData);
                    }
                    else
                    {
                        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, tex.bW, tex.bH, 0, PixelFormat.Rgb, PixelType.UnsignedByte, tex.RawData);
                    }

                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
                    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                    GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
                    tex.bigDone = true;
                    GL.BindTexture(TextureTarget.Texture2D, 0);
                    LoadingTexs.Remove(tex);
                    return;
                }

            }
        }
        public byte[] SmallRawData;
        public Texture2D ( string path, LoadMethod lm, bool alpha = true )
        {
            if ( File.Exists ( path ) == false )
            {
                return;
            }
            if ( path == string.Empty || path == "" || path == null )
            {
                return;
            }
            Path = path;

           


            GL.Enable ( EnableCap.Texture2D );
            ID = GL.GenTexture ( );

            GL.BindTexture ( TextureTarget.Texture2D, ID );

            if ( new FileInfo ( path + ".texDat" ).Exists )
            {


                void Load_Tex()
                {
                    SmallRawData = LoadTexData(path + ".texDatSmall");
                    sW = W;
                    sH = H;
                    smallDone = true;
                    RawData = LoadTexData(path + ".texDat");
                    bW = W;
                    bH = H;
                    bigDone = true;
                }

                LoadingTexs.Add(this);

                LoadThread = new Thread(new ThreadStart(Load_Tex));

                LoadThread.Start();

            }
            else
            {



                Bitmap img = null;
                try
                {
                    img = new Bitmap(path);
                    //System.Drawing.Imaging.BitmapData dat = img.LockBits( new Rectangle(0, 0, img.Width, img.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.);
                }
                catch
                {
                    img = new Bitmap(32, 32);
                }
                W = img.Width;
                H = img.Height;
                Alpha = alpha;

                int pc = 3;
                if (Alpha)
                {
                    pc = 4;
                }
                Bitmap small_img = new Bitmap(img, new Size(img.Width / 4, img.Height / 4));

                pc = WriteDat(path, alpha, small_img, pc, true, small_img.Width, small_img.Height);

                pc = WriteDat(path, alpha, img, pc, false,W, H);

                GL.BindTexture(TextureTarget.Texture2D,ID);

                if (Alpha)
                {
                    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, W, H, 0, PixelFormat.Rgba, PixelType.UnsignedByte, RawData);
                }
                else
                {
                    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, W, H, 0, PixelFormat.Rgb, PixelType.UnsignedByte, RawData);
                }

                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);

                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                //    GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);

                GL.BindTexture(TextureTarget.Texture2D, 0);

            }

          
        }

        private byte[] LoadTexData(string lTex)
        {
            FileStream fs = new FileStream(lTex, FileMode.Open, FileAccess.Read);
            BinaryReader r = new BinaryReader(fs);
            Name = r.ReadString();
            Path = r.ReadString();
            W = r.ReadInt16();
            H = r.ReadInt16();
            Alpha = r.ReadBoolean();
            int pc = 3;
            if (Alpha)
            {
                pc = 4;
            }




            byte[] LData = new byte[W * H * (Alpha ? 4 : 3)];
            r.Read(LData, 0,W*H*(Alpha ? 4 : 3));

            

            fs.Close();
            fs = null;

            return LData;
        }

        private int WriteDat(string path, bool alpha, Bitmap img, int pc,bool small,int ow,int oh)
        {
            RawData = new byte[ow * oh * pc];

            //GL.TexImage2D(TextureTarget.Texture2D,0,PixelInternalFormat.)

            int pi = 0;
            for (int y = 0; y < img.Height; y++)
            {
                for (int x = 0; x < img.Width; x++)
                {
                    Color pix = img.GetPixel(x, y);
                    RawData[pi++] = pix.R;
                    RawData[pi++] = pix.G;
                    RawData[pi++] = pix.B;
                    if (alpha)
                    {
                        RawData[pi++] = pix.A;
                    }
                }
            }


            FileStream fs;
            if (small)
            {
                fs = new FileStream(path + ".texDatSmall", FileMode.Create, FileAccess.Write);
            }
            else
            {
                fs = new FileStream(path + ".texDat", FileMode.Create, FileAccess.Write);
            }

            BinaryWriter w = new BinaryWriter(fs);
            if (Name == null || Name == string.Empty)
            {
                Name = "Tex2D";
            }
            w.Write(Name);
            w.Write(Path);
            w.Write((short)ow);
            w.Write((short)oh);
            w.Write(Alpha);
            pc = 3;
            if (alpha)
            {
                pc = 4;
            }


            w.Write(RawData,0,RawData.Length);
            fs.Flush();
            fs.Close();
            fs = null;
            return pc;
        }

        ~Texture2D ( )
        {
            //NewMethod();
        }

        public string Name
        {
            get;
            set;
        }

        public string Path
        {
            get;
            set;
        }

        public override void Bind ( int texu )
        {
            GL.Enable ( EnableCap.Texture2D );
            GL.ActiveTexture ( ( TextureUnit ) ( ( int ) TextureUnit.Texture0 + texu ) );
            // GL.ClientActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + texu));
            GL.BindTexture ( TextureTarget.Texture2D, ID );
        }

        public void BindData ( )
        {
            GL.Enable ( EnableCap.Texture2D );
            ID = GL.GenTexture ( );
            GL.BindTexture ( TextureTarget.Texture2D, ID );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, ( int ) TextureWrapMode.Repeat );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, ( int ) TextureWrapMode.Repeat );

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ( int ) TextureMinFilter.LinearMipmapLinear );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ( int ) TextureMagFilter.Linear );
            GL.GenerateMipmap ( GenerateMipmapTarget.Texture2D );
            GL.PixelStore ( PixelStoreParameter.PackAlignment, 4 * 4 );
            GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, W, H, 0, PixelFormat.Rgba, PixelType.UnsignedByte, pixs );
            GL.Disable ( EnableCap.Texture2D );
        }

        public void CopyTex ( int x, int y )
        {
            GL.Enable ( EnableCap.Texture2D );
            GL.ActiveTexture ( TextureUnit.Texture0 );
            GL.BindTexture ( TextureTarget.Texture2D, ID );
            GL.CopyTexSubImage2D ( TextureTarget.Texture2D, 0, 0, 0, x, y, W, H );
            GL.BindTexture ( TextureTarget.Texture2D, 0 );
        }

        public void Delete ( )
        {
            GL.DeleteTexture ( ID );
        }

        public Texture2D ( TextureRaw raw )
        {
            RawData = raw.Data;
            Alpha = raw.Alpha;
            W = raw.W;
            H = raw.H;

            GL.Enable ( EnableCap.Texture2D );
            ID = GL.GenTexture ( );

            GL.BindTexture ( TextureTarget.Texture2D, ID );

            if ( Alpha )
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, W, H, 0, PixelFormat.Rgba, PixelType.UnsignedByte, RawData );
            }
            else
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, W, H, 0, PixelFormat.Rgb, PixelType.UnsignedByte, RawData );
            }

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, ( int ) TextureWrapMode.ClampToEdge );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, ( int ) TextureWrapMode.ClampToEdge );

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ( int ) TextureMinFilter.Linear );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ( int ) TextureMagFilter.Linear );
            // GL.GenerateMipmap ( GenerateMipmapTarget.Texture2D );

            GL.BindTexture ( TextureTarget.Texture2D, 0 );
        }

        public void Read ( )
        {
            W = Help.IOHelp.ReadInt ( );
            H = Help.IOHelp.ReadInt ( );
            Alpha = Help.IOHelp.ReadBool ( );
            RawData = Help.IOHelp.ReadBytes ( );
            // GL.ActiveTexture(TextureUnit.Texture0);

            GL.Enable ( EnableCap.Texture2D );
            ID = GL.GenTexture ( );

            GL.BindTexture ( TextureTarget.Texture2D, ID );

            if ( Alpha )
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, W, H, 0, PixelFormat.Rgba, PixelType.UnsignedByte, RawData );
            }
            else
            {
                GL.TexImage2D ( TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, W, H, 0, PixelFormat.Rgb, PixelType.UnsignedByte, RawData );
            }

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, ( int ) TextureWrapMode.Repeat );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, ( int ) TextureWrapMode.Repeat );

            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ( int ) TextureMinFilter.LinearMipmapLinear );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ( int ) TextureMagFilter.Linear );
            GL.GenerateMipmap ( GenerateMipmapTarget.Texture2D );

            GL.BindTexture ( TextureTarget.Texture2D, 0 );
        }

        public void ReadBitmap ( BinaryReader r )
        {
            short bw = r.ReadInt16();
            short bh = r.ReadInt16();
            TexData = new Bitmap ( bw, bh );
            pixs = r.ReadBytes ( bw * bh * 4 );
            W = bw;
            H = bh;
            Alpha = true;
            return;
            for ( int y = 0; y < bh; y++ )
            {
                for ( int x = 0; x < bw; x++ )
                {
                    byte[] col = r.ReadBytes(4);
                    System.Drawing.Color nc = System.Drawing.Color.FromArgb(col[3], col[0], col[1], col[2]);
                    TexData.SetPixel ( x, y, nc );
                }
            }
            W = bw;
            H = bh;
        }

        public override void Release ( int texu )
        {
            GL.ActiveTexture ( ( TextureUnit ) ( ( int ) TextureUnit.Texture0 + texu ) );
            // GL.ClientActiveTexture((TextureUnit)((int)TextureUnit.Texture0 + texu));
            GL.BindTexture ( TextureTarget.Texture2D, 0 );
            GL.Disable ( EnableCap.Texture2D );
        }

        public void SaveTex ( string p )
        {
            if ( string.IsNullOrEmpty ( p ) )
            {
                return;
            }
            if ( File.Exists ( p ) )
            {
                return;
            }

            FileStream fs = new FileStream(p, FileMode.Create, FileAccess.Write);
            BinaryWriter w = new BinaryWriter(fs);

            Bitmap sd = new Bitmap(TexData, 32, 32);
            WriteBitmap ( sd, w );
            WriteBitmap ( TexData, w );

            fs.Flush ( );
            fs.Close ( );
        }

        public void SetPix ( )
        {
            pixs = new byte [ W * H * 4 ];
            int loc = 0;
            for ( int y = 0; y < H; y++ )
            {
                for ( int x = 0; x < W; x++ )
                {
                    Color p = TexData.GetPixel(x, y);
                    pixs [ loc++ ] = p.R;
                    pixs [ loc++ ] = p.G;
                    pixs [ loc++ ] = p.B;
                    pixs [ loc++ ] = p.A;
                }
            }
        }

        public void SkipBitmap ( BinaryReader r )
        {
            short bw = r.ReadInt16();
            short bh = r.ReadInt16();
            r.BaseStream.Seek ( bw * bh * 4, SeekOrigin.Current );
        }

        public void T_LoadTex ( )
        {
            TexData = new Bitmap ( Path );

            W = TexData.Width;
            H = TexData.Height;
            D = 1;
            Alpha = true;
            SetPix ( );
            SaveTex ( Path + ".vtex" );
            Loaded = true;
        }

        public void T_LoadVTex ( )
        {
            ReadBitmap ( nr );

            //W = TexData.Width;
            //H = TexData.Height;
            D = 1;
            Alpha = true;
            //SetPix();
            //SaveTex(Path + ".vtex");
            Loaded = true;
            nf.Close ( );
            nf = null;
            nr = null;
        }

        public void Write ( )
        {
            Help.IOHelp.WriteInt ( W );
            Help.IOHelp.WriteInt ( H );
            Help.IOHelp.WriteBool ( Alpha );
            Help.IOHelp.WriteBytes ( RawData );
        }

        public void WriteBitmap ( Bitmap b, BinaryWriter w )
        {
            w.Write ( ( short ) b.Width );
            w.Write ( ( short ) b.Height );
            for ( int y = 0; y < b.Height; y++ )
            {
                for ( int x = 0; x < b.Width; x++ )
                {
                    Color p = b.GetPixel(x, y);
                    w.Write ( p.R );
                    w.Write ( p.G );
                    w.Write ( p.B );
                    w.Write ( p.A );
                }
            }
        }

        private void GenTex(int w, int h, bool alpha, TextureFormat format = TextureFormat.Normal)
        {
            GL.ActiveTexture(TextureUnit.Texture0);
            Alpha = alpha;
            W = w;
            H = h;
            ID = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, ID);
            if (alpha)
            {
                switch (format)
                {
                    case TextureFormat.Normal:
                        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, w, h, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
                        break;

                }
            }

            else
            {
                switch (format) {
                    case TextureFormat.Normal:
                        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, w, h, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
                        break;
                    case TextureFormat.RGB16F:
                        GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb16f, w, h, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
                        break;
                }
            } 
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapS, ( int ) TextureWrapMode.Repeat );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureWrapT, ( int ) TextureWrapMode.Repeat );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, ( int ) TextureMinFilter.Nearest );
            GL.TexParameter ( TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, ( int ) TextureMagFilter.Nearest );
            GL.PixelStore ( PixelStoreParameter.PackAlignment, 4 * 4 );
        }
    }

Pls let me know if I've missed something. -


And just to clarify I did try changing the constants in your shader, and it does help, but I can't quite get it working overall, i.e in any sort of scene. the more complex room like scenes I try on, have even more artifacts than above.

Thanks, Antony.
congard
congard

If you were able to fix these artifacts, please share the solution

AntRWells
AntRWells

Not been coding it lately, but I will post an update once it's working or at least working better.

Thanks, Antony.
JohnWRS
JohnWRS

Hey sorry I know I'm late to the party on this one but I just had the same issue.

I believe the issue comes from the following line


vec2 coords = rayCast(reflected * max(-viewPos.z, ssr_minRayStep), hitPos, dDepth);

The reflected vector is normalized and therefore only contains a direction. However, by multiplying it by -viewPos.z (This is most often going to be larger than minRayStep) you apply a magnitude to the vector, sometimes a very large one at that. After this, your step value is being applied to that vector. Meaning your step size is being scaled. Causing the steps to be much larger than intended, leading to the odd banding effect on the reflections. If you just send the reflected vector normally without multiplying it by anything you should be good. I can't see why you'd want to give it a magnitude beforehand anyway...

AntRWells
AntRWells

Hey,

Atm I'm converting the engine to have multiple backends(GL and vulkan atm) but I will follow your advice when I get to re-implementing this. Thanks.

Thanks, Antony.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.