Search code examples
c++openglglslshaderparallax

Weird Layered Effect During Parallax Mapping


I am following along with the LearnOpenGL guide and am trying to implement Steep Parallax Mapping.

Everything seems to be working fine except my brick wall seems to have distinct visible layers whereas the photos in the guide don't show any layers. I was trying to use this code to parallax the topography of the world but these weird layers seem to show up there too so I was hoping to find a fix for this.

Layered wall photo

[1

Photo of how it should look

Photo of how it should look

Here is my modified vertex shader

#version 300 es

in vec4 vPosition; // aPos
in vec2 texCoord; // aTexCoords
in vec4 vNormal; // aNormal
in vec4 vTangent; // aTangent

uniform mat4 model_view;
uniform mat4 projection;
uniform vec4 light_position;

out vec2 ftexCoord;
out vec3 vT;
out vec3 vN;
out vec4 position;
out vec3 FragPos;
out vec3 TangentLightPos;
out vec3 TangentViewPos;
out vec3 TangentFragPos;

void
main()
{
    // Normal variables
    vN = normalize(model_view * vNormal).xyz;
    vT = normalize(model_view * vTangent).xyz;

    vec4 veyepos = model_view*vPosition;
    position = veyepos;

    ftexCoord = texCoord;

    // Displacement variables
    vec3 bi = cross(vT, vN);

    FragPos = vec3(model_view * vPosition).xyz;

    vec3 T = normalize(mat3(model_view) * vTangent.xyz);
    vec3 B = normalize(mat3(model_view) * bi);
    vec3 N = normalize(mat3(model_view) * vNormal.xyz);
    mat3 TBN = transpose(mat3(T, B, N));

    TangentLightPos = TBN * light_position.xyz;
    TangentViewPos = TBN * vPosition.xyz;
    TangentFragPos = TBN * FragPos;

    gl_Position = projection * model_view * vPosition;
}

and my modified fragment shader is here

#version 300 es

precision highp float;

in vec2 ftexCoord;
in vec3 vT; //parallel to surface in eye space
in vec3 vN; //perpendicular to surface in eye space
in vec4 position;
in vec3 FragPos;
in vec3 TangentLightPos;
in vec3 TangentViewPos;
in vec3 TangentFragPos;

uniform int mode;
uniform vec4 light_position;
uniform vec4 light_color;
uniform vec4 ambient_light;
uniform sampler2D colorMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;

out vec4  fColor;

// STEEP PARALLAX MAPPING
vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    // number of depth layers
    const float minLayers = 8.0;
    const float maxLayers = 32.0;
    float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy / viewDir.z * 0.1;
    vec2 deltaTexCoords = P / numLayers;

    // get initial values
    vec2 currentTexCoords = texCoords;
    float currentDepthMapValue = texture(depthMap, currentTexCoords).r;

    while(currentLayerDepth < currentDepthMapValue)
    {
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;
        // get depth of next layer
        currentLayerDepth += layerDepth;
    }

    return currentTexCoords;
}

void main()
{
    // DO NORMAL MAPPING
    if (mode == 0) {
        vec3 T = normalize(vT);
        vec3 N = normalize(vN);

        vec3 bi = cross(T, N);

        mat4 changeOfCoord = mat4(vec4(T, 0), vec4(bi, 0), vec4(N, 0), vec4(0, 0, 0, 1));

        vec3 L = normalize(light_position - position).xyz;
        vec3 E = normalize(-position).xyz;

        vec4 text = vec4(texture(normalMap, ftexCoord) * 2.0 - 1.0);

        vec4 eye = changeOfCoord * text;

        vec4 amb = texture(colorMap, ftexCoord) * ambient_light;

        vec4 diff = max(0.0, dot(L, eye.xyz)) * light_color * texture(colorMap, ftexCoord);

        fColor = amb + diff;
    } else if (mode == 1) { // DO PARALLAX MAPPING
        // offset texture coordinates with Parallax Mapping
        vec3 viewDir = normalize(TangentViewPos - TangentFragPos);
        vec2 texCoords = ftexCoord;
        texCoords = ParallaxMapping(ftexCoord,  viewDir);

        // discard samples outside of the default texture coordinate space
        if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0)
        discard;

        // obtain normal from normal map
        vec3 normal = texture(normalMap, texCoords).rgb;

        //values stored in normal texture is [0,1] range, we need [-1, 1] range
        normal = normalize(normal * 2.0 - 1.0);

        // get diffuse color
        vec3 color = texture(colorMap, texCoords).rgb;
        // ambient
        vec3 ambient = 0.1 * color;
        // diffuse
        vec3 lightDir = normalize(TangentLightPos - TangentFragPos);
        float diff = max(dot(lightDir, normal), 0.0);
        vec3 diffuse = diff * color;
        // specular
        vec3 reflectDir = reflect(lightDir, normal);
        vec3 halfwayDir = normalize(lightDir + viewDir);
        float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);

        vec3 specular = vec3(0.2) * spec;
        fColor = vec4(ambient + diffuse + 0.0, 1.0);
    }


}

Solution

  • The layers at acute gazing angles are a common effect at parallax mapping. To improve the result you've to increment the number of samples or implement Parallax Occlusion Mapping (as described in the bottom part of the tutorial):

    // STEEP PARALLAX MAPPING
    vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
    {
        // number of depth layers
        const float minLayers = 8.0;
        const float maxLayers = 32.0;
        float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
        // calculate the size of each layer
        float layerDepth = 1.0 / numLayers;
        // depth of current layer
        float currentLayerDepth = 0.0;
        // the amount to shift the texture coordinates per layer (from vector P)
        vec2 P = viewDir.xy / viewDir.z * 0.1;
        vec2 deltaTexCoords = P / numLayers;
    
        // get initial values
        vec2 currentTexCoords = texCoords;
        float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
    
        while(currentLayerDepth < currentDepthMapValue)
        {
            // shift texture coordinates along direction of P
            currentTexCoords -= deltaTexCoords;
            // get depthmap value at current texture coordinates
            currentDepthMapValue = texture(depthMap, currentTexCoords).r;
            // get depth of next layer
            currentLayerDepth += layerDepth;
        }
    
        // get texture coordinates before collision (reverse operations)
        vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
    
        // get depth after and before collision for linear interpolation
        float afterDepth  = currentDepthMapValue - currentLayerDepth;
        float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
    
        // interpolation of texture coordinates
        float weight = afterDepth / (afterDepth - beforeDepth);
        vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
    
        return finalTexCoords;
    }
    

    By thee way, the vector seems to be inverted. In common the bitangent is the Cross product of the normal vector and the tangent in a Right-handed system. But that depends on the displacement texture.

    vec3 bi = cross(vT, vN);

    vec3 bi = cross(vN, vT);
    

    See further:
    Bump Mapping with javascript and glsl
    Normal, Parallax and Relief mapping
    Demo