Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement real-time vector graphics rendering (e.g. SVG, Rive) #2924

Open
projectitis opened this issue Jun 27, 2021 · 47 comments · May be fixed by godotengine/godot#75278
Open

Implement real-time vector graphics rendering (e.g. SVG, Rive) #2924

projectitis opened this issue Jun 27, 2021 · 47 comments · May be fixed by godotengine/godot#75278

Comments

@projectitis
Copy link

projectitis commented Jun 27, 2021

Describe the project you are working on

Game development using vector art work. I'm a long-time (former) Flash/AIR/ActionScript3 developer, almost exclusively using vector art assets.

Describe the problem or limitation you are having in your project

Although vector art can be imported into Godot, it is rasterized on import and is a bitmap from that point and is not rendered in real time. Real-time vector art rendering has several benefits over bitmap art, which include:

  • Scalable (and rotatable, transformable etc) without quality loss
  • Much smaller file size
  • Transformable in real time, and transformations are not destructive (e.g. can be reversed)
  • Many vector asset types can be managed in source control as they are essentially text based

Yes, there are negatives too - the main one being much slower to render than bitmaps, and lack of GPU acceleration. However, many game developers are willing to make this trade-off for the benefits that vector art can provide,

Describe the feature / enhancement and how it helps to overcome the problem or limitation

I propose a new Node2D descendent which supports real-time vector rendering using a TBD vector renderer.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

The mechanism, if this is the way recommended by the core devs, would be basically to use the custom drawing feature of CanvasItem to update the node (draw a new texture) if the vector updates. Ideally a vector graphic asset would be assigned to the node.

If this enhancement will not be used often, can it be worked around with a few lines of script?

The work-around is not to use vector art, which is really not a workaround :)

Is there a reason why this should be core and not an add-on in the asset library?

If available, it should be a core feature and maintained as part of the product.

@YuriSizov
Copy link
Contributor

However, many game developers are willing to make this trade-off for the benefits that vector art can provide,

Well, if you're making this claim it would be nice to know where you're getting this metric from. 👀

If this to be implemented, you have to make sure that users aren't confused why their vector assets suddenly cause way worse performance.

@Calinou Calinou changed the title Real-time vector art rendering (e.g. SVG) Implement real-time vector art rendering (e.g. SVG) Jun 27, 2021
@Calinou
Copy link
Member

Calinou commented Jun 27, 2021

godotengine/godot#44772 implements this for SVGs using the same rendering technology as MSDF fonts. Since it was implemented as a proof of concept, it's limited to monochrome shapes and its usability isn't very good right now. That said, it can surely be made more convenient to use by creating a dedicated node.

Note that adding new dependencies to Godot is serious business, so Blend2D may not be accepted in core. This is especially the case if Blend2D turns out to be difficult to build or increases binary size significantly.

@fire
Copy link
Member

fire commented Jun 28, 2021

My approach for doing real-time vector art rendering is step by step via godotengine/godot#49645.

Stage 1: Implement thorvg. Notice the render commands in the documentation.
Stage 2: Evaluate either rive https://github.com/rive-app/rive-tizen or a future port of https://github.com/Samsung/rlottie vector animations from the same Samsung team as thorvg. See thorvg's roadmap https://github.com/Samsung/thorvg/wiki/21'-Development-Milestone
Stage 3: Implement a vulkan thorvg backend. See opengl thorvg backend.

There is a lot of concern about library size, so I will need to get consensus on that.

Edited:

Blend2D is not planning to implement SVG importing. blend2d/blend2d#107 (comment)

See also:

Please see previous prototypes. Both "vector graphics" and "lottie" in Godot Engine has trialed two modes. 1. is a baked mode and the second is a converted to curves mode. There is performance difficulty via the runtime route, but need help to revisit via the Vulkan Thorvg backend option

@projectitis projectitis changed the title Implement real-time vector art rendering (e.g. SVG) Implement real-time vector graphics rendering (e.g. SVG) Jun 28, 2021
@projectitis
Copy link
Author

