C++ OpenGL 线宽

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3484260/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 12:56:37  来源:igfitidea点击:

OpenGL Line Width

c++openglline

提问by Matt

In my OpenGL app, it won't let me draw a line greater then ten pixels wide. Is there a way to make it draw more than ten pixels?

在我的 OpenGL 应用程序中,它不会让我画一条大于十像素宽的线。有没有办法让它绘制超过十个像素?

void OGL_Renderer::drawLine(int x, int y, int x2, int y2, int r, int g, int b, int a, int line_width)
{   
    glColor4ub(r, g, b, a);

    glLineWidth((GLfloat)line_width);
    glBegin(GL_LINES);
    glVertex2i(x, y);
    glVertex2i(x2, y2);
    glEnd();
    glLineWidth(1.0f);
}

采纳答案by AshleysBrain

You could try drawing a quad. Make it as wide as you want your line to be long, and tall as the line width you need, then rotate and position it where the line would go.

你可以尝试画一个四边形。使其宽度与您希望的线条一样长,高度与您需要的线条宽度相同,然后旋转并将其定位到线条所在的位置。

回答by AndreasT

Ah, now that I understood what you meant:

啊,现在我明白你的意思了:

  1. draw a one by one square.
  2. calc the length and orientation of the line
  3. stretch it to the length in x
  4. translate to startpos and rotate to line_orientation
  1. 一个一个地画一个正方形。
  2. 计算线的长度和方向
  3. 将其拉伸到 x 中的长度
  4. 转换为 startpos 并旋转为 line_orientation

or:

或者:

  1. get vector of line: v :(x2 - x1, y2 - y1)
  2. normalize v: n 3- get orthogonal (normal) of the vector : o (easy in 2d)
  3. add and subtract o from the line's end and start point to get 4 corner points
  4. draw a quad with these points.
  1. 得到线向量: v :(x2 - x1, y2 - y1)
  2. normalize v: n 3- 得到向量的正交(法线):o(在 2d 中很容易)
  3. 从线的终点和起点加上和减去 o 得到 4 个角点
  4. 用这些点画一个四边形。

回答by jcage

It makes sense that you can't. From the glLineWidth reference:

你不能这样做是有道理的。从 glLineWidth 参考:

The range of supported widths and the size difference between supported widths within the range can be queried by calling glGet with arguments GL_LINE_WIDTH_RANGE and GL_LINE_WIDTH_GRANULARITY.

支持的宽度范围和范围内支持的宽度之间的大小差异可以通过使用参数 GL_LINE_WIDTH_RANGE 和 GL_LINE_WIDTH_GRANULARITY 调用 glGet 来查询。

回答by Rabbid76

I recommend to use a Shader, which generates triangle primitivesalong a line strip (or even a line loop).
The task is to generate thick line strip, with as less CPU and GPU overhead as possible. That means to avoid computation of polygons on the CPU as well as geometry shaders (or tessellation shaders).

我建议使用Shader,它沿线带(甚至线环)生成三角形基元
任务是生成粗线条,尽可能减少 CPU 和 GPU 开销。这意味着要避免在 CPU 以及几何着色器(或曲面细分着色器)上计算多边形。

Each segment of the line consist of a quad represented by 2 triangle primitives respectively 6 vertices.

线的每一段由一个四边形组成,由 2 个三角形基元分别代表 6 个顶点。

0        2   5
 +-------+  +
 |     /  / |
 |   /  /   |
 | /  /     |
 +  +-------+
1   3        4

Between the line segments the miter hast to be found and the quads have to be cut to the miter.

必须找到线段之间的斜接,并且必须将四边形切割成斜接。

+----------------+
|              / |
| segment 1  /   |
|          /     |
+--------+       |
         | segment 2
         |       |
         |       |
         +-------+

Create an array with the corners points of the line strip. The array has to contain the first and the last point twice. Of course it would be easy, to identify the first and last element of the array by comparing the index to 0 and the length of the array, but we don't want to do any extra checks in the shader.
If a line loop has to be draw, then the last point has to be add to the array head and the first point to its tail.

使用线带的角点创建一个数组。该数组必须包含第一个和最后一个点两次。当然,通过比较索引与 0 和数组的长度来识别数组的第一个和最后一个元素会很容易,但我们不想在着色器中做任何额外的检查。
如果必须绘制线循环,则必须将最后一个点添加到数组头部,并将第一个点添加到数组尾部。

The array of points is stored to a Shader Storage Buffer Object. We use the benefit, that the last variable of the SSBO can be an array of variable size. In older versions of OpenGL (or OpenGL ES) a Uniform Buffer Objector even a Texturecan be used.

点数组存储到着色器存储缓冲区对象。我们利用了好处,即 SSBO 的最后一个变量可以是一个可变大小的数组。在旧版本的 OpenGL(或 OpenGL ES)中,可以使用统一缓冲区对象甚至纹理

