OpenGL3 中的虚线?

Dashed line in OpenGL3?

提问人:Elmi 提问时间:10/22/2018 最后编辑:Rabbid76Elmi 更新时间:4/6/2020 访问量:8911

问:

我目前正在将一个旧的 OpenGL 1.1 应用程序移植到 OpenGL 3.0,该应用程序使用线框模型。

在 1.1 中,以下代码用于创建虚线:

glPushAttrib(GL_ENABLE_BIT); 
glLineStipple(1, 0x0F0F);
glEnable(GL_LINE_STIPPLE);

在这里,像往常一样,参数被推送到堆栈中,以影响所有后续的绘图操作。

我的问题是:在不再使用此堆栈的 OpenGL3 中如何做到这一点?如何将我的线设置为虚线(可能在将坐标交给 glBufferData() 之前)?

OpenGL GLSL 着色器 OpenGL-1.x

评论

0赞 mystery 1/3/2019
我认为可以使用片段着色器来完成,但我不太确定如何完成。我想你会设置一个属性 per-vertex,其中包含到该点的线的长度,然后着色器可以查看插值长度和点。
0赞 leone ruggiero 5/1/2023
我试图通过将屏幕空间中的顶点映射到 u 坐标来实现虚线折线: shadertoy.com/view/Dtt3z2,我想这是一种相当幼稚的方法,但是,它对我来说看起来足够好,不需要你预处理几何体。

答:

33赞 Rabbid76 2/6/2019 #1

对于单独的线段,这根本不是很复杂。例如,绘制GL_LINES基元。
诀窍是知道片段着色器中线段的起点。通过使用平坦插值限定符,这非常简单。

顶点着色器必须将归一化的设备坐标传递给片段着色器。一次使用默认插值,一次不使用()插值。这会导致在片段阴影中,第一个输入参数包含线上实际位置的 NDC 坐标,后来包含线起点的 NDC 坐标。flat

#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_resolutionu_dashSizeu_gapSize

因此,可以计算出从开始到实际片段的线的长度:

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

缝隙上的碎片可以通过丢弃命令丢弃。

if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
    discard; 

片段着色器:

#version 330

flat in vec3 startPos;
in vec3 vertPos;

out vec4 fragColor;

uniform vec2  u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;

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

    if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
        discard; 
    fragColor = vec4(1.0);
}

对于以下简单的演示程序,我使用了 GLFW API 来创建窗口,使用 GLEW 来加载 OpenGL,并使用 GLM -OpenGL Mathematics 来进行数学运算。我没有提供函数的代码,它只是从顶点着色器和片段着色器源代码创建一个程序对象:CreateProgram

#include <GL/glew.h>
#include <GL/gl.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <GLFW/glfw3.h>

#include <vector>

#define _USE_MATH_DEFINES
#include <math.h>

int main(void)
{
    if (glfwInit() == GLFW_FALSE)
        return 0;
    GLFWwindow *window = glfwCreateWindow(400, 300, "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_dash = glGetUniformLocation(program, "u_dashSize");
    GLint loc_gap  = glGetUniformLocation(program, "u_gapSize");

    glUseProgram(program);
    glUniform1f(loc_dash, 10.0f);
    glUniform1f(loc_gap, 10.0f);

    std::vector<float> varray{
        -1, -1, -1,   1, -1, -1,   1, 1, -1,   -1, 1, -1,
        -1, -1,  1,   1, -1,  1,   1, 1,  1,   -1, 1,  1
    };
    std::vector<unsigned int> iarray{
        0, 1, 1, 2, 2, 3, 3, 0, 
        4, 5, 5, 6, 6, 7, 7, 4,
        0, 4, 1, 5, 2, 6, 3, 7
    };

    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); 
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, iarray.size()*sizeof(*iarray.data()), iarray.data(), GL_STATIC_DRAW);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    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]);
            project = glm::perspective(glm::radians(90.0f), (float)w/(float)h, 0.1f, 10.0f);
            glUniform2f(loc_res, (float)w, (float)h);
        }

        static float angle = 1.0f;
        glm::mat4 modelview( 1.0f );
        modelview = glm::translate(modelview, glm::vec3(0.0f, 0.0f, -3.0f) );
        modelview = glm::rotate(modelview, glm::radians(angle), glm::vec3(1.0f, 0.0f, 0.0f));
        modelview = glm::rotate(modelview, glm::radians(angle*0.5f), glm::vec3(0.0f, 1.0f, 0.0f));
        angle += 0.5f;
        glm::mat4 mvp = project * modelview;

        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawElements(GL_LINES, (GLsizei)iarray.size(), GL_UNSIGNED_INT, nullptr);

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

    return 0;
}

如果目标是沿着多边形绘制一条虚线,事情就会变得有点复杂。例如,绘制GL_LINE_STRIP基元。

在不知道线条的所有基元的情况下,无法在着色器程序中计算线条的长度。即使所有基元都是已知的(例如 SSBO),计算也必须在循环中完成。
我决定在着色器程序中添加一个附加属性,其中包含从线条开头到顶点坐标的“距离”。“距离”是指投影多边形到视口的长度。

