How to draw a line stipple with ES 2.0

I am struggling to understand how to use the fragment shader to properly discard fragments so that I can get a line stipple pattern simpler to glLineStipple(…). I have read through several concepts and these are the two I am considering:

  1. Use a texture to draw the line and have the fragment shader discard values (This is the one I would like to do)

  2. Generate a lot of points and use glDrawElements to only access the indices that you care about (I don’t like this option however it is easy to implement)

The biggest problem with 1) is that I don’t understand how texture1D works exactly and I am not sure if I want a stipple pattern of 0x5555 with a factor of 3 (e.g. glLineStipple(0x5555, 3)) how to relate that to a texture1D and how to properly discard the correct fragments in the shader. Any help would be appreciated as I am struggling to understand how to implement it correctly. Thanks!

I found a description in another thread but I am not sure how to implement it, but this is exactly what I am looking to try and do.

If you want to emulate OpenGL’s glLineStipple for thick lines, then they use a 16-bit pattern.
You can generate a 1D texture for your stipple texture, and map it onto your line geometry using the repeat factor as texture coordinates.

Question is how do I accomplish that? I have tried without success :(. I am trying the following but I am struggling to understand how to get this working.

widget code [using qt]


#include "glwidget.h"
#include "math.h"

#include <QGLShader>
#include <QGLShaderProgram>
#include <QKeyEvent>

namespace
{
    const float GL_PI = 3.1415f;
}

GLWidget::GLWidget(QWidget *parent)
    : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::Rgba | QGL::DepthBuffer), parent)
    , vShader(0)
    , fShader(0)
    , program(0)
    , xRot(0.0f)
    , yRot(0.0f)
{
}

GLWidget::~GLWidget()
{
   delete program;
   program = NULL;

   delete fShader;
   fShader = NULL;

   delete vShader;
   vShader = NULL;
}

void GLWidget::initializeGL()
{
    vShader = new QGLShader(QGLShader::Vertex, context(), this);
    vShader->compileSourceFile(":/shaders/vertex.vsh");

    fShader = new QGLShader(QGLShader::Fragment, context(), this);
    fShader->compileSourceFile(":/shaders/fragment.fsh");

    program = new QGLShaderProgram(context(), this);
    program->addShader(vShader);
    program->addShader(fShader);
    program->link();

    // attribute
    vertexAttribLoc = program->attributeLocation("a_vertex");
    texCoordAttribLoc = program->attributeLocation("a_texCoord");

    // vertex shader uniform
    mvpMatrixUniLoc = program->uniformLocation("u_mvpMatrix");

    GLfloat x = -80.0f;
    GLfloat y = -90.0f;
    GLfloat z = 0.0f;

    // point 1
    vertices.append(x);
    vertices.append(y);
    vertices.append(z);

    // point 2
    x = 80.0f;
    vertices.append(x);
    vertices.append(y);
    vertices.append(z);

    // fragment shader uniform
    colorUniformLoc = program->uniformLocation("u_color");
    samplerUniLoc = program->uniformLocation("u_sampler");

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    program->bind();

    int tupleSize = 3;
    program->setAttributeArray(vertexAttribLoc, GL_FLOAT, vertices.data(), tupleSize);
    program->enableAttributeArray(vertexAttribLoc);

    program->setAttributeValue(texCoordAttribLoc, 3);
    program->enableAttributeArray(texCoordAttribLoc);

    QVector3D color(0.0f, 1.0f, 0.0f);
    program->setUniformValue(colorUniformLoc, color);

    GLushort pattern = 0x5555;
    program->setUniformValue(samplerUniLoc, pattern);

    QMatrix4x4 lineModel = modelMatrix;
    lineModel.rotate(xRot, 1.0f, 0.0f, 0.0f);
    lineModel.rotate(yRot, 0.0f, 1.0f, 0.0f);

    GLfloat sizes[2];
    glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, sizes);

    GLfloat curSize = sizes[0];

    for(GLfloat y = 20.0f; y < 200.0f; y += 20.0f)
    {
       QMatrix4x4 individualLineModel = lineModel;
       individualLineModel.translate(0.0f, y, 0.0f);

       mvpMatrix = projectionMatrix * viewMatrix * individualLineModel;
       program->setUniformValue(mvpMatrixUniLoc, mvpMatrix);

       glLineWidth(curSize);
       glDrawArrays(GL_LINES, 0, vertices.size());

       curSize += 1.0f;
    }

    swapBuffers();
}

void GLWidget::resizeGL(int w, int h)
{
    GLfloat nRange = 100.0f;
    GLfloat aspectRatio;

    if(h == 0)
    {
        h = 1;
    }

    glViewport(0, 0, w, h);

    projectionMatrix.setToIdentity();

    aspectRatio = (GLfloat) w / (GLfloat) h;

    if(w <= h)
    {
        projectionMatrix.ortho(-nRange, nRange, (-nRange * h) / w, (nRange * h) / w, -nRange, nRange);
    }
    else
    {
        projectionMatrix.ortho((-nRange * w) / h, (nRange * w) / h, -nRange, nRange, -nRange, nRange);
    }

    viewMatrix.setToIdentity();
    modelMatrix.setToIdentity();

    mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
}

