Чтобы ответить на этот вопрос, мы сначала должны выяснить, что на самом деле делает 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 . В этой версии геометрические шейдеры не поддерживаются.
Смотрите рендеринг строки, который был сгенерирован с помощью вышеуказанного шейдера.

Шейдер дает правильные результирующие отрезки линии, но не подходит для линейных отрезков, поскольку шаблон пунктирной линии перезапускается по каждой координате вершины.
Эта проблема не может быть решена даже с помощью геометрического шейдера. Эта часть вопроса остается нерешенной.
Для следующей простой демонстрационной программы я использовал GLFW API для создания окна, GLEW для загрузки OpenGL и GLM -OpenGL Matmatics для математика Я не предоставляю код для функции 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;
}