Thanks for the comments and considerations everyone.
@fire - those prototypes and other (animated) SVG projects are very interesting, especially Rive.

@projectitis
Copy link
Author

projectitis commented Jul 31, 2021

I finally have a working c++ version playing Rive files using Skia as the rendering engine. Not in Godot, just a standalone app at present.

Rive is a vector format for animation (much more than just animation, actually) which would be an amazing addition to Godot.

I hope that @fire or someone else is able to implement a thorvg Rive renderer in time! (see skia renderer for reference).

@projectitis projectitis changed the title Implement real-time vector graphics rendering (e.g. SVG) Implement real-time vector graphics rendering (e.g. SVG, Rive) Jul 31, 2021
@filsd
Copy link

filsd commented Oct 20, 2021

I also would like to see this in Godot.
We are working on a Card game and this would be very useful in some places.

Having said that we are using a very nice work around:
Create a custom Font (using Font Forge) with only the vector icons used on the game. These SVG icons are imported in unused (by the other fonts in the game) UNICODE characters:
unicode_svg

With the Custom font ready, we just add it as one of the "Fallbacks" of the font theme:
fallback_godot_font

Every time you insert an unused unicode, the fallback triggers and the ICON is used. :D

@TackerTacker
Copy link

Here is a Tweet I saw today, just to show that this is indeed a useful feature that many developers are looking for and can't currently find in any game engine.
https://twitter.com/TylerGlaiel/status/1494383530201141252
You find a lot of indie devs in the comments or quote retweeting it, like Rami for example
https://twitter.com/tha_rami/status/1494415211952066560

@fire
Copy link
Member

fire commented Feb 18, 2022

godotengine/godot#49645 is currently merged into master for ThorVG vector graphics support.

Currently the svg module is using the standard importer design, but there's no real reason to be only offline importable.

Here's an early preview on SVG to Mesh. Not expecting to finish SVG to mesh before Godot 4.0 release though.

image

image

@sosasees
Copy link

sosasees commented Apr 3, 2022

Density-independent graphics are Really useful as we're already on the way of having every screen be a HiDPI screen.

Every mobile phone already has a very dense screen with at least 144dpi, and even many of the mid-range computer monitors are already denser than 72dpi even if they don't quite reach true HiDPI yet (e.g. my 1920×1080px monitor is 80.68dpi, which is rounded up to 96dpi by Godot's OS.get_screen_dpi() method or by my computer setup).

My current workaround as of Godot 3.4.4 is to use Distance Field shaders.
Even with the new vector graphics rendering, i might continue to use Distance Field Shaders in the future because it's faster than real vector graphics, but it will no longer be necessary in every single project: I could start out using Vector Images and only switch to Distance Fields once rendering vector graphics actually slows down the game too much.

@fire
Copy link
Member

fire commented Apr 3, 2022

These are currently the three ways to use vector graphics in Godot Engine 4.

  1. Raster vector graphics to a bitmap through thorvg importing.
  2. Save the vector graphics to a mesh https://github.com/V-Sekai/godot-svg-mesh using Tove2D.
  3. Render a font as a (multichannel) signed distance field.

Missing:

  1. Render 2d vectors graphics as a (multichannel) signed distance field and use in Godot Engine 2D.
  2. Save a 2d vector graphic to a mesh using Thorvg instead of Tove2D. https://github.com/V-Sekai/godot-svg-mesh
  3. Rendering lottie vector animations as a Godot Engine video.

@Giwayume
Copy link

Giwayume commented Dec 19, 2022

https://github.com/Giwayume/godot-svg

Here is a proof of concept demonstrating that bezier curves can be drawn in shader code, using a SVG to mesh conversion approach. Curves are not tessellated as line segments, they are perfect at every resolution.

Comparing to fire's example above, the circles in the eyes of the Godot logo in this technique use 12 tris each, instead of like 30. And they look perfect no matter how closely you look at the mesh.

@sosasees
Copy link

@Giwayume i can't check your godot project right now, but this sounds great

