2D vector graphics using shaders

Started by
12 comments, last by Amadeus 13 years, 4 months ago
I'm working on a 2D vector graphics engine that will (hopefully) draw anti-aliased circles, quads, and bezier curves using pixel shaders. So vertex-wise everything is a quad. So for a circle, the pixel shader just checks to see how much of the pixel is inside the circle, for instance, and scales the color's alpha by this value. The result (hopefully) would be a nicely anti-aliased pixel-perfect circle.

I was hoping to use separate shaders for the different shapes, to try and keep the instruction cost and shader model requirements quite low. But for this to work I'll also be alpha-blending a lot.

If I do use different shaders for the different shapes, does that mean that if I have to draw, say, a circle on top of a square on top of a circle, that would require 3 separate draw calls? ie: I can't batch the circles together into a single draw call, because if I do there's no way to draw the square in between the two circles without using the depth buffer, and then I'll have jagged alpha blending artifacts.

Which would quickly amount to a potentially large number of draw calls for any non-trivial scenes, which might become a bottleneck. Which would seem to imply I need to write a monolithic pixel shader which can draw all the 2D primitives I want to support, so I can batch my 2D primitives into reasonably sized chunks.

But the code for drawing anti-aliased pixels will probably be quite chunky. So putting everything into a single shader might push up the minimum specs to shader model 3 or higher just from the instruction count, which I was hoping to avoid.

So I dunno, I'm just wondering if people have any thoughts or tips. Maybe there's some sneaky trick using different passes?
[size=2]Darwinbots - [size=2]Artificial life simulation
Advertisement
What's wrong with a large amount of draw calls?
In a 3D scene, each mesh gets its own draw call, so surely a simple shape should be no problem.
Depends how 'large' large is.

Let's take something simple like an eye:

You have a circle for the eyeball itself. You have a circle for the iris. You have a circle for the pupil. And you have a circle for the light shine. And maybe another eye shine if you want it to be extra cute looking.

That's 4-5 draw calls if each circle is a separate draw call. Just for one eye of a character. An average character might end up being constructed of a few dozen or low hundreds of primitives.

That's vs. maybe 6 draw calls for a 3D mesh based on passes and things like that. The nearest analog would be if you had to draw every triangle in your 3D scene as a separate draw call.

I expect to be fill rate limited, but if every primitive is a separate draw call I could easily end up draw call limited. So I want to nip that in the bud early on.
[size=2]Darwinbots - [size=2]Artificial life simulation
Why don't you use just one shader for everything? This way you can batch draw calls easily, pass the 'shape' type as a uniform to the shader.
Quote:Original post by Relfos
Why don't you use just one shader for everything? This way you can batch draw calls easily, pass the 'shape' type as a uniform to the shader.


Quote:Original post by Numsgil
Which would seem to imply I need to write a monolithic pixel shader which can draw all the 2D primitives I want to support, so I can batch my 2D primitives into reasonably sized chunks.

But the code for drawing anti-aliased pixels will probably be quite chunky. So putting everything into a single shader might push up the minimum specs to shader model 3 or higher just from the instruction count, which I was hoping to avoid.


So yeah, that's my current plan, I'm just wondering if there's a better way.
[size=2]Darwinbots - [size=2]Artificial life simulation
I don't see a better way, if you want alpha blending and zsorting. What about instead of using quads for everything, aproximate the shapes with triangles, this way you can still use a shader for everything, and the shader code wouldn't need to be very complex. With enough triangles, you can get a very smooth circle, and also, you can get access to all kinds of complex shapes, that would be difficult to define with just a formula.
Chapter 25 of GPU Gems 3 might be of interest to you: Rendering Vector Art on the GPU.
Denzel Morris (@drdizzy) :: Software Engineer :: SkyTech Enterprises, Inc.
"When men are most sure and arrogant they are commonly most mistaken, giving views to passion without that proper deliberation which alone can secure them from the grossest absurdities." - David Hume
Quote:Original post by Relfos
I don't see a better way, if you want alpha blending and zsorting.


Actually I only care about alpha blending. I have to sort all the quads anyway, and go back to front to prevent artifacts, so I can't use the depth buffer anyway.

I'm wondering if there's a way to approximate alpha blending that would allow you to draw quads in a more arbitrary manner? I can't use additive blending, for instance, because it makes things look glow-y, but maybe there's some other trick?

Quote:
Is there a way to What about instead of using quads for everything, aproximate the shapes with triangles, this way you can still use a shader for everything, and the shader code wouldn't need to be very complex. With enough triangles, you can get a very smooth circle, and also, you can get access to all kinds of complex shapes, that would be difficult to define with just a formula.


That's an option, but you have to dynamically tessellate everything every frame as you zoom in/out, which is a pain. If I was using SM4, I could use a geometry shader to do that, which would be pretty cool.

Quote:
Original post by Halifax2
Chapter 25 of GPU Gems 3 might be of interest to you: Rendering Vector Art on the GPU.


Thanks, I'll read over it and see how they do it.
[size=2]Darwinbots - [size=2]Artificial life simulation
The article on GPU Gems 3 doesn't solve the ordering problem. But, how much translucent overlapping objects do you want to support? Have you verified this is actually a problem? Have you considered depth peeling techniques?
I would suggest you simply render the shapes triangulated. that way you can simply benefit from alot of feature like MSAA etc. and are not limited to simple shapes . Also you could simply render everything of the same "material" in one pass. You would simply need to optimize in a different place which would be to make the engine push as many triangles as possible. Things like pseudo or real hardware instancing are your friend in a scenario like that and fairly easy to implement.
Another idea would be to approximate the shapes using distancefields. that would have the benefit that you could simply use quads, and even simple textures (holding the distancefield) to define arbitrary shapes. You would also only have one shader to draw the shapes, so you would not have the problem of too many state changes. Alpha blending is still needed, but that should not be a problem.

This topic is closed to new replies.

Advertisement