在现代OpenGL中绘制直线

tez616oj  于 2022-11-04  发布在  其他
关注(0)|答案(1)|浏览(294)

我只是想在屏幕上画一条线。我使用的是OpenGl 4.6。我找到的所有教程都使用了glVertexPointer,据我所知,它已经过时了。
我知道如何使用缓冲区来绘制三角形,所以我尝试用一条线来绘制三角形。它不起作用,只是显示一个黑屏。(我使用GLFW和GLEW,我使用的是顶点+片段着色器,我已经在三角形上测试过了)

// Make line
float line[] = {
    0.0, 0.0,
    1.0, 1.0
};

unsigned int buffer; // The ID, kind of a pointer for VRAM
glGenBuffers(1, &buffer); // Allocate memory for the triangle
glBindBuffer(GL_ARRAY_BUFFER, buffer); // Set the buffer as the active array
glBufferData(GL_ARRAY_BUFFER, 2 * sizeof(float), line, GL_STATIC_DRAW); // Fill the buffer with data
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0); // Specify how the buffer is converted to vertices
glEnableVertexAttribArray(0); // Enable the vertex array

// Loop until the user closes the window
while (!glfwWindowShouldClose(window))
{
    // Clear previous
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw the line
    glDrawArrays(GL_LINES, 0, 2);

    // Swap front and back buffers
    glfwSwapBuffers(window);

    // Poll for and process events
    glfwPollEvents();
}

我的方向是正确的吗?还是一种完全不同的方法是当前的最佳实践?
如果是,我如何修复代码?

ztigrdn8

ztigrdn81#

问题在于glBufferData的调用。第二个参数是缓冲区的大小(以字节为单位)。由于顶点数组由2个坐标和2个组件组成,因此缓冲区的大小是4 * sizeof(float)而不是2 * sizeof(float)
glBufferData(GL_ARRAY_BUFFER, 2 * sizeof(float), line, GL_STATIC_DRAW);

glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(float), line, GL_STATIC_DRAW);

但请注意,这仍然不是“现代”OpenGL。如果您想使用核心配置文件OpenGL Context,那么您必须使用Shader程序和Vertex Array Object
但是,如果您使用coreOpenGL context,并且向前兼容位已设置,则行的宽度(glLineWidth)不能大于1.0。
请参阅OpenGL 4.6 API核心配置文件规范- E.2已弃用和删除的功能

宽线- * 线宽 * 值大于 1.0 将生成INVALID_VALUE错误。

你必须找到一个不同的方法。
我建议使用Shader,它可以沿着线条(甚至是线条循环)生成三角形图元。
任务是生成粗线条,尽可能减少CPU和GPU开销,这意味着避免CPU和几何着色器(或镶嵌着色器)上的多边形计算。
线的每一段由分别由2个三角形基元和6个顶点表示的四边形组成。

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

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

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

创建一个包含线条带的角点的数组。第一个点和最后一个点定义线条带的起点和终点切线。因此,您需要在线条前添加一个点,在线条后添加一个点。当然,通过比较索引为0和数组的长度,识别数组的第一个元素和最后一个元素是很容易的。但我们不想在着色器中做任何额外的检查。
如果必须绘制一个线循环,则必须将最后一个点添加到数组头部,并将第一个点添加到数组尾部。
点的数组被存储到Shader Storage Buffer Object。我们利用了SSBO的最后一个变量可以是可变大小数组的好处。在较早版本的OpenGL(或OpenGL ES)中,可以使用Uniform Buffer Object甚至Texture
着色器不需要任何顶点坐标或属性。我们只需要知道线段的索引。坐标存储在缓冲区中。为了找到索引,我们使用当前正在处理的顶点的索引(gl_VertexID)。
要绘制具有N段的线条带,必须处理6*(N-1)顶点。
我们必须创建一个“空的”顶点数组对象(没有任何顶点属性规范):

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

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

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

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

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

计算顶点坐标所属的线段的索引以及2个三角形中的点的索引:

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

由于我们正在绘制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]是计算斜接所必需的。
线条的粗细应该以像素为单位(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;
}

如果前一个或后一个点分别等于线段的起点和终点,斜接计算也会起作用。在这种情况下,直线的终点将垂直于其切线进行切割:

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));

在最后的顶点着色器中,我们只需要根据tri_i计算v_miter1v_miter2。使用斜接、线段的法向量和线宽(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);
}

最后,窗口坐标必须转换回剪辑空间坐标。从窗口空间转换到规范化设备空间。透视分割必须反向:

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

使用glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)创建的多边形:

演示程序使用GLFW API创建一个窗口,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);
}
)";

// main

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 )
        throw std::runtime_error( "error initializing glfw" );
    GLFWwindow *window = glfwCreateWindow( 800, 600, "GLFW OGL window", nullptr, nullptr );
    if (window == nullptr)
    {
        glfwTerminate();
        throw std::runtime_error("error initializing window"); 
    }
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK)
        throw std::runtime_error("error initializing glew");

    OpenGL::CContext::TDebugLevel debug_level = OpenGL::CContext::TDebugLevel::all;
    OpenGL::CContext context;
    context.Init( debug_level );

    GLuint program  = OpenGL::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;

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

    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
    GLsizei N = (GLsizei)varray.size() - 2;

    glClearColor(0.0f, 0.0f, 0.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]);
            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;

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp1));
        glDrawArrays(GL_TRIANGLES, 0, 6*(N-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;

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glUniformMatrix4fv(loc_mvp, 1, GL_FALSE, glm::value_ptr(mvp2));
        glDrawArrays(GL_TRIANGLES, 0, 6*(N-1));

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

    return 0;
}

相关问题