in past projects i have recreated some vector shapes as shaders to get infinite smoothness, because your proof-of-concept'd feature isn't in godot.
(i have only made circle and squircle as shaders, because the other shapes i needed so far were just polygons, that i could create more easily but still lossless with Polygon2D nodes)

if this feature gets added, i can get these smooth shapes without shader code but from just importing my SVG files

@Giwayume
Copy link

This project takes a generalized approach. The technique can theoretically render any SVG shape, current bugs aside.

@SlugFiller
Copy link

A year ago, I made a realtime vector+SVG importer plugin for Godot 3. It's not complete, but it's never going to be complete, so I just posted it here. It supports shapes, strokes (via stroke to fill conversion), winding rules, and basic gradients. I was also planning to implement mesh gradients, but never got around to it.

Underneath, it works purely as a shader. It makes an approximation of the vector's bounding box, draws a rectangular quad around it, and uses a tracing algorithm in the fragment shader. I did hit quite a few difficulties:

  1. Functions in a shader are not really functions. I couldn't use regular recursion, so I had to use a virtual stack instead.
  2. In fact, I couldn't use functions at all, since I can't pass the virtual stack into a function, nor have two functions that do not call each other share the same virtual stack. The result is that the entire shader is one large function, with a lot of code repetition. This is a flaw in the shader language. If functions operate fundamentally as macros, it would have been better to have actual macros, so they can inherit stack variables from the parent. Alternately, just allow global variables, since all stack variables are technically implemented as global.
  3. Lack of uniform arrays made it difficult to pass data to the shader. Using a texture has limitations, since floating point values are clamped to 0.0-1.0 range, so I can't pass arbitrary curve coordinates. I had to create my own floating point encoding that projects the real plane into the 0.0-1.0 range. Obviously, this carries an accuracy cost, but nothing visible for most use cases. Addition of uniform arrays in Godot 4 could fix this.
  4. When a path is made of many segments, doing an iteration over all segments for every pixel was too slow, and Godot would occasionally crash. I alleviated the problem by creating a binary bounding box tree, which allows very quickly eliminating large portions of the shape, as well as getting a quick decisions on pixels that are not inside the bounding box of any segment. It can still slightly choke on complex shapes. If I could do multiple passes, I could first draw individual paths into a crossing buffer, then do scanline rendering using that buffer as input. But Viewports aren't able to act as integer buffers, nor do they allow accumulating data. And unfortunately, GDScript is too slow to do this in a CPU buffer, and also, isn't able to run every frame, and makes it difficult to get the current viewport matrix. In general, the whole thing could be much faster, if I could first do a "per y" calculation, and then use the results in a "per x" fragment. Maybe it's possible with computer shaders, not sure.
  5. Dealing with viewport matrices in general was also difficult. I had to use varying to pass things like WORLD_MATRIX from the vertex shader to the fragment shader. Doing a projection of the vector in the fragment shader is a lot easier than trying to reverse project the FRAGCOORD, which also has its own issues.
  6. I wanted to added feather measured in screen coordinates, rather than object coordinates, since that would allow me to get easy anti-aliasing by doing 0.5 pixels feather. However, it's not possible to do calculates based on screen coordinates in GDScript. What that meant was that I had to calculate the drawing bbox in the vertex shader. I simply passed a "sufficiently large" rectangle (so it wouldn't be culled for being out of screen) as a drawing command, the shape's bounding box in its local coordinates using uniforms, then used UV to figure out which point I am converting to turn the passed rect into something that actually wraps the shape and its pixel-sized feather.
  7. A lack of a native dirty/flush mechanism in Godot, made it hard to get the vector objects to work with something like animation onion skinning while remaining efficient. For example, I use GDScript to do stroke to fill, but I don't want it to run for every point that is moved in the vector in a tween animation. I ultimately made a hack using the fact that a Spatial has special handling for its NOTIFICATION_TRANSFORM_CHANGED that acts like a dirty/flush. I made a request to have a non-hacky option in Add generic dirty-flush mechanism for nodes #2598.
  8. Godot 3 has no clip shapes, which are a core SVG feature. Godot 4, thankfully, fixes this with Clip Children: Clip only.
  9. Unrelated to the vector rendering itself, as I was making an editor for the vector shapes, I noticed a large gap between methods available on the C++ side, and methods available on the GDScript side. I opened a couple of suggestions where I hit roadblocks. And yes, I have an editor for the shapes, because I believe reliance on imports ignores the gap between what a graphics format that can be pre-processed for render should be capable of doing, and what a realtime interactive engine should be capable of doing.

