Methods for Drawing a Low-Resolution 2D Line Gradually?

Started by
16 comments, last by Sean_Seanston 9 years, 1 month ago

Hope that title was descriptive enough, but I've prepared a diagram to describe exactly what I'm talking about:

oi62.tinypic.com/11imhr7.jpg

As you can see, the image is split into 3 panes. At the top we have two icons that I want to draw a line between. At the bottom there's a complete line between the two as I want it to look when it's finished, and the middle shows a partway progression of the line between the two states.

What I'm trying to achieve is basically the ability to draw a line between 2 points in the same style as if you used the pencil tool in Photoshop by clicking somewhere then holding shift and clicking somewhere else to draw a straight line between them. I don't want it to be anti-aliased or hi-res, and I need to be able to control the thickness of the line while retaining the pixellated look so it fits in with the simplistic art style I'm using.

What I want to use it for is graphically representing the path an object is travelling along over time, so it would start off as some kind of dotted line (e.g. only occasional square sections of the line being drawn) and then as the object got closer the line would be drawn in to represent the object's current position, as shown in the middle pane.

So... how might I accomplish this? I'm using C++ and OpenGL if it matters.

For something that seems so simple, it sure gets complicated when you don't have any experience with this kind of thing and start trying to come up with actual solutions that can be coded.

We start with the 2 points, so we'll need to come up with the equation of the line between them and the slope of that line. The slope tells us how many pixels y will increase by for every pixel that x increases, which is clearly what we need here, but where do we go from there?

My first semi-naive and probably impractical (but maybe someone else knows better) thought was:

1. Create an image file of a square representing the minimum "brush size" of the line.

2. Draw it onto the start position of the line, then draw it for every subsequent step along the line.

Am I right in thinking that would be terribly inefficient? Sounds like it would be. I'm also not sure if I really know how to determine exactly where each "brush" should be drawn.

Then I searched around a bit and heard about Bresenham's Line Algorithm. That looks like it would be relevant. The description on Wikipedia states:

"determines the points of an n-dimensional raster that should be selected in order to form a close approximation to a straight line between two points."

So that's exactly it. My only concerns would be:

1. Is it a simple matter to replicate a lower-resolution line than the actual resolution of the display? i.e. Make it nice and blocky and easily change this resolution at will.

2. Even if I do use that algorithm to determine what pixels should be coloured in, how should I draw them? Would it be best to use some kind of OpenGL primitives or similar OpenGL function? It would have to be horribly inefficient to use a textured quad for every square "pixel" of the line, but I'm not sure what the obvious alternative is because I haven't done anything like this before.

Thoughts?

Advertisement

0) Yes, the algorithm is simple enough. Figure out the rise and run, then step along each pixel and draw it.

1) You can change the line width the same way, but it requires a bit of math to build properly. Ensure that you have the correct number of parallel pixels involved in the line, or draw the correct number of pixels above/below/beside the pixel you are processing. Determining the correct number depends on the angle of the line.

2) You should only do this if you are building your own rasterizer. If you are using OpenGL it already has these primitives built in. Your graphics card and drivers also already have these primitives built in. A thick line might require changing thousands of pixels. For drawing an individual line you can either make thousands of individual calls, potentially requiring thousands of trips across the system bus and across the hardware, or you can make a single call, with a single trip out to the graphics card. It should be clear that one is several orders of magnitude more efficient.

3) Thoughts are that as a learning exercise to understand how the raserizers work it is a good thing to experience on your own. But after you've build your own software rasterizer, throw it away and go back to the hardware accelerated graphics systems.

2) You should only do this if you are building your own rasterizer. If you are using OpenGL it already has these primitives built in. Your graphics card and drivers also already have these primitives built in. A thick line might require changing thousands of pixels. For drawing an individual line you can either make thousands of individual calls, potentially requiring thousands of trips across the system bus and across the hardware, or you can make a single call, with a single trip out to the graphics card. It should be clear that one is several orders of magnitude more efficient.

