#### Response title

This is preview!

Loading...

Copy code

Permalink

Please tell us why you want to mark the subject as inappropriate.
(Maximum 200 characters)

Private Message

Feedback

Private Message

Type the characters you see in the picture below.

Type the characters you see in the picture below.

Attach files

Loading User Profile...

This is preview!

Sign In

You can also use the below options to login

- Login with Facebook
- Login with Google
- Login with Yahoo

- Recent Topics
- All Forums
- Search All
- Tags
- Author
- Advanced Search

Search

Move this topic

Hi,

does anyone of you know a good solution to drawing thick line segments with proper end points? Right now, I simply create a GL_LINES mesh and draw it using the fixed function pipeline, but when I specify glLineWidth(8.0f), gaps appear at sharp corners. This is a common problem.

I was hoping one of you would know how to fill the gaps between two segments, either by pre-calculation during mesh generation, or by using a geometry shader. Any help is appreciated.

-Paul

1

Couldn't a line segment be represented by a triangle strip? As I imagine it, as you move across the line, you find the normal to it and calculate a "left" and "right" point to it, based on the the thickness that you want.

For straight parts you would just need 4 points and for curved / "corner" parts you would need a higher "sampling".

-Nik.

Edit: I might be wrong, but If you want to use a shader, the tessellation shader is probably more appropriate than the geometry shader.

For straight parts you would just need 4 points and for curved / "corner" parts you would need a higher "sampling".

-Nik.

Edit: I might be wrong, but If you want to use a shader, the tessellation shader is probably more appropriate than the geometry shader.

Leave a comment on okinp's reply

I remember using a geometry shader for that purpose.

Ben

The idea was to pass line_strip to the shader. For each original line you need to pass the previous and the next line as well. Then you output triangle strips by computing the normal and cutting the corners...

I'll try to find this shader later, I think it's on my external hard drive.

Ben

Leave a comment on I33N's reply

Thank you both for your replies.

@I33N: I would very much appreciate getting a look at your shader.

My current line of thought is to convert the line segments to a GL_TRIANGLES mesh on the CPU, using my own algorithm. I had hoped to find a ready-made piece of code somewhere, but wasn't successful. On paper, the algorithm is finished, I hope to implement it in the coming days. If it works, I will share it with you.

-Paul

Leave a comment on paul.houx's reply

Leave a comment on safetydank's reply

I just found the geometry shader used to draw prismoid using this technique but it's pretty much the same, you just have to remove the other faces... Got this code from the grasshopper.

Ben

- uniform mat4 ModelviewProjection;
- varying in vec3 vPosition[4]; // Four inputs since we're using GL_LINE_STRIP_ADJACENCY
- varying in vec3 vNormal[4]; // Orientation vectors are necessary for consistent alignment
- vec4 prismoid[8]; // Scratch space for the eight corners of the prismoid
- void emit(int a, int b, int c, int d)
- {
- gl_Position = prismoid[a]; EmitVertex();
- gl_Position = prismoid[b]; EmitVertex();
- gl_Position = prismoid[c]; EmitVertex();
- gl_Position = prismoid[d]; EmitVertex();
- EndPrimitive();
- }
- void main()
- {
- // Compute orientation vectors for the two connecting faces:
- vec3 p0, p1, p2, p3;
- p0 = vPosition[0]; p1 = vPosition[1];
- p2 = vPosition[2]; p3 = vPosition[3];
- vec3 n0 = normalize(p1-p0);
- vec3 n1 = normalize(p2-p1);
- vec3 n2 = normalize(p3-p2);
- vec3 u = normalize(n0+n1);
- vec3 v = normalize(n1+n2);
- // Declare scratch variables for basis vectors:
- vec3 i,j,k; float r = Radius;
- // Compute face 1 of 2:
- j = u; i = vNormal[1]; k = cross(i, j); i *= r; k *= r;
- prismoid[0] = ModelviewProjection * vec4(p1 + i + k, 1);
- prismoid[1] = ModelviewProjection * vec4(p1 + i - k, 1);
- prismoid[2] = ModelviewProjection * vec4(p1 - i - k, 1);
- prismoid[3] = ModelviewProjection * vec4(p1 - i + k, 1);
- // Compute face 2 of 2:
- j = v; i = vNormal[2]; k = cross(i, j); i *= r; k *= r;
- prismoid[4] = ModelviewProjection * vec4(p2 + i + k, 1);
- prismoid[5] = ModelviewProjection * vec4(p2 + i - k, 1);
- prismoid[6] = ModelviewProjection * vec4(p2 - i - k, 1);
- prismoid[7] = ModelviewProjection * vec4(p2 - i + k, 1);
- // Emit the six faces of the prismoid:
- emit(0,1,3,2); emit(5,4,6,7);
- emit(4,5,0,1); emit(3,2,7,6);
- emit(0,3,4,7); emit(2,1,6,5);
- }

