glLineStipple устарел в OpenGL 3.1

glLineStipple устарело в последних API OpenGL. Чем его заменить? Если не заменить, как я могу получить аналогичный эффект? (Конечно, я не хочу использовать профиль совместимости...)


person Vincent    schedule 16.05.2011    source источник
comment
На самом деле не заброшен, изучение и игра с opengl — это хобби, и на самом деле у меня очень мало времени для этого. Но я с удовольствием проголосую за ваш ответ!   -  person Vincent    schedule 05.07.2011


Ответы (3)


Жаль, ничего не заменили. Первой идеей, которая пришла мне в голову для эмуляции, был геометрический шейдер. Вы передаете геометрическому шейдеру линию, вычисляете длину ее экранного пространства и на основе этого создаете переменное количество подстрок между ее начальной и конечной вершинами.


РЕДАКТИРОВАТЬ: Возможно, вы также можете использовать одномерную текстуру с альфа-каналом (или красным), кодирующим шаблон как 0,0 (без линии) или 1,0 (линия), а затем изменить координату текстуры линий с 0 на 1, а в блоке фрагментов вы делаете простой альфа-тест, отбрасывая фрагменты с альфа-каналом ниже некоторого порога. Вы можете упростить шейдер геометрии для генерации texCoords вашей линии, иначе вам потребуются разные вершины для каждой линии. Таким образом, вы также можете сделать texCoord зависимым от длины строки на экране.

Все становится сложнее, если вы рисуете треугольники (используя режим многоугольника GL_LINE). Затем вам нужно самостоятельно выполнить преобразование треугольник-линия в геометрическом шейдере, вставляя треугольники и выводя линии (это также может стать причиной отказа от режима полигонов в будущем, если он еще не был).


EDIT: Хотя я считаю, что этот вопрос заброшен, я сделал простой тройной шейдер для второго подхода. Это просто минимальное решение, не стесняйтесь добавлять собственные функции самостоятельно. Я не проверял это, потому что мне не хватает необходимого оборудования, но вы должны понять:

uniform mat4 modelViewProj;

layout(location=0) in vec4 vertex;

void main()
{
    gl_Position = modelViewProj * vertex;
}

Вершинный шейдер — это простой проход.

layout(lines) in;
layout(line_strip, max_vertices=2) out;

uniform vec2 screenSize;
uniform float patternSize;

noperspective out float texCoord;

void main()
{
    vec2 winPos0 = screenSize.xy * gl_in[0].gl_Position.xy / gl_in[0].gl_Position.w;
    vec2 winPos1 = screenSize.xy * gl_in[1].gl_Position.xy / gl_in[1].gl_Position.w;
    gl_Position = gl_in[0].gl_Position;
    texCoord = 0.0;
    EmitVertex();
    gl_Position = gl_in[1].gl_Position;
    texCoord = 0.5 * length(winPos1-winPos0) / patternSize;
    EmitVertex();
}

В шейдере геометрии мы берем линию и вычисляем ее длину на экране в пикселях. Затем мы делим это на размер текстуры штрихового узора, который будет равен factor*16 при эмуляции вызова glLineStipple(factor, pattern). Это принимается за одномерную текстурную координату второй конечной точки линии.

Обратите внимание, что эта координата текстуры должна интерполироваться линейно (спецификатор интерполяции noperspective). Обычная интерполяция с правильной перспективой привела бы к тому, что штриховой узор "сжимался" на более удаленных частях линии, в то время как мы явно работаем со значениями экранного пространства.

uniform sampler1D pattern;
uniform vec4 lineColor;

noperspective in float texCoord;

layout(location=0) out vec4 color;

void main()
{
    if(texture(pattern, texCoord).r < 0.5)
        discard;
    color = lineColor;
}

