How do you render primitives as wireframes in OpenGL?
10 Answers
From http://cone3d.gamedev.net/cgi-bin/index.pl?page=tutorials/ogladv/tut5
// Turn on wireframe mode
glPolygonMode(GL_FRONT, GL_LINE);
glPolygonMode(GL_BACK, GL_LINE);
// Draw the box
DrawBox();
// Turn off wireframe mode
glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_FILL);
Assuming a forward-compatible context in OpenGL 3 and up, you can either use glPolygonMode
as mentioned before, but note that lines with thickness more than 1px are now deprecated. So while you can draw triangles as wire-frame, they need to be very thin. In OpenGL ES, you can use GL_LINES
with the same limitation.
In OpenGL it is possible to use geometry shaders to take incoming triangles, disassemble them and send them for rasterization as quads (pairs of triangles really) emulating thick lines. Pretty simple, really, except that geometry shaders are notorious for poor performance scaling.
What you can do instead, and what will also work in OpenGL ES is to employ fragment shader. Think of applying a texture of wire-frame triangle to the triangle. Except that no texture is needed, it can be generated procedurally. But enough talk, let's code. Fragment shader:
in vec3 v_barycentric; // barycentric coordinate inside the triangle
uniform float f_thickness; // thickness of the rendered lines
void main()
{
float f_closest_edge = min(v_barycentric.x,
min(v_barycentric.y, v_barycentric.z)); // see to which edge this pixel is the closest
float f_width = fwidth(f_closest_edge); // calculate derivative (divide f_thickness by this to have the line width constant in screen-space)
float f_alpha = smoothstep(f_thickness, f_thickness + f_width, f_closest_edge); // calculate alpha
gl_FragColor = vec4(vec3(.0), f_alpha);
}
And vertex shader:
in vec4 v_pos; // position of the vertices
in vec3 v_bc; // barycentric coordinate inside the triangle
out vec3 v_barycentric; // barycentric coordinate inside the triangle
uniform mat4 t_mvp; // modeview-projection matrix
void main()
{
gl_Position = t_mvp * v_pos;
v_barycentric = v_bc; // just pass it on
}
Here, the barycentric coordinates are simply (1, 0, 0)
, (0, 1, 0)
and (0, 0, 1)
for the three triangle vertices (the order does not really matter, which makes packing into triangle strips potentially easier).
The obvious disadvantage of this approach is that it will eat some texture coordinates and you need to modify your vertex array. Could be solved with a very simple geometry shader but I'd still suspect it will be slower than just feeding the GPU with more data.
If you are using the fixed pipeline (OpenGL < 3.3) or the compatibility profile you can use
//Turn on wireframe mode
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//Draw the scene with polygons as lines (wireframe)
renderScene();
//Turn off wireframe mode
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
In this case you can change the line width by calling glLineWidth
Otherwise you need to change the polygon mode inside your draw method (glDrawElements, glDrawArrays, etc) and you may end up with some rough results because your vertex data is for triangles and you are outputting lines. For best results consider using a Geometry shader or creating new data for the wireframe.
In Modern OpenGL(OpenGL 3.2 and higher), you could use a Geometry Shader for this :
#version 330
layout (triangles) in;
layout (line_strip /*for lines, use "points" for points*/, max_vertices=3) out;
in vec2 texcoords_pass[]; //Texcoords from Vertex Shader
in vec3 normals_pass[]; //Normals from Vertex Shader
out vec3 normals; //Normals for Fragment Shader
out vec2 texcoords; //Texcoords for Fragment Shader
void main(void)
{
int i;
for (i = 0; i < gl_in.length(); i++)
{
texcoords=texcoords_pass[i]; //Pass through
normals=normals_pass[i]; //Pass through
gl_Position = gl_in[i].gl_Position; //Pass through
EmitVertex();
}
EndPrimitive();
}
Notices :
- for points, change
layout (line_strip, max_vertices=3) out;
tolayout (points, max_vertices=3) out;
- Read more about Geometry Shaders
A good and simple way of drawing anti-aliased lines on a non anti-aliased render target is to draw rectangles of 4 pixel width with an 1x4 texture, with alpha channel values of {0.,1.,1.,0.}, and use linear filtering with mip-mapping off. This will make the lines 2 pixels thick, but you can change the texture for different thicknesses. This is faster and easier than barymetric calculations.
use this function: void glPolygonMode( GLenum face, GLenum mode);
face : Specifies the polygons that mode applies to. can be GL_FRONT for front side of the polygone and GL_BACK for his back and GL_FRONT_AND_BACK for both.
mode : Three modes are defined and can be specified in mode:
GL_POINT :Polygon vertices that are marked as the start of a boundary edge are drawn as points.
GL_LINE : Boundary edges of the polygon are drawn as line segments. (your target)
GL_FILL : The interior of the polygon is filled.
P.S : glPolygonMode controls the interpretation of polygons for rasterization in the graphics pipeline.
for more information look at the OpenGL reference pages in khronos group : https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glPolygonMode.xhtml
If it's OpenGL ES 2.0 you're dealing with, you can choose one of draw mode constants from
GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES,
to draw lines,
GL_POINTS
(if you need to draw only vertices), or
GL_TRIANGLE_STRIP
, GL_TRIANGLE_FAN
, and GL_TRIANGLES
to draw filled triangles
as first argument to your
glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices)
or
glDrawArrays(GLenum mode, GLint first, GLsizei count)
calls.