Well for the sake of getting a game/feature completed, I'll take a quick and easy way here if at all possible. It's annoying getting hung up on this little features...

So am I right in thinking it's just a matter of using GL_LINES with some vertex data?

For a low-res effect would you have to do something with the fragment shader? And would that be quite a simple thing to implement, or somewhat tedious/time-consuming? I just wouldn't be quite sure how/where to begin...

Though I have heard about rendering the whole screen to a texture and then resizing that texture before displaying it on screen as a means of getting a low-res look. Here I just need to change the line itself though, so presumably there are better/quicker ways of achieving that.

BTW, on the subject of fixed-function v shader-based OpenGL, is it ever a good idea to actually use FF functions to accomplish things even if you're generally using shaders? i.e. Can it sometimes be more convenient and not really worth the hassle of shaders etc. when you just want to do something quite limited, or should FF always be avoided nowadays?

EDIT: Also thinking about it further, I could clearly achieve the desired effect if I was able to use OpenGL to render a 1px-thick line between 2 points and then simply find a way to scale up the pixels by a factor of X and only keep the first 1/X of the line. Does that sound practical? How might you scale up a line like that? Or am I way off in my thinking?

You could render a 1-pixel thick line to a texture and then apply it to a quad of any size. I've never done render to texture, so I cannot help you there, but I've seen several tutorials online.

You could render a 1-pixel thick line to a texture and then apply it to a quad of any size.

Hmmm... yes that seems like it would make sense. Haven't used render to texture myself either, but it sounds like the kind of thing that would work.

Might be a bit fiddly, but presumably it would work out in the end.

So unless anyone else has any better ideas, which I would be interested in hearing, I'll probably go with that.

Thanks all.

If you're set on drawing the line gradually, I'd do the same thing with RTT, but linear interpolate the position between endpoints and draw quads centered at each point. You'll get some overdraw, but it will be minor since you'll be drawing so little anyway and you'll be able to control the thickness of the line at the same time.

If you're set on drawing the line gradually, I'd do the same thing with RTT, but linear interpolate the position between endpoints and draw quads centered at each point.

Just to clarify because I'm not 100% sure I understand fully... are you saying to use 2 quads, each centred at an endpoint, one representing the filled/complete section of the line and the other representing the dashed/incomplete section?

No, it sounded to me like you want to draw the line gradually over time. I might be misunderstanding. If this is what you mean, what I am saying is to linearly interpolate the line between the two endpoints at each time step. So, the first time step you'd draw the first end point. The next time step, you'd draw the first interpolated point between the two end points, etc. Each time you draw, don't draw a single pixel (unless that is what you want), but a quad of the thickness you desire.

on the subject of fixed-function v shader-based OpenGL, is it ever a good idea to actually use FF functions to accomplish things even if you're generally using shaders?

Never.

As for your original question:
The way lines are typically animated as being “drawn in real time” is to have an image with the whole line-art fully completed and then to add slowly decreasing alpha values along the line from start-to-finish. Then you draw the whole image while decreasing the alpha-testing value (which is a discard in a shader), causing more and more of the line to appear over time.


This can be ported from using an image to using a line primitive easily.
Pass to the shader the start point, the end point, and an interpolation value.
For each pixel your shader renders along the line, it is easy to determine where that pixel is between the start and end points, and based on the interpolation value you either discard the pixel or not.

For example:
Start: [0,0]
End: [50,50]
Interpolation (from 0 to 1): 0.5

In your pixel shader you determine how far from Start to End you are (Cur being the coordinate of the current pixel): (Cur - Start) / (End - Start).
If this value is greater than Interpolation, discard.
if ( (Cur - Start) / (End - Start) > Interpolation ) { discard; }
return LineColor;
Now all you have to do is slowly increase Interpolation from 0 to 1 during the time you want the line to appear.
Once this is in place you can add fancier effects if you like, such as the stipple pattern or a smooth fade rather than a sharp cut-off.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

This topic is closed to new replies.

Advertisement