I was mistaking you have to pass line_strip_adjacency and not just line_strip.

Hope it helps!

Ben

Leave a comment on I33N's reply

@Safetydank: your link didn't work (the book isn't visible), but searching for "polyline quadstrips" gave me a bunch of new ideas on how to approach this problem. Thanks!

@Ben: also very helpful, I came across that shader before. It even produces 3D lines made of boxes, cool. However, I think with the knowledge I have now I can come up with a 2D version based on triangles myself. I prefer 2D lines over 3D ones for this project, and I also want to run this application on slightly older hardware that doesn't have geometry shader support (read: my computer at home).

I will keep you guys posted. Thanks for helping me out.

-Paul

Leave a comment on paul.houx's reply

Hi,

I managed to come up with a solution of my own. It's basically a 2D version of the shader found by Ben, but with different characteristics for the miter (explained below).

Construction of the line starts with a segment:

P0 and P1 are Vec2f's. The line is defined by Vec2f line = p1 - p0

and the normal (n) is defined by Vec2f normal = Vec2f( -line.y, line.x).normalized().

You could then draw the segment by multiplying the normal by the thickness and add or subtract it from the end points:

Vec2f a = p0 - thickness * normal;

Vec2f b = p0 + thickness * normal;

Vec2f c = p1 - thickness * normal;

Vec2f d = p1 + thickness * normal;

But when two segments are joined, we need to calculate the cross-section of the joint to form the miter:

First, I calculate the tangent (t) as follows:

Vec2f tangent = ( (p2-p1).normalized() + (p1-p0).normalized() ).normalized()

Next, I find the miter line (m), which is the normal of the tangent:

Vec2f miter = Vec2f( -tangent.y, tangent.x )

The correct length (d) of the miter can then be found by projecting it on one of the normals (shown in red) using the dotproduct. This gives us a value that is the inverse of the desired length, so:

float length = thickness / miter.dot( normal )

That's all the information we need. Now we can construct the triangle mesh:

The result is quite nice, even for sharp angles:

Note that this algorithm does not always give the desired result at very sharp angles or if the line segment is too short. This can be detected and taken care of, for example by converting the sharp angle to a blunt one by adding an extra point.

You can play with the algorithm in this sample:

It can also be found on GitHub:

I haven't written the TriMesh code yet, and expect some difficulties with texturing (all triangles have different sizes, requiring perspective correction). But I am hopeful I can soon draw awesome 2D textured lines :) Thanks everyone for helping me out.

-Paul

Leave a comment on paul.houx's reply

Hi,

I managed to write a geometry shader based on the discussion above. It accepts a GL_LINES_ADJACENCY_EXT mesh as input and outputs a smooth line without gaps.

It is slightly different from the explanation given earlier. Instead of calculating the tangent, I simply calculate the 3 normals (previous segment, current segment and next segment) and average 2 of them to find the miter.

Additional code checks for very sharp angles using a dotproduct and performs some naive culling to prevent visual glitches. Shader code can be found in the ZIP file (see end of this post).

At the corners, it extends the line segments to create nice sharp corners that maintain the thickness and appearance of the line.

At very sharp corners, however, it will automatically fall back to normal OpenGL behavior to prevent visual glitches.

Using the shader, you can now (relatively) easily draw proper lines of any thickness. Texturing is not supported at the moment, but it should not be too hard to add it to the shader.

A geometry shader is the only way to go, because I wanted a fixed thickness in pixels. Pre-calculating the mesh in 3D would result in perspective distortion when using a 3D camera. If you would not use a shader, you'd have to recreate the mesh every time the camera moves or zooms and that would put quite a burden on the CPU.

It proved to be a bit tricky doing the conversion from 3D to 2D screen space in the shader. After many unfruitful attempts, I found out it didn't work because I was passing the window size as a Vec2i instead of a Vec2f. This caused the shader to simply not draw anything. So be careful when adopting this piece of code: make sure to properly cast getWindowSize() to a Vec2f when supplying it to the shader!

The sample and shader can be downloaded here:

Edit: this sample has been updated. See below for more information.

Have fun with it,

-Paul

Very cool exploration. Geometry shaders are nice to work with because you can easily write expressive code while getting much-better-than-CPU performance. Not sure about using them for a more fundamental task like this, though.

I've found that creating lines the same way you create ribbons with GL_TRIANGLES (recommended) or GL_QUADS accomplishes this task. Because the points in each segment are shared with the next, there are no open corners.

Leave a comment on paul.houx's reply

Hi,

the GL_TRIANGLES option would not work for me in this case. I had to draw 2D lines in a 3D world, so had to emulate OpenGL's own fixed-function line drawing as much as possible. Had I used a 3D triangle mesh (where all z-coordinates were 0), the line would become thinner at greater distances and thicker when near the camera. That was not what I wanted.

Since I had to draw in 2D screen space and had to avoid recreating the mesh every time the camera moves, I was forced to use a geometry shader. Apart from that, it was a nice opportunity to learn about geometry shaders in the first place.