Incidentally, vectors can actually give an advantage over extremely high resolution sprites, due to taking up less memory, especially on mobile platform. But I did test this using a browser engine with native vector support, not using Godot.

Overall, new features in Godot 4 might make it easier to implement an efficient vector shape renderer. But a native vector shape object, even if it's only shape fill (with winding rule support), that runs in the render pipeline, can save a significant amount of effort. Implementing gradients, stroker, shape tweens, etc, on top of that, is much easier.

So, IMO, import formats should be left to plugins, but a basic bezier curve shape fill with infinite smoothness and builtin editor should still be implemented in-engine. Preferably with quadric and arc support (although the prior can be accurately represented using cubic)

@Giwayume
Copy link

Giwayume commented Jan 9, 2023

It's cool to see calculating the fill rule per-pixel in a shader is feasible for simple shapes, though the performance looks very poor.

On my laptop:
Max FPS: 600
SVG circle with fill: 180 FPS
SVG circle with stroke: 95 FPS
SVG circle with fill and stroke: 70 FPS

EDIT: Also, the FPS absolutely tanks to single digits the more I zoom in. If you split the quad the shader is drawn across into a grid of quads, could take advantage of triangle culling.

I actually can't get many SVGs to render in order to test more complex examples. I'd love to see improvements made to that project as theoretically it could be the best technique for rendering SVG animations that morph the path every frame.

@sosasees
Copy link

sosasees commented Jan 9, 2023

the SVG format was made for websites, not games

SVG has many advanced features that help with making more detailed pictures at the cost of speed.
a 3D analogy: SVG is more similar to Blender files than glTF

you can make lite vector graphics with Godot features like Polygon2D and shaders and the draw_*() functions.
if SVG rendering can be made fast, it's great — but not as needed as some people might think

@Giwayume
Copy link

Giwayume commented Jan 9, 2023

I think to SlugFilter's point, what people mostly want out of the engine is a way to render shapes with smooth bezier curves, that can be colored, textured, or used with a shader.

What I have presented is a concept where that shape is converted to a triangulated mesh, where each bezier command gets dedicated triangles and the calculation to draw the bezier curves in the shader is simple linear algebra. This is very performant at runtime, but requires some work on the CPU beforehand to generate the mesh from the SVG path commands.

The approach SlugFilter shows could potentially be more performant for animating the d attribute of a path (animating transitions between curves), because the work on the CPU to convert a new path command to a texture that basically stores the path data is much less than all of the path solving work my project does to convert everything to a new mesh. Though, the mesh conversion approach I have shown is basically more performant for every single other scenario, and by a large margin.

I could envision Godot natively supporting a node that uses both techniques in an exchangeable manner, either automatically if it's feasible to detect that the developer is trying to animate the path commands, or through a boolean property on the node.

@SlugFiller
Copy link

@sosasees The issue with both Polygon2D and draw_*() is that both only option for straight segments. (Well, there's a draw_arc, but it only draws a stroked arc, so you can't compose a filled shape from multiple arcs).

While a complex format like SVG isn't built for speed, something like CanvasRenderingContext2D::bezierCurveTo is built for performance, and is used to effect in HTML5 2D games.

Even something as tiny as adding a draw_filled_curve method to CanvasItem (and a canvas_item_add_filled_curve to VisualServer, obviously) would allow filling in the rest using plugins and shaders.