The shader doesn't need any vertex coordinates or attributes. All we have to know is the index of the line segment. The coordinates are stored in the buffer. To find the index we make use of the the index of the vertex currently being processed (gl_VertexID).
To draw a line strip with Nsegments, 6*(N-1)vertices have tpo be processed.

着色器不需要任何顶点坐标或属性。我们只需要知道线段的索引。坐标存储在缓冲区中。为了找到索引,我们使用当前正在处理的顶点的索引 ( gl_VertexID)。
要绘制带N线段的线带,6*(N-1)必须处理顶点 tpo。

We have to create an "empty" Vertex Array Object(without any vertex attribute specification):

我们必须创建一个“空”的顶点数组对象(没有任何顶点属性规范):

glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

And to draw 2*(N-1)triangle (6*(N-1)vertices):

并绘制2*(N-1)三角形(6*(N-1)顶点):

glDrawArrays(GL_TRIANGLES, 0, 6*(N-1));

For the coordinate array in the SSBO, the data type vec4is used (Pleas believe me, you don't want to use vec3):

对于 SSBO 中的坐标数组,使用了数据类型vec4(请相信我,您不想使用vec3):

layout(std430, binding = 0) buffer TVertex
{
   vec4 vertex[];
};

Compute the index of the line segment, where the vertex coordinate belongs too and the index of the point in the 2 triangles:

计算线段的索引,顶点坐标也属于这里,以及2个三角形中点的索引:

int line_i = gl_VertexID / 6;
int tri_i  = gl_VertexID % 6;

Since we are drawing N-1line segments, but the number of elements in the array is N+2, the elements form vertex[line_t]to vertex[line_t+3]can be accessed for each vertex which is processed in the vertex shader.
vertex[line_t+1]and vertex[line_t+2]are the start respectively end coordinate of the line segment. vertex[line_t]and vertex[line_t+3]are required to compute the miter.

由于我们绘制N-1线段,但元件的阵列中的数量为N+2,所述元件形成vertex[line_t]vertex[line_t+3]可针对其在顶点着色器处理的每个顶点进行访问。
vertex[line_t+1]vertex[line_t+2]是线段的起点坐标和终点坐标。vertex[line_t]并且vertex[line_t+3]需要计算斜接。

The thickness of the line should be set in pixel unit (uniform float u_thickness). The coordinates have to be transformed from model space to window space. For that the resolution of the viewport has to be known (uniform vec2 u_resolution). Don't forget the perspective divide. The drawing of the line will even work at perspective projection.

线的粗细应以像素为单位(uniform float u_thickness)。坐标必须从模型空间转换到窗口空间。为此,必须知道视口的分辨率 ( uniform vec2 u_resolution)。不要忘记透视鸿沟。线条的绘制甚至可以在透视投影中工作。

vec4 va[4];
for (int i=0; i<4; ++i)
{
    va[i] = u_mvp * vertex[line_i+i];
    va[i].xyz /= va[i].w;
    va[i].xy = (va[i].xy + 1.0) * 0.5 * u_resolution;
}

The miter calculation even works if the predecessor or successor point is equal to the start respectively end point of the line segment. In this case the end of the line is cut normal to its tangent:

如果前导点或后继点等于线段的起点或终点,斜接计算甚至有效。在这种情况下,线的末端垂直于其切线切割:

vec2 v_line   = normalize(va[2].xy - va[1].xy);
vec2 nv_line  = vec2(-v_line.y, v_line.x);
vec2 v_pred   = normalize(va[1].xy - va[0].xy);
vec2 v_succ   = normalize(va[3].xy - va[2].xy);
vec2 v_miter1 = normalize(nv_line + vec2(-v_pred.y, v_pred.x));
vec2 v_miter2 = normalize(nv_line + vec2(-v_succ.y, v_succ.x));

In the final vertex shader we just need to calculate either v_miter1or v_miter2dependent on the tri_i. With the miter, the normal vector to the line segment and the line thickness (u_thickness), the vertex coordinate can be computed:

在最终的顶点着色器中,我们只需要计算v_miter1v_miter2依赖于tri_i. 使用斜接、线段的法向量和线宽 ( u_thickness),可以计算顶点坐标:

vec4 pos;
if (tri_i == 0 || tri_i == 1 || tri_i == 3)
{
    vec2 v_pred  = normalize(va[1].xy - va[0].xy);
    vec2 v_miter = normalize(nv_line + vec2(-v_pred.y, v_pred.x));

    pos = va[1];
    pos.xy += v_miter * u_thickness * (tri_i == 1 ? -0.5 : 0.5) / dot(v_miter, nv_line);
}
else
{
    vec2 v_succ  = normalize(va[3].xy - va[2].xy);
    vec2 v_miter = normalize(nv_line + vec2(-v_succ.y, v_succ.x));

    pos = va[2];
    pos.xy += v_miter * u_thickness * (tri_i == 5 ? 0.5 : -0.5) / dot(v_miter, nv_line);
}

Finally the window coordinates have to be transformed back to clip space coordinates. Transform from window space to normalized device space. The perspective divide has to be reversed:

最后,必须将窗口坐标转换回剪辑空间坐标。从窗口空间转换到标准化设备空间。必须扭转透视鸿沟:

pos.xy = pos.xy / u_resolution * 2.0 - 1.0;
pos.xyz *= pos.w;

The shader can generate the following polygons (rendered with glPolygonMode(GL_FRONT_AND_BACK, GL_LINE))

着色器可以生成以下多边形(用 渲染glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

(with default mode - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL))

(默认模式 - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

For the following simple demo program I've used the GLFWAPI for creating a window, GLEWfor loading OpenGL and GLM -OpenGL Mathematicsfor the math. I don't provide the code for the function CreateProgram, which just creates a program object, from the vertex shader and fragment shader source code:

对于下面的简单演示程序,我使用GLFWAPI 创建窗口,使用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 460

layout(std430, binding = 0) buffer TVertex
{
   vec4 vertex[]; 
};

uniform mat4  u_mvp;
uniform vec2  u_resolution;
uniform float u_thickness;

void main()
{
    int line_i = gl_VertexID / 6;
    int tri_i  = gl_VertexID % 6;

    vec4 va[4];
    for (int i=0; i<4; ++i)
    {
        va[i] = u_mvp * vertex[line_i+i];
        va[i].xyz /= va[i].w;
        va[i].xy = (va[i].xy + 1.0) * 0.5 * u_resolution;
    }

    vec2 v_line  = normalize(va[2].xy - va[1].xy);
    vec2 nv_line = vec2(-v_line.y, v_line.x);

    vec4 pos;
    if (tri_i == 0 || tri_i == 1 || tri_i == 3)
    {
        vec2 v_pred  = normalize(va[1].xy - va[0].xy);
        vec2 v_miter = normalize(nv_line + vec2(-v_pred.y, v_pred.x));

        pos = va[1];
        pos.xy += v_miter * u_thickness * (tri_i == 1 ? -0.5 : 0.5) / dot(v_miter, nv_line);
    }
    else
    {
        vec2 v_succ  = normalize(va[3].xy - va[2].xy);
        vec2 v_miter = normalize(nv_line + vec2(-v_succ.y, v_succ.x));

        pos = va[2];
        pos.xy += v_miter * u_thickness * (tri_i == 5 ? 0.5 : -0.5) / dot(v_miter, nv_line);
    }

    pos.xy = pos.xy / u_resolution * 2.0 - 1.0;
    pos.xyz *= pos.w;
    gl_Position = pos;
}
)";

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

out vec4 fragColor;

void main()
{
    fragColor = vec4(1.0);
}
)";