Фрагментный шейдер теперь просто выполняет простой альфа-тест, используя значение из текстуры шаблона, которая содержит 1 для линии и 0 для отсутствия линии. Таким образом, для эмуляции фиксированной функции stipple у вас будет 16-пиксельная однокомпонентная одномерная текстура вместо 16-битного шаблона. Не забудьте установить режим переноса паттерна на GL_REPEAT, насчет режима фильтрации я не уверен, но полагаю, что GL_NEAREST будет хорошей идеей.

Но, как было сказано ранее, если вы хотите визуализировать треугольники с помощью glPolygonMode, это не сработает. Вместо этого вам нужно адаптировать шейдер геометрии, чтобы он принимал треугольники и генерировал 3 линии для каждого треугольника.


EDIT: На самом деле прямая поддержка OpenGL 3 целочисленных операций в шейдерах позволяет нам полностью отказаться от всего этого подхода с одномерными текстурами и работать прямо с реальным битовым шаблоном. Таким образом, шейдер геометрии немного изменен, чтобы вывести фактическую координату шаблона размера экрана без нормализации:

texCoord = 0.5 * length(winPos1-winPos0);

Затем во фрагментном шейдере мы просто берем битовый паттерн как беззнаковое целое (хотя и 32-битное, в отличие от 16-битного значения glLineStipple) и коэффициент растяжения паттерна, а также просто берем координату текстуры (на самом деле текстуры больше нет, но неважно) по модулю 32, чтобы получить его позицию в шаблоне (эти явные uint раздражают, но мой компилятор GLSL говорит, что неявные преобразования между int и uint являются злом):

uniform uint pattern;
uniform float factor;

...
uint bit = uint(round(linePos/factor)) & 31U;
if((pattern & (1U<<bit)) == 0U)
    discard;
person Christian Rau    schedule 16.05.2011
comment
Потрясающий. Но можно ли это сделать без шейдера геометрии в OpenGLES-2? - person Bram; 01.05.2012
comment
@ Брэм Да. Я не использовал геометрический шейдер для создания новых примитивов, а только для вычисления текстурных координат линии на основе ее длины на экране. Вы можете сделать это без GS, но в этом случае вам придется вычислять texCoords на процессоре. Но это означает, что вы должны дублировать вершины, так как каждой вершине нужен свой texCoord для каждой строки, и вы не можете так легко сделать ее зависимой от размера экранного пространства (ну, вы все еще можете вычислить ее длину экранного пространства на ЦП, но каждый кадр?). Это невозможно сделать в VS, так как вы видите только одну вершину, а не всю линию. - person Christian Rau; 01.05.2012
comment
@Bram Принципиальная техника - это не что иное, как простое альфа-сопоставление (подумайте о проволочном заборе или листьях деревьев в вашей любимой игре) на линии. Геометрический шейдер выигрывает только от автоматического вычисления длины экрана, но не потому, что для этого требуется сложная генерация примитивов, а потому, что GS — единственный, кто видит весь примитив целиком, а не только одну вершину или фрагмент. Вы можете просто использовать размер объектного пространства линии, который не зависит от преобразования и может быть предварительно вычислен. Но это не будет так хорошо эмулировать glLineStipple. - person Christian Rau; 01.05.2012
comment
@ChristianRau, это старый вопрос, но мне интересно, как мы можем рассчитать координаты текстуры на процессоре, потому что у меня действительно проблемы с геометрическим шейдером, например: 0 (2): ошибка C3008: неизвестный спецификатор макета «линии» 0 (3): ошибка C3008: неизвестно спецификатор макета «max_vertices» 0 (3): ошибка C3008: неизвестный спецификатор макета «line_strip» - person Veysel Bekir Macit; 23.11.2014

Чтобы ответить на этот вопрос, мы должны сначала выяснить, что glLineStipple на самом деле делает.

См. изображение, где четырехугольник слева нарисован 4 отдельными отрезками линии с использованием типа примитива GL_LINES.
Окружность справа нарисована последовательной многоугольной линией с использованием примитивного типа GL_LINE_STRIP.

