godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.08k stars 69 forks source link

Implement tessellation as an alternative to parallax occlusion mapping #5995

Open Arnklit opened 1 year ago

Arnklit commented 1 year ago

Describe the project you are working on

Various 3D projects, including the Waterways add-on

Describe the problem or limitation you are having in your project

It's difficult or impossible to manage high density details in Godot at the moment. Especially with non-static meshes such as water, hair, deforming snow.

While POM can help with adding fine detail it has a lot of limitations:

While Godot 4 can handle a lot more geometry than Godot 3.x, I don't think it's feasible to add all this detail into the meshes for most users. Even if Godot eventually gets some kind of virtualised geometry solution similar to "Nanite", it is often much easier to add details with tiling textures than using dense meshes, both from an asset creation point of view and dealing with the asset import / storage.

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

An option to enable dynamic tessellation on a material or mesh.

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

I am aware that geometry shaders are getting deprecated, so this would have to be a custom compute shader implementation, but I do not know the details on how this would be done.

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

This would be used often and cannot be done easily with a few lines of script.

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

This has to be done in core.

Arnklit commented 1 year ago

Note I am by no means a graphics programmer, but researching this a bit more, it seems it is possible to implement into the core of Godot 4.0's Vulkan renderer without geometry shaders by implementing tessellation stages.

https://registry.khronos.org/vulkan/specs/1.3-khr-extensions/html/chap22.html

Calinou commented 1 year ago

In a way similar to geometry shaders, tesselation has lost a lot of traction over the years. These days, it's more common to use actual geometry to represent bumps in surfaces where it matters (plus a LOD system or virtualized geometry). Modern GPUs can handle a lot of triangles as long as they're not smaller than a pixel (which causes those triangles to be rasterized through a slow path). On the other hand, a lot of GPUs[^1] implement tesselation in a slow manner, making the added implementation complexity not worth it in most cases.

[^1]: Anything that's not NVIDIA :slightly_smiling_face: – This is one of the reasons why many AAA titles in the early 2010s used excessive amounts of tesselation.

  • POM doesn't cast shadows from the added displacement (this might be solved).

See https://github.com/godotengine/godot-proposals/issues/1942. I've tested https://github.com/godotengine/godot/pull/65307 and it doesn't resolve the issue with POM shadowing, but that could be done with further changes.

  • POM doesn't work with tri-planar texture mapping, limiting it's use for environments (this might be solved).

This can be implemented in a shader, but it's very expensive due to the high number of texture fetches required. To my knowledge, using tesselation won't resolve this issue, as you still need to perform those texture reads to modify the underlying geometry.

Arnklit commented 1 year ago

@Calinou do you have any details on the performance thing with NVidia? Wouldn't that be when using geometry shaders?

There are modern examples of tessellation made for consoles only, which are AMD chips. Like the Demon's Souls remaster. https://youtu.be/kAv-ajC1bWg?t=365

ja2142 commented 1 year ago

Tessellation being slow on non-nvidia GPUs is a valid concern, but I think there are cases in which this is the most sane approach: one of them is having a large geometry with fine details in which both close-up and far away elements may be visible at the same time (prime examples of this would be terrain, large bodies of water etc.). In those cases other methods I know either don't work or are rather hacky:

So unless tessellation performance makes it unusable, I think it'd be better then all of the solutions above in cases like terrain and water.

@myaaaaaaaaa you propose to run tessellate() before vertex(). In both vulkan and opengl tessallation seems to be a step after vertex shader. So the pipeline probably has to be done more like in those apis:

vertex -> tessellation control -> tessallation evaluation -> fragment

Tessellation evaluation coule be named something like interpolate()?

There's a quite nice sample of tessellation shaders in https://github.com/KhronosGroup/Vulkan-Samples in case someone tries to implement this: https://github.com/KhronosGroup/Vulkan-Samples/blob/master/samples/api/terrain_tessellation/terrain_tessellation.cpp https://github.com/KhronosGroup/Vulkan-Samples/blob/master/shaders/terrain_tessellation/terrain.tesc https://github.com/KhronosGroup/Vulkan-Samples/blob/master/shaders/terrain_tessellation/terrain.tese

PeterSHollander commented 1 year ago

I would like to add support for seeing tessellation implemented in Godot 🌐

In my use case, it would be a major fidelity boost for curved model silhouettes in XR content (see PN-Triangles for reference), where it is not predictable how close the camera will ever be to any object, and where tessellation would save an otherwise unreasonable amount of LOD file size to achieve a similar result. Additionally, as suggested here, using realtime tessellation for a large amount of models would save significantly on memory usage. A use case of tessellation shaders for skewed viewing angles of large meshes such as terrain can be found in #6279 (comment) and #445 (comment). Silhouette tessellation may also relate to the implementation of OpenSubdiv proposed in #784, but as it operates realtime on the CPU it is my understanding that GPU realtime tessellation would be more performant (and potentially more flexible for non-uniform tessellation).