void GLWidget::keyPressEvent(QKeyEvent *event)
{
   switch(event->key())
   {
   case Qt::Key_Up:
       xRot -= 5.0f;
       break;
   case Qt::Key_Down:
       xRot += 5.0f;
       break;
   case Qt::Key_Left:
       yRot -= 5.0f;
       break;
   case Qt::Key_Right:
       yRot += 5.0f;
       break;
   case Qt::Key_Q:
   case Qt::Key_Escape:
      exit(0);
   default:
       break;
   }

   if(xRot > 356.0f)
   {
       xRot = 0.0f;
   }

   if(xRot < -1.0f)
   {
       xRot = 355.0f;
   }

   if(yRot > 356.0f)
   {
       yRot = 0.0f;
   }

   if(yRot < -1.0f)
   {
       yRot = 355.0f;
   }

   update();
}

vertex shader


attribute vec4 a_vertex;
attribute float a_textureCoord;

uniform mat4 u_mvpMatrix;

varying float v_textureCoord;

void main(void)
{
    gl_Position = u_mvpMatrix * a_vertex;
    v_textureCoord = a_textureCoord;
}

fragment


uniform vec3 u_color;
uniform sampler1D sampler;

varying float v_texCoord;

void main(void)
{

    vec4 baseColor = vec4(u_color, 1.0);

    vec4 texColor = texture1D(sampler, v_texCoord);

    gl_FragColor = baseColor + texColor;
}

I seem to be crashing whenever I try to draw however if I take out the texColor from fragment shader and just use the baseColor, I don’t crash.


    // fragment shader uniform
    colorUniformLoc = program->uniformLocation("u_color");
    samplerUniLoc = program->uniformLocation("u_sampler");

Your shader uses “sampler” (no u_ prefix) for the name of the sampler variable.


    GLushort pattern = 0x5555;
    program->setUniformValue(samplerUniLoc, pattern);

Uhm, no, that is not how texture sampler uniforms work. You need to create a texture, fill it with a black and white pattern that corresponds to your stipple pattern, then bind the texture to a texture unit and pass the number of that texture unit to the program->setUniformValue(samplerUniLoc, …) call - the above would result in an attempt to use texture unit 0x5555, which normally is a bit out of range :wink:

Thanks for the heads up on the shaders, here are the two shaders now fixed :).

Vertex



attribute vec4 a_vertex;
attribute float a_texCoord;

uniform mat4 u_mvpMatrix;

varying float v_texCoord;

void main(void)
{
    gl_Position = u_mvpMatrix * a_vertex;
    v_texCoord = a_texCoord;
}

Fragment


uniform vec3 u_color;
uniform sampler1D u_sampler;

varying float v_texCoord;

void main(void)
{
    vec4 baseColor = vec4(u_color, 1.0);
    vec4 texColor = texture1D(u_sampler, v_texCoord);
    gl_FragColor = baseColor + texColor;

}

So I understand that samplers are not normally set the way I am trying to do it, however I am confused as this is not a normal 2D texture that I am trying to use. I am guessing I still need to do these type of functions glGenTextures, glBindTexture, glTexImage, and some glTexParameteri for how to handle filtering. I am still confused how that relates to a line stipple since it is 1D thing. I am trying to emulate glLineStipple(0x5555, 3) and the quote mentioned above all you had to was use the 0x5555 which in this case is an unsigned int, followed by a texture coordinate that represents the factor. I would like to emulate the same functionality as glLineStipple using those pieces of data.

If I am understanding the statement above, I would use sometype of bitwise array (e.g. 111001110011100 and so on) to represent my sampler but what I am missing is how that relates to the 1D texture in the fragment shader?

I have also read something about using gl_FragCoord as another option to represent a line stipple and was curious if anyone knows how I might be able to use that technique for a glLineStipple replacement.

There hasn’t been a response to this thread so I was curious if I might be able to ask the same question in a different way. The first question I have is how to use a texture1D and apply it to a line? Maybe if I understand that concept the line stippling will be clear. Thanks for any help!

Hmm, do you understand how a 2D texture is applied to a triangle? For a line and a 1D texture it works accordingly, just with one dimension less: you assign (single component) texture coordinates to the vertices of a line and for each fragment of the line the texture coordinates are interpolated so that you can look up a value that corresponds to where the fragment is along the line. Say you have a 1D texture (which is just an image consisting of a single row) with alternating black and white pixels and draw a single line with texture coordinates 0 and 1 at the vertices respectively you’ll get a black and white “chequered” line.
Not sure if that helps, if not perhaps you could try explaining where you see the difference between 2D and 1D textures and why the latter gives you trouble?

FWIW: Replicating the exact glLineStipple semantics is probably not entirely trivial, because it was specified in terms of counting fragments - on the other hand drawing lines with the kind of patterns that can be constructed with glLineStipple, but not matching the pixel exact results is much simpler.

Hmm, do you understand how a 2D texture is applied to a triangle?

I do understand how 2D textures work, at least to the level where I can properly place these textures and see them applied to my geometry.

What I am having trouble understanding is that if I take two points Point1(x1, y1) and Point2(x2, y2) and I get the idea of the applying a texture that is interpolated across the line, however how to create that image is what I am after. So lets say image[16] where each element in the array is ‘on’ or ‘off’, I am struggling how to get a pattern from that image[16]. I hope this maybe helps clear up what I am struggling with. Thanks for any replies!

Textures are really just 1/2/3 dimensional arrays of constant data that you can access from a shader that happen to have a bunch of options that make them very suitable for storing images :wink: For a dotted line you could have a single channel 1D texture that stores GL_UNSIGNED_BYTE data and store alternately 255 and 0 as values of the “pixels”. In you shader you sample that texture and if the sampled value is below a threshold (e.g. 0.5) you discard (see page 115f of the GLSL 4.5 spec) that fragment otherwise you assign the color of the line to the fragment shader output.

So there are a lot of topics about stipple line with modern OpenGl capabilities here on the forum, but it much more like empty talk. Is there anybody who implement this task through shaders?