При использовании сегментов линии штриховой узор начинался с каждого сегмента. Узор перезапускается для каждого примитива.
При использовании линейной полосы точечный узор применяется бесшовно ко всему многоугольнику. Шаблон, плавно продолжающийся за пределами координат вершин.
Имейте в виду, что длина шаблона растягивается по диагоналям. Возможно, это ключ к реализации.

Для отдельных отрезков это совсем не сложно, а вот для полосок все немного сложнее. Длину линии нельзя рассчитать в шейдерной программе, не зная всех примитивов линии. Даже если бы были известны все примитивы (например, SSBO), расчеты пришлось бы выполнять в цикле.
См. также
Штриховые линии с основным профилем OpenGL.

В любом случае, нет необходимости реализовывать геометрический шейдер. Хитрость заключается в том, чтобы знать начало сегмента линии во фрагментном шейдере. Это легко сделать с помощью квалификатора интерполяции flat.

Вершинный шейдер должен передать нормализованную координату устройства фрагментному шейдеру. Один раз с интерполяцией по умолчанию и один раз без (плоской) интерполяции. Это приводит к тому, что в тени фрагмента первый входной параметр содержит NDC-координату фактического положения на линии, а более поздний — NDC-координату начала строки.

#version 330

layout (location = 0) in vec3 inPos;

flat out vec3 startPos;
out vec3 vertPos;

uniform mat4 u_mvp;

void main()
{
    vec4 pos    = u_mvp * vec4(inPos, 1.0);
    gl_Position = pos;
    vertPos     = pos.xyz / pos.w;
    startPos    = vertPos;
}

Кроме различных входных данных, фрагментный шейдер имеет юниформ-переменные. u_resolution содержит ширину и высоту области просмотра. u_factor и u_pattern — это множитель и 16-битный шаблон в соответствии с параметрами glLineStipple.

Таким образом, длину строки от начала до фактического фрагмента можно рассчитать:

vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution/2.0;
float dist = length(dir);

А фрагмент на промежутке можно отбросить командой discard.

uint bit = uint(round(dist / u_factor)) & 15U;
if ((u_pattern & (1U<<bit)) == 0U)
    discard; 

Фрагментный шейдер:

#version 330

flat in vec3 startPos;
in vec3 vertPos;

out vec4 fragColor;

uniform vec2  u_resolution;
uniform uint  u_pattern;
uniform float u_factor;

void main()
{
    vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution/2.0;
    float dist = length(dir);

    uint bit = uint(round(dist / u_factor)) & 15U;
    if ((u_pattern & (1U<<bit)) == 0U)
        discard; 
    fragColor = vec4(1.0);
}

Эта реализация намного проще и короче, чем использование геометрических шейдеров. Квалификатор интерполяции flat поддерживается, начиная с GLSL 1.30 и GLSL ES 3.00. В этой версии геометрические шейдеры не поддерживаются.
См. рендеринг линий, созданный с помощью приведенного выше шейдера.

Шейдер дает правильный результат для сегментов линий, но не работает для полос линий, поскольку штриховой узор перезапускается в каждой координате вершины.
Проблему не может решить даже геометрический шейдер. Эта часть вопроса остается до сих пор нерешенной.

В следующей простой демонстрационной программе я использовал API GLFW для создания окна, GLEW для загрузки OpenGL и GLM -OpenGL Mathematics для математики. Я не привожу код функции CreateProgram, которая просто создает программный объект, из исходного кода вершинного и фрагментного шейдера:

#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <gl/gl_glew.h>
#include <GLFW/glfw3.h>

std::string vertShader = R"(
#version 330

layout (location = 0) in vec3 inPos;

flat out vec3 startPos;
out vec3 vertPos;

uniform mat4 u_mvp;

void main()
{
    vec4 pos    = u_mvp * vec4(inPos, 1.0);
    gl_Position = pos;
    vertPos     = pos.xyz / pos.w;
    startPos    = vertPos;
}
)";

std::string fragShader = R"(
#version 330

flat in vec3 startPos;
in vec3 vertPos;

out vec4 fragColor;

uniform vec2  u_resolution;
uniform uint  u_pattern;
uniform float u_factor;