这导致顶点着色器和片段着色器更加简单:

顶点着色器:

#version 330

layout (location = 0) in vec3 inPos;
layout (location = 1) in float inDist;

out float dist;

uniform mat4 u_mvp;

void main()
{
    dist        = inDist;
    gl_Position = u_mvp * vec4(inPos, 1.0);
}

片段着色器:

#version 330

in float dist;

out vec4 fragColor;

uniform vec2  u_resolution;
uniform float u_dashSize;
uniform float u_gapSize;

void main()
{
    if (fract(dist / (u_dashSize + u_gapSize)) > u_dashSize/(u_dashSize + u_gapSize))
        discard; 
    fragColor = vec4(1.0);
}

在演示程序中,该属性是在 CPU 上计算的。每个顶点坐标都由模型、视图、投影矩阵进行变换。最后,将其从归一化设备空间转换为窗口空间。计算线条相邻坐标之间的 XY 距离,并将长度沿线条相加并分配给相应的属性值:inDist

int w = [...], h = [...];               // window widht and height
glm::mat4 mpv = [...];                  // model view projection matrix
std::vector<glm::vec3> varray{ [...] }; // array of vertex 

std::vector<float> darray(varray.size(), 0.0f); // distance attribute - has to be computed

glm::mat4 wndmat = glm::scale(glm::mat4(1.0f), glm::vec3((float)w/2.0f, (float)h/2.0f, 1.0f));
wndmat = glm::translate(wndmat, glm::vec3(1.0f, 1.0f, 0.0f));

glm::vec2 vpPt(0.0f, 0.0f);
float dist = 0.0f;
for (size_t i=0; i < varray.size(); ++i)
{
    darray[i] = dist;
    glm::vec4 clip = mvp * glm::vec4(varray[i], 1.0f);
    glm::vec4 ndc  = clip / clip.w;
    glm::vec4 vpC  = wndmat * ndc;
    float len = i==0 ? 0.0f :  glm::length(vpPt - glm::vec2(vpC));
    vpPt = glm::vec2(vpC);
    dist += len;
}

演示程序:

int main(void)
{
    if (glfwInit() == GLFW_FALSE)
        return 0;
    GLFWwindow *window = glfwCreateWindow(800, 600, "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_dash = glGetUniformLocation(program, "u_dashSize");
    GLint loc_gap  = glGetUniformLocation(program, "u_gapSize");

    glUseProgram(program);
    glUniform1f(loc_dash, 10.0f);
    glUniform1f(loc_gap, 10.0f);

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

    GLuint bo[2], vao;
    glGenBuffers(2, bo);
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glEnableVertexAttribArray(0); 
    glEnableVertexAttribArray(1); 
    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); 
    glBindBuffer(GL_ARRAY_BUFFER, bo[1] );
    glBufferData(GL_ARRAY_BUFFER, darray.size()*sizeof(*darray.data()), darray.data(), GL_STATIC_DRAW);
    glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 0, 0); 

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    glm::mat4 project, wndmat;
    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]);
            project = glm::perspective(glm::radians(90.0f), (float)w/(float)h, 0.1f, 10.0f);
            glUniform2f(loc_res, (float)w, (float)h);
            wndmat = glm::scale(glm::mat4(1.0f), glm::vec3((float)w/2.0f, (float)h/2.0f, 1.0f));
            wndmat = glm::translate(wndmat, glm::vec3(1.0f, 1.0f, 0.0f));
        }

        static float angle = 1.0f;
        glm::mat4 modelview( 1.0f );
        modelview = glm::translate(modelview, glm::vec3(0.0f, 0.0f, -2.0f) );
        modelview = glm::rotate(modelview, glm::radians(angle), glm::vec3(1.0f, 0.0f, 0.0f));
        modelview = glm::rotate(modelview, glm::radians(angle*0.5f), glm::vec3(0.0f, 1.0f, 0.0f));
        angle += 0.5f;
        glm::mat4 mvp = project * modelview;

        glm::vec2 vpPt(0.0f, 0.0f);
        float dist = 0.0f;
        for (size_t i=0; i < varray.size(); ++i)
        {
            darray[i] = dist;
            glm::vec4 clip = mvp * glm::vec4(varray[i], 1.0f);
            glm::vec4 ndc  = clip / clip.w;
            glm::vec4 vpC  = wndmat * ndc;
            float len = i==0 ? 0.0f :  glm::length(vpPt - glm::vec2(vpC));
            vpPt = glm::vec2(vpC);
            dist += len;
        }
        glBufferSubData(GL_ARRAY_BUFFER, 0, darray.size()*sizeof(*darray.data()), darray.data());

        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp));
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)varray.size());

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

    return 0;
}


请参阅 OpenGL 3.1 中弃用的 glLineStipple OpenGL ES - 虚线

评论

0赞 ollydbg23 11/22/2023
非常好的答案。我看到你给了我们两组着色器,一组用于GL_LINES,另一组用于GL_LINE_STRIP。