Saltar al contenido

Ancho de línea OpenGL

Después de de nuestra larga compilación de información dimos con la solución este asunto que tienen algunos lectores. Te dejamos la solución y esperamos serte de gran ayuda.

Solución:

Podrías intentar dibujar un quad. Hágalo tan ancho como desee que su línea sea larga y tan alto como el ancho de línea que necesita, luego gírelo y colóquelo donde iría la línea.

Recomiendo usar un Shader, que genera primitivas triangulares a lo largo de una franja de línea (o incluso un bucle de línea).
La tarea es generar una franja de líneas gruesas, con la menor sobrecarga de CPU y GPU posible. Eso significa evitar el cálculo de polígonos en la CPU, así como sombreadores de geometría (o sombreadores de teselación).

Cada segmento de la línea consta de un cuadrante representado por 2 primitivas triangulares respectivamente 6 vértices.

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

Entre los segmentos de línea hay que encontrar la inglete y los cuadrantes deben cortarse a inglete.

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

Crear un array con los puntos de las esquinas de la franja de línea. los array tiene que contener el primer y el último punto dos veces. Por supuesto, sería fácil identificar el primer y último elemento del array comparando el índice con 0 y la longitud del array, pero no queremos hacer comprobaciones adicionales en el sombreador.
Si se debe dibujar un bucle de línea, entonces el último punto debe agregarse al array cabeza y el primer punto a su cola.

los array de puntos se almacena en un objeto de búfer de almacenamiento de sombreado. Usamos el beneficio de que la última variable del SSBO puede ser un array de tamaño variable. En versiones anteriores de OpenGL (u OpenGL ES) se puede utilizar un objeto de búfer uniforme o incluso una textura.

El sombreador no necesita coordenadas de vértice ni attributes. Todo lo que tenemos que saber es el índice del segmento de línea. Las coordenadas se almacenan en el búfer. Para encontrar el índice utilizamos el índice del vértice que se está procesando actualmente (gl_VertexID).
Para dibujar una franja de línea con N segmentos, 6*(N-1) los vértices deben ser procesados.

Tenemos que crear un objeto de matriz de vértices “vacío” (sin ningún vértice attribute especificación):

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

Y dibujar 2*(N-1) triángulo6*(N-1) vértices):

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

Para la coordenada array en el SSBO, el tipo de datos vec4 se usa (Por favor, créeme, no quieres usar vec3):

layout(std430, binding = 0) buffer TVertex

   vec4 vertex[];
;

Calcule el índice del segmento de línea, donde también pertenece la coordenada del vértice y el índice del punto en los 2 triángulos:

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

Ya que estamos dibujando N-1 segmentos de línea, pero el número de elementos en el array es N+2, los elementos forman vertex[line_t] para vertex[line_t+3] se puede acceder a cada vértice que se procesa en el sombreador de vértices.
vertex[line_t+1] y vertex[line_t+2] son la coordenada inicial y final del segmento de línea. vertex[line_t] y vertex[line_t+3] son necesarios para calcular el inglete.

El grosor de la línea debe establecerse en unidades de píxeles (uniform float u_thickness). Las coordenadas deben transformarse del espacio modelo al espacio de la ventana. Para eso, se debe conocer la resolución de la ventana gráfica (uniform vec2 u_resolution). No olvide la división de perspectivas. El dibujo de la línea funcionará incluso en la proyección en perspectiva.

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;

El cálculo de inglete funciona incluso si el punto predecesor o sucesor es igual al punto inicial o final del segmento de línea. En este caso, el final de la línea se corta normal a su tangente:

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

En el sombreador de vértices final solo necesitamos calcular v_miter1 o v_miter2 dependiente de la tri_i. Con el inglete, el vector normal al segmento de línea y el grosor de la línea (u_thickness), la coordenada del vértice se puede calcular:

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

Finalmente, las coordenadas de la ventana deben transformarse de nuevo a las coordenadas del espacio de recorte. Transfórmate de espacio de ventana a espacio de dispositivo normalizado. La brecha de perspectiva debe revertirse:

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

El sombreador puede generar los siguientes polígonos (renderizados con glPolygonMode(GL_FRONT_AND_BACK, GL_LINE))

(con modo predeterminado - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL))

Para el siguiente programa de demostración simple, utilicé la API GLFW para crear una ventana, GLEW para cargar OpenGL y GLM -OpenGL Mathematics para las matemáticas. No proporciono el código para la función CreateProgram, que simplemente crea un objeto de programa, a partir del código fuente del sombreador de vértices y del sombreador de fragmentos:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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()
 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 &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 varray1 p3, p0, p1, p2, p3, p0, p1 ;
    GLuint ssbo1 = CreateSSBO(varray1);

    std::vector 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))
      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;

Ah, ahora que entendí lo que querías decir:

  1. dibuja un cuadrado uno por uno.
  2. Calcule la longitud y la orientación de la línea.
  3. estíralo a la longitud en x
  4. traducir a startpos y rotar a line_orientation

o:

  1. obtener el vector de la línea: v: (x2 - x1, y2 - y1)
  2. normalizar v: n 3- obtener ortogonal (normal) del vector: o (fácil en 2d)
  3. sumar y restar o del final de la línea y el punto de inicio para obtener 4 puntos de esquina
  4. dibuja un cuadrante con estos puntos.

Reseñas y puntuaciones del artículo

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *