4

tl;dr: What is the best method for accessing data from each individual vertex whilst in the fragment shader?

e.g. The triangle in this fragment is made up from vertices v0,v1 and v2 and I want to give each vertex a specific integer which I can use to pick a texture and then fade between the 3 in the fragment shader; I don't want these ids to be interpolated and it is important that I can access each vertex's id.

Current Situation: I am currently writing a shader for rendering terrain; I have a function in my fragment shader which will return the appropriate texture colour from uvs for a given ID (By means of a texture atlas). I can then fade between the 3 textures to give the give smoothly textured terrain

Current Code

Vertex Shader:

#version 330 core

layout(location = 0) in vec3 in_position;
layout(location = 1) in vec2 in_uv_coords;
layout(location = 2) in vec3 in_normal;
layout(location = 3) in float in_texture_id;

uniform mat4 view_matrix;
uniform mat4 projection_matrix;

out vec2 pass_uv_coords;
out vec3 pass_normal;

out vec3 texture_ratio;
out float pass_fake_brightness;

out float pass_id0;
out float pass_id1;
out float pass_id2;


void CalculateFakeLighting()
{
    const vec3 light_direction = normalize(vec3(1,-1,1));
    vec3 unit_normal = normalize(in_normal);

    float normal_dot_light = dot(unit_normal, -light_direction);
    pass_fake_brightness = max(0.2, normal_dot_light);
}


void main()
{
    pass_uv_coords = in_uv_coords;
    pass_normal = in_normal;

    gl_Position = projection_matrix * view_matrix * vec4(in_position, 1.0);

    int tile_track = int(mod(gl_VertexID, 3));

    switch(tile_track)
    {
        case 0:
            texture_ratio = vec3(1,0,0);
            pass_id0 = in_texture_id;
            break;
        case 1:
            texture_ratio = vec3(0,1,0);
            pass_id1 = in_texture_id;
            break;
        case 2:
            texture_ratio = vec3(0,0,1);
            pass_id0 = in_texture_id;
            break;
    };

    CalculateFakeLighting();
}

Fragment Shader:

#version 330 core

in vec2 pass_uv_coords;
in vec3 pass_normal;

in vec3 texture_ratio;
in float pass_fake_brightness;

in float pass_id0;
in float pass_id1;
in float pass_id2;

const int HORIZONTAL_IDS = 8;
const int VERTICAL_IDS = 8;

uniform sampler2D texture0_sampler;

out vec4 colour;


void UseFakeLighting()
{
    colour *= pass_fake_brightness;
}

vec2 CorrectUVs(vec2 uvs)
{
    vec2 corrected_uvs = uvs;
    const float cushion = 0.001;

    //Correct UV scale
    while(corrected_uvs.x >= 1)
        corrected_uvs.x--;
    while(corrected_uvs.y >= 1)
        corrected_uvs.y--;

    if(corrected_uvs.x < cushion)
        corrected_uvs.x = cushion;
    if(corrected_uvs.x > 1 - cushion)
        corrected_uvs.x = 1 - cushion;

    if(corrected_uvs.y < cushion)
        corrected_uvs.y = cushion;
    if(corrected_uvs.y > 1 - cushion)
        corrected_uvs.y = 1 - cushion;

    return corrected_uvs;
}


vec4 GetTexture(float id, vec2 uv_coords)
{
    vec2 step = vec2(
        1.0/HORIZONTAL_IDS,
        1.0/VERTICAL_IDS
    );


    uv_coords.x/=HORIZONTAL_IDS;
    uv_coords.y/=VERTICAL_IDS;

    uv_coords.x += step.x * mod(id, HORIZONTAL_IDS);
    uv_coords.y += step.y * floor(id/VERTICAL_IDS);
    //Texture is upsidedown
    uv_coords.y = 1.0 - uv_coords.y;

    return texture(texture0_sampler, uv_coords);
}


void main()
{
    vec2 corrected_uvs = CorrectUVs(pass_uv_coords);
    vec3 correct_ratio = normalize(texture_ratio);

    colour = GetTexture(pass_id0, corrected_uvs) * correct_ratio.x +
    GetTexture(pass_id1, corrected_uvs) * correct_ratio.y +
    GetTexture(pass_id2, corrected_uvs) * correct_ratio.z;

    if(colour.a == 0)
        discard;

    UseFakeLighting();
}
Sam Potter
  • 93
  • 1
  • 6

2 Answers2

4

By default the output variables from the vertex shader to the fragment shader use perspective-correct interpolation. If you want no interpolation done then qualify your variables with flat:

flat out vec3 pass_id0;

For more info see GLSL Type Qualifiers. Also see this question “flat” qualifier in glsl?

Community
  • 1
  • 1
