Search code examples
c++openglglsl2dsfml

GLSL: Fade 2D grid based on distance from camera


I am currently trying to draw a 2D grid on a single quad using only shaders. I am using SFML as the graphics library and sf::View to control the camera. So far I have been able to draw an anti-aliased multi level grid. The first level (blue) outlines a chunk and the second level (grey) outlines the tiles within a chunk.

enter image description here

I would now like to fade grid levels based on the distance from the camera. For example, the chunk grid should fade in as the camera zooms in. The same should be done for the tile grid after the chunk grid has been completely faded in.

I am not sure how this could be implemented as I am still new to OpenGL and GLSL. If anybody has any pointers on how this functionality can be implemented, please let me know.

Vertex Shader

#version 130
out vec2 texCoords;

void main() {
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    texCoords = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;
}

Fragment Shader

#version 130

uniform vec2 chunkSize = vec2(64.0, 64.0);
uniform vec2 tileSize = vec2(16.0, 16.0);

uniform vec3 chunkBorderColor = vec3(0.0, 0.0, 1.0);
uniform vec3 tileBorderColor = vec3(0.5, 0.5, 0.5);

uniform bool drawGrid = true;

in vec2 texCoords;

void main() {
    vec2 uv = texCoords.xy * chunkSize;
    
    vec3 color = vec3(1.0, 1.0, 1.0);

    if(drawGrid) {
        float aa = length(fwidth(uv));

        vec2 halfChunkSize = chunkSize / 2.0;
        vec2 halfTileSize =  tileSize / 2.0;
        vec2 a = abs(mod(uv - halfChunkSize, chunkSize) - halfChunkSize);
        vec2 b = abs(mod(uv - halfTileSize, tileSize) - halfTileSize);

        color = mix(
            color, 
            tileBorderColor, 
            smoothstep(aa, .0, min(b.x, b.y))
        );

        color = mix(
            color, 
            chunkBorderColor, 
            smoothstep(aa, .0, min(a.x, a.y))
        );
    }

    gl_FragColor.rgb = color;
    gl_FragColor.a = 1.0;
}   

Solution

  • You need to split your multiplication in the vertex shader to two parts:

    // have a variable to be interpolated per fragment
    out vec2 vertex_coordinate;
    ...
    {
      // this will store the coordinates of the vertex
      // before its projected (i.e. its "world" coordinates)
      vertex_coordinate = gl_ModelViewMatrix * gl_Vertex;
    
      // get your projected vertex position as before
      gl_Position = gl_ProjectionMatrix * vertex_coordinate;
      ...
    }
    

    Then in the fragment shader you change the color based on the world vertex coordinate and the camera position:

    in vec2 vertex_coordinate;
    // have to update this value, every time your camera changes its position
    uniform vec2 camera_world_position = vec2(64.0, 64.0);
    ...
    {
    ...
      // calculate the distance from the fragment in world coordinates to the camera
      float fade_factor = length(camera_world_position - vertex_coordinate);
    
      // make it to be 1 near the camera and 0 if its more then 100 units.
      fade_factor = clamp(1.0 - fade_factor / 100.0, 0.0, 1.0);
    
      // update your final color with this factor
      gl_FragColor.rgb = color * fade_factor;
    ...
    }
    

    The second way to do it is to use the projected coordinate's w. I personally prefer to calculate the distance in units of space. I did not test this code, it might have some trivial syntax errors, but if you understand the idea, you can apply it in any other way.