GLuint CreateSSBO(std::vector<glm::vec4> &varray)
{
    GLuint ssbo;
    glGenBuffers(1, &ssbo);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo );
    glBufferData(GL_SHADER_STORAGE_BUFFER, varray.size()*sizeof(*varray.data()), varray.data(), GL_STATIC_DRAW); 
    return ssbo;
}

int main(void)
{
    if ( glfwInit() == 0 )
        return 0;
    GLFWwindow *window = glfwCreateWindow( 800, 600, "GLFW OGL window", nullptr, nullptr );
    if ( window == nullptr )
    {
        glfwTerminate();
        retturn 0;
    }
    glfwMakeContextCurrent(window);
    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_thi  = glGetUniformLocation(program, "u_thickness");

    glUseProgram(program);
    glUniform1f(loc_thi, 20.0);

    GLushort pattern = 0x18ff;
    GLfloat  factor  = 2.0f;

    glm::vec4 p0(-1.0f, -1.0f, 0.0f, 1.0f);
    glm::vec4 p1(1.0f, -1.0f, 0.0f, 1.0f);
    glm::vec4 p2(1.0f, 1.0f, 0.0f, 1.0f);
    glm::vec4 p3(-1.0f, 1.0f, 0.0f, 1.0f);
    std::vector<glm::vec4> varray1{ p3, p0, p1, p2, p3, p0, p1 };
    GLuint ssbo1 = CreateSSBO(varray1);

    std::vector<glm::vec4> varray2;
    for (int u=-8; u <= 368; u += 8)
    {
        double a = u*M_PI/180.0;
        double c = cos(a), s = sin(a);
        varray2.emplace_back(glm::vec4((float)c, (float)s, 0.0f, 1.0f));
    }
    GLuint ssbo2 = CreateSSBO(varray2);

    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    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));
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo1);
        GLsizei N1 = (GLsizei)varray1.size()-2;
        glDrawArrays(GL_TRIANGLES, 0, 6*(N1-1));

        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));
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo2);
        GLsizei N2 = (GLsizei)varray2.size()-2;
        glDrawArrays(GL_TRIANGLES, 0, 6*(N2-1));

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

    return 0;
}