void main()
{
    vec2  dir  = (vertPos.xy-startPos.xy) * u_resolution/2.0;
    float dist = length(dir);

    uint bit = uint(round(dist / u_factor)) & 15U;
    if ((u_pattern & (1U<<bit)) == 0U)
        discard; 
    fragColor = vec4(1.0);
}
)";

GLuint CreateVAO(std::vector<glm::vec3> &varray)
{
    GLuint bo[2], vao;
    glGenBuffers(2, bo);
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glEnableVertexAttribArray(0); 
    glBindBuffer(GL_ARRAY_BUFFER, bo[0] );
    glBufferData(GL_ARRAY_BUFFER, varray.size()*sizeof(*varray.data()), varray.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); 

    return vao;
}

int main(void)
{
    if ( glfwInit() == 0 )
        return 0;
    GLFWwindow *window = glfwCreateWindow( 800, 600, "GLFW OGL window", nullptr, nullptr );
    if ( window == nullptr )
        return 0;
    glfwMakeContextCurrent(window);

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

    GLuint program    = CreateProgram(vertShader, fragShader);
    GLint loc_mvp     = glGetUniformLocation(program, "u_mvp");
    GLint loc_res     = glGetUniformLocation(program, "u_resolution");
    GLint loc_pattern = glGetUniformLocation(program, "u_pattern");
    GLint loc_factor  = glGetUniformLocation(program, "u_factor");

    glUseProgram(program);

    GLushort pattern = 0x18ff;
    GLfloat  factor  = 2.0f;
    glUniform1ui(loc_pattern, pattern);
    glUniform1f(loc_factor, factor);
    //glLineStipple(2.0, pattern);
    //glEnable(GL_LINE_STIPPLE);

    glm::vec3 p0(-1.0f, -1.0f, 0.0f);
    glm::vec3 p1(1.0f, -1.0f, 0.0f);
    glm::vec3 p2(1.0f, 1.0f, 0.0f);
    glm::vec3 p3(-1.0f, 1.0f, 0.0f);
    std::vector<glm::vec3> varray1{ p0, p1, p1, p2, p2, p3, p3, p0 };
    GLuint vao1 = CreateVAO(varray1);

    std::vector<glm::vec3> varray2;
    for (size_t u=0; u <= 360; u += 8)
    {
        double a = u*M_PI/180.0;
        double c = cos(a), s = sin(a);
        varray2.emplace_back(glm::vec3((float)c, (float)s, 0.0f));
    }
    GLuint vao2 = CreateVAO(varray2);

    glm::mat4(project);
    int vpSize[2]{0, 0};
    while (!glfwWindowShouldClose(window))
    {
        int w, h;
        glfwGetFramebufferSize(window, &w, &h);
        if (w != vpSize[0] ||  h != vpSize[1])
        {
            vpSize[0] = w; vpSize[1] = h;
            glViewport(0, 0, vpSize[0], vpSize[1]);
            float aspect = (float)w/(float)h;
            project = glm::ortho(-aspect, aspect, -1.0f, 1.0f, -10.0f, 10.0f);
            glUniform2f(loc_res, (float)w, (float)h);
        }

        glClear(GL_COLOR_BUFFER_BIT);

        glm::mat4 modelview1( 1.0f );
        modelview1 = glm::translate(modelview1, glm::vec3(-0.6f, 0.0f, 0.0f) );
        modelview1 = glm::scale(modelview1, glm::vec3(0.5f, 0.5f, 1.0f) );
        glm::mat4 mvp1 = project * modelview1;

        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp1));
        glBindVertexArray(vao1);
        glDrawArrays(GL_LINES, 0, (GLsizei)varray1.size());

        glm::mat4 modelview2( 1.0f );
        modelview2 = glm::translate(modelview2, glm::vec3(0.6f, 0.0f, 0.0f) );
        modelview2 = glm::scale(modelview2, glm::vec3(0.5f, 0.5f, 1.0f) );
        glm::mat4 mvp2 = project * modelview2;

        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp2));
        glBindVertexArray(vao2);
        glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)varray2.size());

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glfwTerminate();

    return 0;
}