In fact, I might prefer this minimal approach, because, despite the extra effort, I'm more likely to do well making and using my own curve editor, then rely on one designed by someone else.

@Giwayume The reason it's slow is because the fragment shader recalculates a lot of things a normal rasterizer would only need to calculate once. For example, if a bunch of path segments are completely above or below the current y, each fragment in the same y would need to make the same calculation to figure this out, even though it's the exact same calculation for all of them (I do winding counting by tracing a ray in the screen's x+ direction, so checks for the y value are largely uniform). Even though they do this in parallel, the GPU's number of cores is not infinite, and it takes resources that could be otherwise used for parallelizing for different y values.

As I've said above, if I had a "row" shader, that allowed me to make a bunch of calculations for a given y value, and then pass the results as an array to the fragment shader, it could vastly improve performance. This is how software rasterizers can fill curves very quickly. Hybrid rasterizers can do extract horizontal slices in CPU, then let the GPU simply draw horizontal line commands.

An alternative approach is to draw winding additively on a buffer, then rasterize each row left to right, summing up the winding as you go. The fragment shader's inability to run in any sort of sequence works against it, again, but this could be alleviated by storing the winding in a tree format, that would make it possible to look up the winding for a given fragment at log(width) complexity. The initial winding rasterizer can still be fairly performant if done in software. But not if that software is written in GDScript. Additionally, it's impossible to patch the render pipeline in Godot directly, so just figuring out the height of the screen, and the needed resolution for rasterization, and running it on every frame, would be impossible from GDScript.

Put more simply, my approach is NOT the best approach, it is the best approach given the limitations of working inside Godot 3. Browsers and programs like Inkscape, demonstrate rasterizing far more complex vectors, at significantly higher speed, due to using more effective rasterization methods that can't be implemented in pure GDScript or simple vertex-fragment shaders.

As for subdividing the shader rect, triangle culling wouldn't help with performance, because the fragment shader doesn't run for out-of-screen pixels anyway. If anything, it would just create extra effort for the GPU, as fragments near the seams have to run twice. The reason performance dies as you zoom in is simply because the shader is ran for more fragments.

It would be pretty easy to test in practice, though. Replace the draw_rect in fill.gd with a draw_mesh. The locations of the vertices in the source mesh don't really matter, only the UVs, since the vertex shader uses only the UVs to produce the actual vertex positions.

@sosasees
Copy link

sosasees commented Jan 9, 2023

i like to make my game vector graphics as Godot shaders, mainly because i can add motion effects that i can't with SVG

i understand that many people don't want to write shaders, so i respect the effort of adding realtime SVG rendering to Godot — different from Godot's current SVG support, which justs imports SVG files as raster images (similar to 'Export PNG' in Inkscape)

@SlugFiller
Copy link

@sosasees The issue is that shaders alone, in their current specs/limitations, are insufficient to implement generic and efficient bezier shape rendering. You can see that both my approach, and @Giwayume's approach, completely buckle under certain conditions. This is due to limitations of what the Godot shader specs expose, and how slow GDScript is for large iterations or recursion.

It's possible that Godot 4 exposes better APIs for making a more efficient shader. I haven't tested. It still couldn't possibly be as efficient as an engine-supported command.

Incidentally, if you only want infinitely smooth corners, and don't need beziers specifically, you could make a shader that turns a triangle into a filled arc, then make a smooth shape out of those. But it would be impossible to work with artists who expect standard tools, and very difficult to make any line effects and animations.

@Giwayume
Copy link

Giwayume commented Jan 9, 2023

The reason performance dies as you zoom in is simply because the shader is ran for more fragments.

This is not what I'm seeing, because the entire shape is already taking up the entire size of the game window, running at about 50fps, then as I zoom in more it dips into the single digits.

There might be something Godot is doing under the hood that you're not expecting.

@SlugFiller
Copy link

@Giwayume Are you zooming near the center, or the edge? From what I saw, the shape you're using is a circle, which is implemented as 4 arcs (the editor shows 2, but the engine pre-divides arcs to 90 degrees, since they are easier to work with mathematically). So a possible culprit is that the number of subdivisions necessary before a given fragment is determined to be outside the arc's approximate bounding box increases as you zoom. From the get go, the arc's bounding box approximation isn't as good as for beziers. And a good approximation is necessary for early-stopping on the iterations.

@voidshine
Copy link

Yep, native vector graphics support for Godot could bring on Flash-like popularity for the engine, as vector unlocks many new kinds of game and application possibilities. How many times do we need to see grainy raster graphics before we accept that resolutions & devices will vary widely? The future is scalable. The present is, too. Even if the implementation just rasterized to the right size texture at runtime, that'd be good enough for many use cases.

@TechnoPorg
Copy link

How does this proposal tie in to the use of SVGs as Texture2Ds, as compared to the current handling strategy, which is just to rasterize them on import? It would be nice to have adjustable vector graphics textures for things like shader parameters, theme icons, etc.

@Calinou
Copy link
Member

Calinou commented May 12, 2023

How does this proposal tie in to the use of SVGs as Texture2Ds, as compared to the current handling strategy, which is just to rasterize them on import? It would be nice to have adjustable vector graphics textures for things like shader parameters, theme icons, etc.

This proposal is about rendering SVGs as polygons or SDFs, while your proposal is different entirely. Each approach has upsides and downsides:

  • Polygons/SDFs can be scaled at run-time with no quality loss. A generated texture is rendered once, and while it can be downscaled OK if it has mipmaps, it will not be as sharp as a polygon.
  • Polygons/SDFs are more demanding to render than a generated texture, especially if antialiasing is required.
  • Polygons/SDFs can only be used in 2D, not 3D unless the engine has a specific implementation for that. In contrast, generated textures can be used on any 2D or 3D surface.

@SlugFiller
Copy link

@TechnoPorg Textures are necessarily raster. Any attempt to use SVG as a texture will therefore necessarily set it to a specific resolution. This can already be done directly today, by importing the SVG as a raster image. However, if you want the resolution to change in runtime, it is possible to do this by combining this suggestion with a viewport. i.e. rendering the vector to a viewport, and then using the viewport's texture as the texture. Since you still need to manually adjust the viewport's resolution, it is questionable how much of an advantage this gives.

It is impossible for an SVG to act as a "true" texture at infinite resolution. At most, it can be done as a shader. But that would make it difficult to combine with other shaders, and also, as demonstrated in the above shader-based vector graphics implementation, is incredibly slow, putting way more stress on the GPU than can be managed in a game that must run in real time.

@fire
Copy link
Member

fire commented May 13, 2023

One of the thorvg team is employed at https://lottiefiles.com/ which takes After Effect animated motion artwork and renders it.

So both lottie (standard) and rive (proprietary) are approaches to render.

A contributor to godot engine is also working on shader based rasterization of vector artwork.

See godotengine/godot#75278

So there are a few approaches to rendering animated vector graphics without going through the raster movie route.

@TechnoPorg
Copy link

@SlugFiller Those are excellent points that I had not considered, and I like the idea of the viewport solution.
What I had originally been thinking of was some kind of VectorTexture type that renders the vector image, to be used as the texture. It would then cache that until its transform or animate property is changed, perhaps by script, or by some special VectorSprite node, which forwards its transform changes to its texture. At that point the texture re-rasterizes the vector image at the highest possible display resolution and displays the new texture.
That concept probably has all kinds of flaws, so please forgive my ignorance. If it could be made to work, though, I think something like a VectorSprite could be faster than calling a draw_filled_curve call every frame.

@SlugFiller
Copy link

@TechnoPorg If the vector doesn't change, you don't need to call draw_filled_curve every frame. Only every change. If it does change, it would have to be rasterized every frame, which would be just as slow. Also, a viewport set to UPDATE_ONCE would achieve the same effect of pre-rasterizing. It would only draw once, and can be used as a texture until it needs to be updated, at which point it can be set to UPDATE_ONCE again. Adding a special node or resource to achieve that wouldn't serve any purpose. It would be no more efficient, and the amount of things that need to be handled manually (e.g. deciding the resolution of the raster) wouldn't be reduced even by a bit. Nor would there be any performance boost to it (Whichever rasterizer is used, it's not going to beat an in-engine one. If it could, the same rasterization technique could be used by the engine. At the very least, a software rasterizer is not going to beat a hardware-aided one at anything other than trivial resolutions).

@SlugFiller
Copy link

SlugFiller commented May 15, 2023

@fire Now that I'm done working on this, I took the time to see how Tove2D implements its path to mesh. It's... "interesting". It uses a library called PolyPartition, and performs a few steps:

  1. It "removes holes" by adding edges connecting "holes" (counter-clockwise polygons) with the main "shape" (clockwise polygon), creating a single connected shape that has 0-width "tunnels" connecting any holes to the "outside".
  2. It uses ear-clipping to triangulate the shape. Ear clipping is known to not work if the shape has any holes or self-intersections. Holes were removed in the previous step, but intersecting edges could still make the algorithm fail at this point.
  3. It uses Hertel-Mehlhorn to reduce the triangulation to convex polygons. This works by "merging" adjacent triangles into polygons while ensuring they remain convex.
  4. It iterates over the convex polygons, and triangulates them by ear-clipping. You may be wondering what's the point of doing this after doing the previous step. There is no point.

All in all, the algorithm is horribly inefficient, and can fail on intersecting edges, e.g. in a simple pentagram. Worse yet, PolyPartition actually contains a different algorithm called "Triangulate_MONO" which is basically Bently-Ottmann, but without intersection checking. It would still fail on self-intersection, but would run at a fraction of the time.

Incidentally, Godot already contains an ear-clipping triangulator, used by Polygon2D, so including a third party library for it would have been redundant.

@Giwayume
Copy link

I've found that a constrained delaunay implementation works fairly well for generating a triangulation with holes. The idea being, run the fill rule in the center of each triangle generated to find the holes.

@fire
Copy link
Member

fire commented May 15, 2023

I didn't look into this deeply. @SlugFiller thanks for summary.

image

Have a SVG to mesh cat :D. This was generated by the Tove2D PolyPartition workflow.

Hope to find a way to get that into Godot Engine properly.

@sosasees

This comment was marked as resolved.

@SlugFiller
Copy link

@Giwayume Delauney will not only ignore edge intersections, but the edges themselves, as it does not accept them as input. It can choose to split triangles using an edge that crosses one of the original edges, causing it to have an incorrect shape, regardless of which triangles you leave out.

@fire If my patch gets merged, you can use the methods I expose on Geometry2D to tessellate and triangulate the paths. It might be a bit more work, but performance and stability should be way better. Or you can patch Tove2D to use Triangulate_MONO instead of Triangulate_HM. The prior probably has a better chance of getting into Godot, because, less 3rd-party dependencies.

@Giwayume
Copy link

Giwayume commented May 15, 2023

@Giwayume Delauney will not only ignore edge intersections, but the edges themselves, as it does not accept them as input. It can choose to split triangles using an edge that crosses one of the original edges, causing it to have an incorrect shape, regardless of which triangles you leave out.

You need to look up what a "constrained" delaunay is, not a regular delaunay. From your description, it appears you don't understand what I'm talking about.

A constrained delaunay can be passed a list of edges (the edges that define the outside of a shape, the outside of a hole), then work around those, assuming that those edges must be attached.

I have actually implemented this, it works.

@SlugFiller
Copy link

@Giwayume Wiki says there's O(nlogn) algorithms for it, so it would be reasonably efficient. It still wouldn't handle edge intersections. I'm also not sure about the complexity of computing the winding. If it's O(n) for each triangle, that would be O(n^2) in total, which wouldn't be particularly efficient. If you have triangle adjacency stored (via the edges), then a flood-fill algorithm can probably do the whole thing in O(n), but I'm just theorizing, and haven't really thought deeply on whether it would work.

@ghost

This comment was marked as off-topic.

@sosasees
Copy link

@felaugmar

  • GDScript has the _draw() function, which should be used for custom drawing
  • you are proposing a second API for custom drawing. this would be either a breaking change if replacing the current API, or very confusing if co-existing with the current API

@ghost

This comment was marked as resolved.

@fire
Copy link
Member

fire commented Jul 17, 2023

My original design was for thorvg to first export to pixel buffers. Once things are futher along we can export static and animations as vector buffers for godot engine.

We don’t expose the thorvg api bare.

@SlugFiller
Copy link

@felaugmar The issue with that approach is that ThorVG, as it appears in Godot, uses a 100% software renderer. It's good enough for turning icons to bitmaps in the editor. It is not, however, sufficient for rendering vector animations, in real time, at any resolution expected of a modern game.

Even a meshing method, with GPU draw, is so slow that you can only afford 50-80 sprites on screen before going below 60 FPS. And that's running on an Intel i7. A 100% software scanline renderer would be even slower.

If a render-to-texture approach was to be used, something like Vello or at least Pathfinder, would be more appropriate, as it at least uses GPU acceleration to achieve 60 FPS on large scenes. They do carry other downsides, and would be a new 3rd party dependency if taken as-is.

One thing that could be a benefit in combination with a draw_ command is an SVG simplifier, like usvg. However, something like that can just as easily be implemented in GDScript and put up as an addon.

@sosasees
Copy link

since @SlugFiller's comment, it seems even more unlikely that real-time vector graphics rendering will be added to Godot.
until then, i would make my scalable game graphics from distance field shaders and/or icon fonts.

@SlugFiller
Copy link

Cross-posting from godotengine/godot#75278 (comment)

There's been a suggestion to instead of implementing this directly, to implement hooks to the rendering process which would allow implementing this as an addon/extension. To anyone following this topic: Input would be appreciated.

@QbieShay
Copy link

QbieShay commented Jul 30, 2023

To add to SlugFiller's comment, we discussed the relative PR and the general consensus from the maintainers is that this feature opens up a new usecase for Godot and we're not sure that it's possible, resource wise, to support this long-term, offer bugfixes and new features to the people that would start using Godot more like they'd use flash for making games before.
We do not want to ship features that we're not sure we can maintain.

Due to these concerns, the first step is to figure out if this can be made into a gdextension, and if not, what's missing in order to make it into a gdextension. (web support for gdextension is a goal of Godot so the current non-working state should not be ground for putting this into core)

If the extension route is not possible, we'll evaluate whether this is a use-case we can afford to support or not.

My own personal take (so take this as a one person opinion and not the rendering team's final stance) is that it's better for Godot to focus on properly supporting the feature set that it already offers and improve on that, and leave realtime meshing for vector rendering to either plugins or a specialized engine. Godot never set out to be a replacement for flash.

I know this comment will displease all of you who would like to have these features, but I think it's important to set the expectations right for everyone participating.

SlugFiller has asked for help in figuring out how to decouple the svg rendering from Godot's core rendering loop, so if anyone wants to help with that please do get in touch in rocket chat https://chat.godotengine.org/channel/rendering/

@HendrixString
Copy link

Hi @here
I am probably late to the party,
I was told that this vector engine I have written might interest you
https://github.com/micro-gl/micro-tess

@FedTheCat
Copy link

Rive just announced they are working to implement their runtime into Godot. Fingers crossed it gets in sooner rather than later!

https://twitter.com/guidorosso/status/1714737152633008500?t=Gf-8igKuoDWwUIXtEIEkHQ&s=19

@PluckyDuck99
Copy link

PluckyDuck99 commented Nov 15, 2023

Rive just announced they are working to implement their runtime into Godot. Fingers crossed it gets in sooner rather than later!

https://twitter.com/guidorosso/status/1714737152633008500?t=Gf-8igKuoDWwUIXtEIEkHQ&s=19

Here's an update on that:
#4287

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.