Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

glsl fragment shader how to get non-interpolated data from each vertex

Tags:

opengl

glsl

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();
}
like image 430
Sam Potter Avatar asked Mar 13 '23 15:03

Sam Potter


2 Answers

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?

like image 66
aslg Avatar answered Mar 15 '23 03:03

aslg


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;
}
like image 26
Sam Potter Avatar answered Mar 15 '23 04:03

Sam Potter