Search code examples
c++openglglslglfwtessellation

No output from tessellation shader?


I have been working on a terrain LOD algorithm, but the primary logic is on the CPU:

CPU based terrain LOD

I tried to convert most of the logic to the tessellation control and evaluation phases of the opengl pipeline, but nothing is displayed:

GPU based terrain LOD?

I reduced the code to a basic "hello quad" program

#define GLEW_STATIC
#include <glew.h>
#include <glfw3.h>
#include <glm.hpp>
#include <gtc/matrix_transform.hpp>
#include <stdio.h>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <algorithm>

using namespace std;

/* Shader Source */

// vertex shader
const GLchar * VS_src[] = {
"#version 430\n"

"layout(location = 0) in vec3 position;\n"
"out vec4 vposition;\n"

"void main()\n"
"{\n"
"   vposition = vec4(position, 1.0);\n"
"}\n"
};

// tesselation control shader
const GLchar * TCS_src[] = {
"#version 430\n"

"layout(vertices = 4) out;\n"
"in vec4 vposition[];\n"
"out vec4 tposition[];\n"

"void main()\n"
"{\n"
"   tposition[gl_InvocationID] = vposition[gl_InvocationID];\n"

"   if (gl_InvocationID == 0)\n"
"   {\n"
"       float tessLevel = 1.0;\n"

"       gl_TessLevelInner[0] = tessLevel;\n"
"       gl_TessLevelInner[1] = tessLevel;\n"

"       gl_TessLevelOuter[0] = tessLevel;\n"
"       gl_TessLevelOuter[1] = tessLevel;\n"
"       gl_TessLevelOuter[2] = tessLevel;\n"
"       gl_TessLevelOuter[3] = tessLevel;\n"
"   }\n"
"}\n"
};

// tesselation evaluation shader
const GLchar * TES_src[] = {
"#version 430\n"

"uniform mat4 mvp;\n"
"layout(quads) in;\n"
"in vec4 tposition[];\n"

"void main()\n"
"{\n"
"   float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;\n"
"   float z = gl_TessCoord[2] * (tposition[1].z - tposition[2].z) + tposition[2].z;\n"
"   float y = 0.0;\n"

"   gl_Position = mvp * vec4(x, y, z, 1.0);\n"

"}\n"
};

// fragment shader
const GLchar * FS_src[] = {
"#version 430\n"

"out vec3 color;\n"

"void main()\n"
"{\n"
    "color = vec3(0.0);\n"
"}\n"
};

/* Link Shaders to Program */

