Home

Awesome

Rust Vulkan TressFX

This is an implementation of AMD's TressFX hair rendering and simulation technology using Rust and Vulkan.

https://github.com/Scthe/Rust-Vulkan-TressFX/assets/9325337/d558c2cb-8164-47f0-903c-1346d2829c71

Showcase adjusting real-time shadows using the UI

TressFX is AMD's library used for the simulation and rendering of hair. It's been used in commercial games like the newest Tomb Raider titles and Deus Ex: Mankind Divided. The library itself is open source under GPUOpen initiative. Please visit the provided links to get more details.

Previously, I've already ported TressFX to OpenGL with C++. It was mainly required to provide bindings for AMD's framework functions and translate HLSL into GLSL shader code. The app contained both the rendering and the simulation part. Later on, I created WebFX (Demo) - in browser viewer for TressFX files. Due to WebGL limitations (no compute shaders), it only contained the rendering part.

https://github.com/Scthe/Rust-Vulkan-TressFX/assets/9325337/3f99198f-425c-4eb9-94d4-65b18c593339

TressFX simulation: adjusting the wind strength

Based on this project, I've also written a series of Vulkan articles:

Usage

Requires glslc in PATH. By default, debug data is added to shaders, which requires glslangValidator. If you want to skip this last step, set ADD_DEBUG_DATA = False in compile_shaders.py.

Run make run to:

  1. Compile shaders (it just calls compile_shaders.py) to SPIR-V
  2. Build and run the main rust app (cargo run).

Use the [W, S, A, D] keys to move and [Z, SPACEBAR] to fly up or down. Click and drag to rotate the camera (be careful around the UI). All materials, effects, rendering and simulation techniques are configurable using the UI on the left side of the screen.

FAQ

Q: Which effects are implemented?

Q: Where can I find ...?

Q: Why write this project? Isn't TressFX-OpenGL and WebFX enough?

My main goal was to learn Vulkan. The API is infamous for requiring 1000 LOC to render a single triangle. One can hide tons of concepts and techniques in 1 thousand lines of code! We can also combine all the goodness of WebFX rendering techniques with compute shader based simulation. As I'm no longer simply porting AMD's framework to OpenGL, I could rewrite and simplify a lot of code.

Q: How to load new models?

Authoring new models for TressFX is quite complicated. First, there is the scale. Certain simulation steps require models to have roughly comparable scales. For example, wind displacing a hair strand that has a length of 3 units is vastly different from a hair strand 300 units long. Sintel's model is about 50x50x50 blender units.

Another thing is tweaking all simulation and rendering parameters. All the constraints and forces are hard to debug and adjust.

There are also collision capsules that are hard to get right. When hair near the root intersects with a collision capsule, it is automatically 'pushed away'. Collision resolution has the highest priority. This results in colliding part of the hair strand just ignoring any other simulation forces. One could make the capsules smaller, but that leads to penetration of the object.

Q: Where can I find TressFX Blender plugin?

Simple Blender exporter can be found in my original TressFX-OpenGL project.

Q: Sintel? How cool!

Well, I made a rule to not modify the official Sintel lite hair model. As a result, there are a few issues that can probably be noticed when watching the animations above. The model was just not prepared to handle this kind of simulation.

We can compare this to models from commercial games that utilize TressFX:

Q: Why Sintel?

If you know me, you probably know why I like Sintel so much.

FAQ Vulkan

Q: Did you use a render graph?

No, all code is a straightforward Vulkan with a few utils functions. Render graph would make the code shorter, but someone would have to write it.

Q: Is Vulkan as verbose as they say?

Yes. In WebFX my pass to write linear depth buffer was 28LOC. In Vulkan same pass is 223LOC. Around half of the Vulkan code is declarations of render targets, uniforms and pipelines. Some libraries can analyze SPIR-V to make a lot of this automatic (especially VkPipelineLayout). During the frame loop, this pass also has to set a lot of barriers (which is rare in OpenGL).

Q: Which Vulkan concept is hardest?

Around 1/3 of the project time was spent on synchronization. VkAccessFlagBits is efficient if you create an API, but a nightmare to work with. There is a lack of clear documentation about which VkAccessFlagBits are required by which command. E.g. vkCmdFillBuffer mentions only that it's a "transfer" operation (so probably vk::AccessFlags2::TRANSFER_WRITE with vk::PipelineStageFlags2::TRANSFER), but vkCmdClearColorImage has no such mention (it's vk::AccessFlags2::TRANSFER_WRITE with vk::PipelineStageFlags2::CLEAR according to vk::AccessFlags2::TRANSFER_WRITE). I strongly recommend using VK_KHR_synchronization2 extension (promoted to Vulkan 1.3). While all concepts stay the same, at least the API is a tiny bit more organized. Synchronization is also prevalent due to image layout transitions. vkCmdPipelineBarrier works differently inside a render pass, which in multithreaded env. would prohibit simplifications like VkTexture.layout: vk::ImageLayout to store the previous layout. Fortunately, this app is single-threaded.

Q: What is your favorite Vulkan feature?

Vulkan validation layers that intercept Vulkan calls and check provided parameters. Good for checking e.g. VkPipelineStageFlagBits vs VkAccessFlagBits. It also has best practices and basic synchronization guidelines.

Honorable mentions