I would also like to ask for clarification if there is a possibility to achieve tessellation-like behaviour using compute shaders, as suggested by @fire in #2075 (comment), and adjacently suggested in this comment?

It appears that @painfulexistence is experimenting with adding tessellation, as mentioned in #6121 (comment). While I am not graphics engine programmer, I am willing to learn if it can help contribute the feature of tessellation to core Godot.

fire commented 1 year ago

https://github.com/v-sekai/godot-subdiv needs a implementation of vulkan compute shaders.

See also https://github.com/PixarAnimationStudios/OpenSubdiv/issues/1153

Friendly note. The currently implementation of opensubdiv requires a gdscript plugin. I wasn't able to find effort to port it to C++. It should be in c++ though.

Edit:

Ported to godot engine c++ modules.

https://github.com/V-Sekai/godot/tree/vsk-subdiv-4-0

Igameart commented 9 months ago

Just leaving my support for tessellation. It would be mostly beneficial, in my opinion, on terrain systems similar to how it's used in Unreal engines. I would love to see that implementation in Godot.

erickweil commented 9 months ago

I am aware that geometry shaders are getting deprecated, so this would have to be a custom compute shader implementation, but I do not know the details on how this would be done.

Yes, It could be a compute shader implementation but the current way they work requires a sync read back to CPU which invalidates their usage for anything happening each frame.

My scenario I'm trying to generate triangles in the fly and render then each frame (It's for showing the 3d slice of 4d geometry made of tetrahedrons, but the exact use-case is not relevant). At least in theory should be possible using compute shaders to produce geometry by storing the result in buffers (generated triangle Indices and Vertex Data) and then rendering it without creating a Mesh.

I managed to get the compute shader part working, but no hopes in making the result go to the normal render pipeline like a normal Mesh would go, without actually reading the data back to CPU and creating the ArrayMesh. This basically turns compute shaders not useful for this kinds of tasks since the CPU syncs eats up any performance gains that we would otherwise gain.

In conclusion... If compute shaders were allowed to

Then they would be game-changing useful and allow things that truly harness the power of GPU's, like implementing compute shader based tesselation and virtual geometry.

gamma-delta commented 2 weeks ago

Hi Erick, I am LITERALLY doing exactly the same thing right now, and I've been chasing the problem for several days without any luck. Like you, I can get vertices onto the GPU in a compute shader, but I can't figure out how to get it to actually draw it without going through the CPU. If you figure it out please tell me!

erickweil commented 2 weeks ago

Hi Erick, I am LITERALLY doing exactly the same thing right now, and I've been chasing the problem for several days without any luck. Like you, I can get vertices onto the GPU in a compute shader, but I can't figure out how to get it to actually draw it without going through the CPU. If you figure it out please tell me!

Well, the 'solution' I ended up isn't ideal, I figured out how to bind textures generated by the compute shader into a normar custom shader material, then my custom shader does texel fetches on the texture to get the vertex data. The only problem is the memory overhead because for it to work I needed a dummy vertex array and indices so that the vertex counts worked correctly, otherwise the shader wouldn't know how many vertices and what triangles to draw, since there is no way to do a drawProceduralIndirect like Unity does, obtaining indices info from buffers.

So my half baked solution leaves a lot of memory unused since I need to allocate the maximum size I could get in the generating phase and in the shader drop the vertices that overflow the actually generated count. Also idk but I think reading from textures aren't as efficient as from structured buffers (if they were supported)

You can see it in my github: https://github.com/erickweil/GodotTests

gamma-delta commented 2 weeks ago

Cool!

I was recommended by a friend that it might be possible to use an atomic counter to "append" stuff to the buffer. IIRC, each tetrahedron can get sliced into... 0, 16, 24, or 32 triangles? I don't remember. Point being, it might be able to save some space by using an atomic counter and banking on how every tetrahedron probably isn't going to require the maximum number of output triangles. If one tetrahedron outputs 0 triangles, that leaves space for another tetrahedron to output many triangles.

EDIT: oh look at that you're already using it. sick

fire commented 2 weeks ago

See also https://github.com/AIFanatic/three-nanite.

An attempt at reproducing a dynamic LOD in threejs similarly to unreal's nanite. Very far from it but nonetheless a start. For now it clusters a mesh (meshlets), then groups adjacent clusters into a group, merges the mesh (shared vertices), performs mesh simplification to half the triangles in the mesh (max 128) and finally it splits it into 2 (should be N/2).

Like for example you take a mesh, use a subdivision library like https://github.com/tefusion/godot-subdiv and present tesselation backwards as lod.

fire commented 2 weeks ago

@erickweil Maybe you write a proposal on adding to the Godot Engine api to obtain indices info from buffers for the dummy vertex array and indices problem?

gamma-delta commented 1 week ago

https://github.com/gamma-delta/HelloComputeGeometry

Here's my implementation of Erick's idea, if someone wants another perspective