aslg
  • 1,966
  • 2
  • 15
  • 20
  • I know that, but it will only pass through the value for pass_id0 for the last vertex of the triangle and I need the value from all of the vertices – Sam Potter Apr 09 '16 at 16:36
  • Where did you read that? Provoking vertices are completely user-defined. You are correct that the provoking vertex is last by default, but the correct way to describe what you did is to refer to the "provoking vertex" ;) Nevertheless, if you're doing something that needs values from multiple vertices, it really sounds like you want to do this calculation in an earlier stage (geometry shader, perhaps?) – Andon M. Coleman Apr 10 '16 at 01:06
  • @AndonM.Coleman I haven't touched geometry shaders as of yet, so I'll read into it. Could I output something like a vec3 which stores a specific value from each vertex from the geometry shader to the fragment shader? – Sam Potter Apr 10 '16 at 12:14
  • @SamPotter In the geometry shader you have access to all vertices on each primitive (say, all vertices in a triangle). You could indeed output a vec3 which stores that specific value for each vertex and then let the pipeline interpolate it for you. Eg. Calculate the barycentric coordinates for each vertex in the triangle. There was one such example in this book: [OpenGL 4 SL Cookbook 4](http://www.amazon.com/OpenGL-Shading-Language-Cookbook-Edition/dp/1782167021). – aslg Apr 10 '16 at 13:15
3

As recommended by @aslg and @AndonM.Coleman, geometry is a good solution to this issue. A flat vec3 is passed out of the geometry stage, which stores the id of each vertex which is then accessible in the fragment shader.

The key lines are in the geometry shader; one part of the output is

flat vec3 texture_ids;

Which is then set as such:

vertex_out.texture_ids.x = vertex_in[0].texture_id;
vertex_out.texture_ids.y = vertex_in[1].texture_id;
vertex_out.texture_ids.z = vertex_in[2].texture_id;

Full Shader Sources:

Vertex

#version 330 core

layout(location = 0) in vec3 in_position;
layout(location = 1) in vec2 in_uv_coords;
layout(location = 2) in vec3 in_normal;
layout(location = 3) in float in_texture_id;

out VertexData
{
    vec2 uv_coord;
    vec3 normal;
    uint texture_id;
} vertex_out;


void main()
{
    vertex_out.uv_coord = in_uv_coords;
    vertex_out.normal = in_normal;
    vertex_out.texture_id = uint(round(in_texture_id));
    gl_Position = vec4(in_position, 1.0);
}

Geometry

#version 330 core

layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

uniform mat4 view_matrix;
uniform mat4 projection_matrix;
mat4 vp_matrix = projection_matrix * view_matrix;

in VertexData
{
    vec2 uv_coord;
    vec3 normal;
    uint texture_id;
} vertex_in[];

out VertexDataPass
{
    vec2 uv_coord;
    vec3 normal;
    vec3 texture_ratio;
    float brightness;
    flat vec3 texture_ids;
} vertex_out;


void CalculateFakeBrightness(int index)
{
    const vec3 light_direction = normalize(vec3(1,-1,1));
    vec3 unit_normal = normalize(vertex_in[index].normal);

    float normal_dot_light = dot(unit_normal, -light_direction);
    vertex_out.brightness = max(0.2, normal_dot_light);
}


void main()
{
    for(int i = 0; i < gl_in.length(); i++)
    {
        gl_Position = vp_matrix * gl_in[i].gl_Position;
        vertex_out.uv_coord = vertex_in[i].uv_coord;
        vertex_out.normal = vertex_in[i].normal;

        vertex_out.texture_ids.x = vertex_in[0].texture_id;
        vertex_out.texture_ids.y = vertex_in[1].texture_id;
        vertex_out.texture_ids.z = vertex_in[2].texture_id;
        CalculateFakeBrightness(i);

        switch(int(mod(i,3)))
        {
            case 0:
                vertex_out.texture_ratio = vec3(1,0,0);
                break;
            case 1:
                vertex_out.texture_ratio = vec3(0,1,0);
                break;
            case 2:
                vertex_out.texture_ratio = vec3(0,0,1);
                break;
        };

        EmitVertex();
    }
    EndPrimitive();
}

Fragment

#version 330 core

in VertexDataPass
{
    vec2 uv_coord;
    vec3 normal;
    vec3 texture_ratio;
    float brightness;
    flat vec3 texture_ids;
} vertex_data;

const int HORIZONTAL_IDS = 8;
const int VERTICAL_IDS = 8;
uniform sampler2D texture0_sampler;

out vec4 colour;


vec2 CorrectUVs(vec2 uvs)
{
    vec2 corrected_uvs = uvs;
    const float cushion = 0.001;

    //Correct UV scale
    while(corrected_uvs.x >= 1)
        corrected_uvs.x--;
    while(corrected_uvs.y >= 1)
        corrected_uvs.y--;

    if(corrected_uvs.x < cushion)
        corrected_uvs.x = cushion;
    if(corrected_uvs.x > 1 - cushion)
        corrected_uvs.x = 1 - cushion;

    if(corrected_uvs.y < cushion)
        corrected_uvs.y = cushion;
    if(corrected_uvs.y > 1 - cushion)
        corrected_uvs.y = 1 - cushion;

    return corrected_uvs;
}


vec4 GetTexture(uint id, vec2 uv_coords)
{
    vec2 step = vec2(
        1.0/HORIZONTAL_IDS,
        1.0/VERTICAL_IDS
    );


    uv_coords.x/=HORIZONTAL_IDS;
    uv_coords.y/=VERTICAL_IDS;

    uv_coords.x += step.x * mod(id, HORIZONTAL_IDS);
    uv_coords.y += step.y * floor(float(id)/VERTICAL_IDS);
    //Texture is upsidedown
    uv_coords.y = 1.0 - uv_coords.y;

    return texture(texture0_sampler, uv_coords);
}


void main()
{
    vec2 uvs = CorrectUVs(vertex_data.uv_coord);

    colour = 
        GetTexture(uint(vertex_data.texture_ids.x), uvs) * vertex_data.texture_ratio.x +
        GetTexture(uint(vertex_data.texture_ids.y), uvs) * vertex_data.texture_ratio.y +
        GetTexture(uint(vertex_data.texture_ids.z), uvs) * vertex_data.texture_ratio.z;

    if(colour.a == 0)
        discard;

    colour.xyz *= vertex_data.brightness;
}
Sam Potter
  • 93
  • 1
  • 6