Here's a screenshot to compare line drawing without and with the shader (click to enlarge):

As you can see, the map is set in perspective, but the line had to be of the same thickness wherever and however it is drawn. The reason for that is that the map can be zoomed quite a lot, from street-level to viewing the whole country. The line has to be drawn with the same thickness all the time, so it remains clearly visible at every size and does not disappear when zooming out.

Here's a zoomed out version of that same track:

-Paul

Leave a comment on paul.houx's reply

Hi,

I have updated the sample to explore a few different line rendering techniques, as well as texturing. You can still download it from: http://www.cowlumbus.nl/forum/GeometryShader.zip

When playing with it, try this:

- Draw a zig-zagging line by placing points with your mouse. Make a few very sharp corners.
- Change the miter limit using the Plus (+) and Minus (-) keys and note how some of the corners will get cut off, or not.
- Reduce the miter limit to the minimum by repeatedly pressing Minus (-), then switch to the second Geometry shader by pressing F8. The outline will remain the same, but tessellation will be different which in some cases may look better (I personally still prefer the looks of shader 1 (F7)).
- Shader 1 will be better for drawing lines with corners, but can't handle very sharp corners well.
- Shader 2 will produce smoother curves, but the line will not always be centered on the control points and won't appear to have the same thickness everywhere.

-Paul

Leave a comment on paul.houx's reply

Hi Paul,

Really great. Thanks for sharing.

I got some shader errors in OS X 10.6:

- Could not compile shader:FRAGMENT: ERROR: 0:7: 'in' : supported in geometry shaders only
- ERROR: 0:7: '' : 'varying in's need to be of array type
- ERROR: 0:11: '.' : cannot apply dot operator to an array

modifying line 7 of lines_frag.glsl fixed it.

- varying vec2 gsTexCoord;

And it seems to work very nicely. Thank you.

Thanks for that, Gabor. Yup, it is inevitable that the shader doesn't work the same way on the Mac as it does on Windows. It usually takes a little bit of tinkering.

-Paul

Leave a comment on gabor_papp's reply

hi Paul,

could you give me some advices how to make it work with line loops, please? i briefly added the first and last 2 vertex indices to the indices vector, but it does not seem right. do i have to create some more adjacency vertices?

Hi,

the shader in this sample works with GL_LINES_ADJACENCY_EXT meshes. This means that for **each **segment (every 2 vertices) of the "line loop", you will have to supply 4 vertices in the index buffer.

Alternatively, you can use GL_LINE_STRIP_ADJACENCY_EXT, in which case your code should work. See also: http://web.engr.oregonstate.edu/~mjb/glman/ClassNotes/geometry_shader.pdf (page 3)

-Paul

Leave a comment on gabor_papp's reply

hi Paul,

thanks for the explanation and the link. it works fine now.

Gabor

Leave a comment on gabor_papp's reply

I tried to use some of the code you posted but I have problem with WIN_SCALE attribute. I am working with QGLfunctions API which doesn't provide function getWindowSize(). My program is about to draw lines eed smooth th n andick lines.

Hi Mirzavio,

the getWindowSize() function simply returns the resolution of the window in pixels. You should be able to get that information from somewhere, for example from a window resize event. Note that you need to supply them as floats, not integers, to make the shader work.

-Paul

Leave a comment on mirzavio's reply

Hi Paul,

this is very nice post. Thank you for sharing it with us. I am working on similar project and I would like to test CPU performance when calculating these points for thick line. Since you omitted part of calculations, do you have the code in C++ perhaps?

Hi,

thank you for your reply. I am quite sure all code can be found in the posts. The basic algorithm is outlined above, and the C++ code can be found in the accompanying ZIP:

The shader code can be found in the sample on GitHub:

-Paul

Leave a comment on h.ajla's reply

Thanks for quick reply.

So you recommend me rewriting your shaders since I want to calculate points on CPU instead on GPU. There are two geom shaders, which one should I use.

I am sorry for asking too much, I would be very happy if you answer me.

Ajla

Leave a comment on h.ajla's reply

Change topic type

Link this topic

Provide the permalink of a topic that is related to this topic

Reply to paul.houx's question

{"z5229916":[23286000001271003,23286000001270103],"z3493023":[23286000001298007],"z30213818":[23286000001856103],"z3906314":[23286000001268016,23286000001271017,23286000001270111,23286000001269127,23286000001298005,23286000001297011,23286000001297067,23286000001306055,23286000001309019,23286000001853039,23286000002040031,23286000002043009],"z37196665":[23286000002037007,23286000002042003],"z257636":[23286000001269043],"z3501876":[23286000001306035,23286000001309009,23286000001311007],"z4730534":[23286000001270101]}

**22**Replies**40122**Views**0**Followers

- © 2015ZOHO Corp.
- All Rights Reserved.
- Privacy
- Terms & Conditions
- RSS
- Powered by