GLuint LoadShaders(const GLchar ** VS, const GLchar ** TCS, const GLchar ** TES, const GLchar ** FS)
{

    // Create the shaders
    GLuint VS_ID = glCreateShader(GL_VERTEX_SHADER);
    GLuint TCS_ID = glCreateShader(GL_TESS_CONTROL_SHADER);
    GLuint TES_ID = glCreateShader(GL_TESS_EVALUATION_SHADER);
    GLuint FS_ID = glCreateShader(GL_FRAGMENT_SHADER);

    GLint Result = GL_FALSE;
    int InfoLogLength;

    // Compile Vertex Shader
    printf("compiling vertex shader\n");
    glShaderSource(VS_ID, 1, VS, NULL);
    glCompileShader(VS_ID);

    // Check Vertex Shader
    glGetShaderiv(VS_ID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(VS_ID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
        printf("%s\n", &VertexShaderErrorMessage[0]);
    }

    // Compile Tess Control Shader
    printf("compiling tesselation control shader\n");
    glShaderSource(TCS_ID, 1, TCS, NULL);
    glCompileShader(TCS_ID);

    // Check Tess Control Shader
    glGetShaderiv(TCS_ID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(TCS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> TessControlShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(TCS_ID, InfoLogLength, NULL, &TessControlShaderErrorMessage[0]);
        printf("%s\n", &TessControlShaderErrorMessage[0]);
    }

    // Compile Tess Evaluation Shader
    printf("compiling tesselation evaluation shader\n");
    glShaderSource(TES_ID, 1, TES, NULL);
    glCompileShader(TES_ID);

    // Check Tess Evaluation Shader
    glGetShaderiv(TES_ID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(TES_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> TessEvaluationShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(TES_ID, InfoLogLength, NULL, &TessEvaluationShaderErrorMessage[0]);
        printf("%s\n", &TessEvaluationShaderErrorMessage[0]);
    }

    // Compile Fragment Shader
    printf("compiling fragment shader\n");
    glShaderSource(FS_ID, 1, FS, NULL);
    glCompileShader(FS_ID);

    // Check Fragment Shader
    glGetShaderiv(FS_ID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FS_ID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(FS_ID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
        printf("%s\n", &FragmentShaderErrorMessage[0]);
    }

    // Link the program
    printf("linking program\n");
    GLuint ProgramID = glCreateProgram();

    glAttachShader(ProgramID, VS_ID);
    glAttachShader(ProgramID, TCS_ID);
    glAttachShader(ProgramID, TES_ID);
    glAttachShader(ProgramID, FS_ID);

    glLinkProgram(ProgramID);

    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> ProgramErrorMessage(InfoLogLength+1);
        glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
        printf("%s\n", &ProgramErrorMessage[0]);
    }

    glDetachShader(ProgramID, VS_ID);
    glDetachShader(ProgramID, TCS_ID);
    glDetachShader(ProgramID, TES_ID);
    glDetachShader(ProgramID, FS_ID);

    glDeleteShader(VS_ID);
    glDeleteShader(TCS_ID);
    glDeleteShader(TES_ID);
    glDeleteShader(FS_ID);

    return ProgramID;
}

/* MAIN */
int main()
{
    GLFWwindow * window;

    if (!glfwInit()) return 0;

    glfwWindowHint(GLFW_SAMPLES, 0);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    window = glfwCreateWindow(1600, 900, "Test", NULL, NULL);
    if (!window) return 0;

    glfwMakeContextCurrent(window);
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

    glewExperimental=true;
    if (glewInit() != GLEW_OK) return 0;

    // Init
    glm::mat4 p = glm::perspective(glm::radians(45.0f), 1600.0f / 900.0f, 0.1f, 1000.0f);
    // look at <0,0,0> from <20,20,20>
    glm::mat4 v = glm::lookAt(glm::vec3(20.0f), glm::vec3(0.0f), glm::vec3(0,1,0));
    glm::mat4 m = glm::mat4(1.0f);
    glm::mat4 mvp = p * v * m;

    // draw 1 quad
    std::vector<unsigned int> indices;
    std::vector<glm::vec3> vertices;

    indices.push_back(0);
    indices.push_back(1);
    indices.push_back(2);
    indices.push_back(3);

    vertices.push_back(glm::vec3(-10.0f, 0.0f, -10.0f));
    vertices.push_back(glm::vec3( 10.0f, 0.0f, -10.0f));
    vertices.push_back(glm::vec3( 10.0f, 0.0f,  10.0f));
    vertices.push_back(glm::vec3(-10.0f, 0.0f,  10.0f));

    // VAO
    GLuint VertexArrayID;
    glGenVertexArrays(1, &VertexArrayID);
    glBindVertexArray(VertexArrayID);

    // program
    GLuint ProgramID = LoadShaders(VS_src, TCS_src, TES_src, FS_src);

    // mvp uniform
    GLuint MatrixID = glGetUniformLocation(ProgramID, "mvp");

    // Vertex Buffer
    GLuint vertexbuffer;
    glGenBuffers(1, &vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

    // Element Buffer
    GLuint elementbuffer;
    glGenBuffers(1, &elementbuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

    // loop
    while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 )
    {
        glViewport(0, 0, 1600, 900);
        glClearColor(0.478f, 0.702f, 0.816f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glDisable(GL_DEPTH_TEST);
        glDisable(GL_CULL_FACE);

        glUseProgram(ProgramID);

        glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &mvp[0][0]);

        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

        glPatchParameteri(GL_PATCH_VERTICES, 4);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
        glDrawElements(GL_PATCHES, indices.size(), GL_UNSIGNED_INT, (void*)0);

        glDisableVertexAttribArray(0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // cleanup
    glDeleteVertexArrays(1, &VertexArrayID);
    glDeleteProgram(ProgramID);
    glDeleteBuffers(1, &vertexbuffer);
    glDeleteBuffers(1, &elementbuffer);

    glfwTerminate();

    return 0;
}

but still nothing is displayed (besides the blue clearcolor background). All shaders compile with no errors and the program is linked with no errors.


Solution

  • When the quads primitive mode is used, then only the first 2 components of gl_TessCoord have a meaning. The 3rd component is 0.0. gl_TessCoord[0] and gl_TessCoord[1] provide normalized 2D coordinates, similar the UV coordinates of textures.

    This means that you have to use gl_TessCoord[1] instead of gl_TessCoord[2] in the tessellation evaluation shader:

    float x = gl_TessCoord[0] * (tposition[1].x - tposition[0].x) + tposition[0].x;
    float z = gl_TessCoord[1] * (tposition[1].z - tposition[2].z) + tposition[2].z;
    

    See Tessellation, Quads


    Specification:

    GLSL - The OpenGL Shading Language 4.6, 7.1 Built-In Language Variables, page 129:

    The variable gl_TessCoord is available only in the tessellation evaluation language. It specifies a threecomponent (u,v,w) vector identifying the position of the vertex being processed by the shader relative to the primitive being tessellated.

    OpenGL 4.6 core profile specification, 11.2.2.2 Quad Tessellation, page 416:

    If the tessellation primitive mode is quads, a rectangle is subdivided into a collection of triangles covering the area of the original rectangle. First, the original rectangle is subdivided into a regular mesh of rectangles, where the number of rectangles along the u = 0 and u = 1 (vertical) and v = 0 and v = 1 (horizontal) edges are derived from the first and second inner tessellation levels, respectively.