См. также
Штриховая линия в OpenGL3?
OpenGL ES — пунктирные линии

person Rabbid76    schedule 10.03.2019

Поскольку я немного потрудился (без каламбура), чтобы сделать это правильно, я подумал, что это может быть полезно для других, если я поделюсь своей реализацией набора точечных шейдеров, основанных на версии Кристиана Рау. Для управления плотностью паттернов фрагментному шейдеру требуется количество паттернов nPatterns на единицу длины окна просмотра — вместо установки коэффициента. Также включена дополнительная функция плоскости отсечения. Остальное в основном комментирует и чистит.

Бесплатное использование во всех смыслах и целях.

Вершинный шейдер:

#version 330

in vec4 vertex;

void main(void)
{
    // just a pass-through
    gl_Position = vertex;
}

Геометрический шейдер:

#version 330

layout(lines) in;
layout(line_strip, max_vertices = 2) out;

uniform mat4 pvmMatrix;
uniform mat4 mMatrix;
uniform mat4 vMatrix;


out vec3 vPosition;  // passed to the fragment shader for plane clipping
out float texCoord;  // passed to the fragment shader for stipple pattern

void main(void)
{
    // to achieve uniform pattern density whatever the line orientation
    // the upper texture coordinate is made proportional to the line's length
    vec3 pos0 = gl_in[0].gl_Position.xyz;
    vec3 pos1 = gl_in[1].gl_Position.xyz;
    float max_u_texture = length(pos1 - pos0);

    // Line Start
    gl_Position = pvmMatrix * (gl_in[0].gl_Position);
    texCoord = 0.0;
    // depth position for clip plane
    vec4 vsPos0 = vMatrix * mMatrix * gl_Position;
    vPosition = vsPos0.xyz / vsPos0.w;
    EmitVertex();  // one down, one to go

    // Line End
    gl_Position = pvmMatrix * (gl_in[1].gl_Position);
    texCoord = max_u_texture;
    // depth position for clip plane
    vec4 vsPos1 = vMatrix * mMatrix * gl_Position;
    vPosition = vsPos0.xyz / vsPos0.w;
    EmitVertex();

    // done
    EndPrimitive();
}

Фрагментный шейдер:

#version 330

uniform int pattern;   // an integer between 0 and 0xFFFF representing the bitwise pattern
uniform int nPatterns; // the number of patterns/unit length of the viewport, typically 200-300 for good pattern density
uniform vec4 color;
uniform vec4 clipPlane0; // defined in view-space

in float texCoord;

in vec3 vPosition;

layout(location=0) out vec4 fragColor;

void main(void)
{
    // test vertex postion vs. clip plane position (optional)
    if (vPosition.z > clipPlane0.w) {
        discard;
        return;
    }

    // use 4 bytes for the masking pattern
    // map the texture coordinate to the interval [0,2*8[
    uint bitpos = uint(round(texCoord * nPatterns)) % 16U;
    // move a unit bit 1U to position bitpos so that
    // bit is an integer between 1 and 1000 0000 0000 0000 = 0x8000
    uint bit = (1U << bitpos);

    // test the bit against the masking pattern
    //  Line::SOLID:       pattern = 0xFFFF;  // = 1111 1111 1111 1111 = solid pattern
    //  Line::DASH:        pattern = 0x3F3F;  // = 0011 1111 0011 1111
    //  Line::DOT:         pattern = 0x6666;  // = 0110 0110 0110 0110
    //  Line::DASHDOT:     pattern = 0xFF18;  // = 1111 1111 0001 1000
    //  Line::DASHDOTDOT:  pattern = 0x7E66;  // = 0111 1110 0110 0110
    uint up = uint(pattern);

    // discard the bit if it doesn't match the masking pattern
    if ((up & bit) == 0U) discard;

    fragColor = color;
}
person techwinder    schedule 04.03.2019