Home

Awesome

Real-Time Hybrid Hair Renderer in Vulkan™

<p align="center"><img src="/docs/figures/ponytail-hair.jpg" alt="Lara Croft's Ponytail"/></p>

This is a proof of concept hair renderer that's based on a novel hybrid approach. It's capable of rendering strand-based hair geometry in real-time, even for fully simulated hair styles with over 100,000 strands. Our hybrid rendering pipeline scales in the performance/quality domain by using a rasterizer for closeup shots and a raymarcher for level of detail minification. We can render multiple hair styles at once with this hybrid technique, which has a smooth transition between the two solutions.

In the figure above we see the ponytail from TressFX 3.1 (with 136,320 hair strands and 1,635,840 line segments) rendered in real-time (7ms) with our rasterized solution. If we want to render more than a couple of characters on the screen at once, we're going to need a more scalable solution, as it doesn't scale for far away distances. This is where our raymarcher comes in, as its performance scales linearly with the fragments rendered on the screen, and is a also lot cheaper for far away hairs. However, our raymarcher's performance breaks down during close-up shots, and also looks "worse" than our rasterizer for those cases, as it's an approximation of the real geometry. The trick is to use both of the solutions together! The rasterizer may perform worse, but it still produces high-quality close-up shots, and performance scaling isn't too bad in those cases. Our raymarcher performs and scales better at a distance and is indistinguishable from the rasterized results in those cases. The figure below shows both the rasterized and raymarched solutions together. Each screenshot is split in the middle, with the left part being the rasterized solution, and the right side the raymarched solution. We have alpha blended these two in the middle to simulate a level of detail transition. As you can see the results are quite close and the transitions are not that noticeable from far away. There's however a huge (up to 5-6x) performance difference that scales proportional to distance.

It is written 100% from scratch in Vulkan™ and simulates light scattering, self-shadowing and transparency in real-time. We do anti-aliasing by using a fast line coverage method. Our volumetric approximation is derived from the original geometry by a fast voxelization scheme that's used for raymarching and direct shading on an isosurface. We also use this volume for ambient occlusion and for more precise self-shadowing even in the rasterized case. Our work was published at EGSR 2019.

<p align="center"><img width=97% src="/docs/figures/hybrid.jpg" alt="Hybrid Hair Render"/></p>

Outline

For the rest of this document you'll find out about the features in our renderer and the benefits of using a hybrid approach when rendering hair, by looking at its performance. Once you're intrigued, we'll show you how to build, and run the project yourself so you can try it out. I have written lots of documentation if you want to find out how it works or which limitations there are. You'll find more screenshots towards the end of this document and in the Captain's Log found in the project wiki.

Table of Contents

Features

A real-time hybrid hair rendering pipeline suitable for video games, that scales in the performance and quality domain. It is:

It uses this rasterized solution for close-up shots, and our raymarched solution for level of detail. This hybrid hair renderer:

This novel volumetric approximation for strand-based hair can be found once per-frame for fully simulated hair. It features:

Our hybrid rendering solution combines the best of strand- and volume-based hair representations. Some benefits are that:

Benchmarks

Along with this project we bundle a set of benchmarks that can be run by passing the --benchmark yes flag. They compare the performance between the rasterizer and raymarcher and how these performance scale (e.g. with respect to increasing distances or strands). In order for you to get an idea if our solution is good enough for your purposes, we have included the results from our paper, which were run on a Radeon™ Pro WX 9100. The results were taken with V-Sync off and without any other GPU intensive programs running in the background. The timing information was taken via Vulkan timestamp queries, and averaged over a period of 60 frames (not much variance). We have plotted the results below for your viewing pleasure.

<p align="center"><img width=97% src="/docs/figures/breakdown.png" alt="Performance and Memory Breakdown"/></p>

In the above plot to the left we see how the rasterizer and raymarcher fare at different distances, and how much time each rendering pass takes. For the near cases (e.g. a character close-up) the raymarched solution is around twice as fast, but the fidelity isn't as good, as strands appear to be clumped together because of the volume approximation. On the other hand, the rasterized solution produces high-quality output as each strand is individually distinguishable. However for far away to medium distances, these small details are not noticable, and the rasterized and raymarched solution are indistinguishable. The raymarcher on the other hand is now 5x faster in these distances! It has better scaling with distance for far away shots.

Setup: Rendering at 1280x720, Ponytail Scene, V-Sync Off, 1024x1024 Shadow Maps, 256³ Volume, 512 Raymarching Steps.

The raymarcher also doesn't have to produce shadow maps, which would scale linearly with the number of light sources for the scene. Finally, notice that the strand voxelization is quite cheap and does not account for much of the total render time. In the memory department, the figure to the right shows the GPU data breakdown. When comparing to the original strand-based geometry, the volume does not consume an inordinate amount of memory, and this value can also be tweaked with the volume resolution. The main culpit are the PPLL nodes that are used for our transparency solution. These scale with the resolution and also depend on how many strands are being shaded (and might lead to artifacts if memory underallocated).

<p align="center"><img width=97% src="/docs/figures/scaling.png" alt="Performance Scaling"/></p>

For the two plots above we see how performance scales for each renderer with respect to screen coverage and number of hair strands. The raymarcher has a lower intercept, making it cheap to render for low screen coverage (far away distances). Performance on the rasterizer scales linearly with the number of hair strands (as expected), and also for the raymarcher but with a very slow slope (caused by the voxelization). Our technique works especially well for realistic amounts of hair, where anything less than ~20,000 strands of hair will look bald. While the scaling on the right doesn't look very promising for the raymarcher, its performance can be tuned by changing the number of raymarch steps that moves the intercept up / down.

Dependencies

All other dependencies are fetched using git submodules. They include the following awesome libraries: g-truc/glm, ocurnut/imgui, syoyo/tinyobjloader, nothings/stb and nlohmann/json. The C++17 Vulkan wrapper: vkpp is being developed alongside this project. It will at a later time be split into another repository: vkpp, when I have time to do it.

Compiling

  1. First, make sure you've changed your current directory to vkhr
  2. Then do git submodule update --init --recursive --depth 1
    • Description: fetches submodule dependencies to foreign
  3. Since we use premake, you'll most likely need to fetch it as well:
    • Tip: there's pre-generated Visual Studio solutions in build
      • if you're happy with that, you can skip the steps below
    • Unix-like: just install premake5 with your package manager
  4. Now make sure you have the glfw3 external dependency solved
    • Unix-like: just install glfw with your package manager too
    • Visual Studio: pre-built version is already provided for you!
  5. Finally, you'll also need Embree for the hair raytracing back-end:
    • Unix-like: just install embree using your package managers
    • Visual Studio: pre-built version is already provided for you!
  6. Generate the vkhr project files by targeting your current setup
    • Visual Studio: premake5 vs2019 or my alias make solution
      • then open the Visual Studio project in build/vkhr.sln
      • you might have to retarget the VS solution to your SDK
    • GNU Makefiles: premake5 gmake or just call make all/run.
  7. Build as usual in your platform, and run with bin/vkhr <scene>.

Distribution

Install: if you're on Arch Linux it's as simple as running makepkg -i.

For Windows just call make distribute for a "portable" ZIP archive.

The client then only needs a working Vulkan runtime to start vkhr.

System Requirements

Platforms must support Vulkan™.

It has been tested on these GPUs:

on Windows 10 and GNU / Linux.

Usage

Documentation

You're reading part of it! Besides this readme.md, you'll find that most of the important shaders are nicely documented. Two good examples are GPAA.glsl for the line coverage calculations, and approximate_deep_shadows.glsl for the self-shadowing technique. You'll notice that the quality of it varies quite a bit, feel free to open an issue if you sense something isn't clear. I haven't documented the host-side of the implementation yet as that would take too long, and isn't that interesting anyway.

If you want a high-level summary of our technique read Real-Time Hybrid Hair Rendering, which is a short conference paper on our method (only the pre-print). You'll also find a copy of it here, which you can build by using LaTeX. If you want a more extensive and detailed version of our paper, my thesis Scalable Strand-Based Hair Rendering, will soon be available. Both of these also show the difference between our technique and other existing frameworks like TressFX, that only use a rasterizer.

And if you still haven't had enough, I have written a bunch of entries in the Captain's Log, that shows the progress log from day 1 to the current version. Besides having a lot of pretty pictures, it shows the problems we encountered, and how we've solved them. This gives a bit more insight into why we have chosen this approach, and not something completely different. The slides for my thesis defense and presentation at EGSR 2019 could also be useful to get an overview into our technique.

Directories

Reporting Bugs

vkhr is 100% bug-free, anything that seems like a bug is in fact a feature!

This is a proof-of-concept research prototype, and as such, I wouldn't recommend using it for something serious, at least as it is. Also, do not expect this repository to be well maintained, I will not spend too much time with it after the thesis is done.

Still, if you find anything, feel free to open an issue, I'll see what I can do :)

Acknowledgements

First I would like to thank Matthäus Chajdas, Dominik Baumeister, and Jason Lacroix at AMD for supervising this thesis, and for always guiding me in the right direction. I'd also like to thank the fine folk at LiU for providing feedback and support, in particular, my examinator Ingemar Ragnemalm and Harald Nautsch at ISY and Stefan Gustavson from ITN. I would also like to thank AMD and RTG Game Engineering for their hospitality and friendliness, and for letting me sit in their Munich office.

Legal Notice

<img width=66% src="https://www.khronos.org/assets/images/api_logos/vulkan.svg" alt="The Vulkan Logo"/>

Vulkan and the Vulkan logo are registered trademarks of Khronos Group Inc.

All hair styles are courtesy of Cem Yuksel's great HAIR model files repository.

The ponytail and bear hair geometry are from the TressFX 3.1 repository, and proper rights have been granted by AMD Inc. to be used in this repository. However, you are not allowed to use it outside of this repository! i.e. not the MIT license for it!

The woman model was created by Murat Afshar (also for Cem Yuksel's repo).

Everything in this repository is under the MIT license except the assets I've used. Those fall under the license terms of their respective creators. All of the code in this repository is my own, and that you can use however you like (under the license).

Both GLFW and Embree are pre-compiled to facilitate building on Windows.

See: foreign/glfw/COPYING and foreign/embree/LICENSE for these licenses.

Screenshots

<p align="center"> <img src="/docs/figures/ponytail.jpg" alt="TressFX Ponytail"/> <br> The screenshot above is another render of the ponytail from TressFX (with 136,320 hair strands), but from a different angle. </p> <p align="center"> <img src="/docs/figures/bear.jpg" alt="Big Bear from TressFX"/> <img src="/docs/figures/bear-fur.jpg" alt="Enhanced Fur for Bear"/> <br> Above are screenshots of the bear from TressFX 3.1 (961,280 fur strands and 3,845,120 line segments) rendered in real-time. </p> <p align="center"> <img width=100% src="/docs/figures/composition.jpg" alt="Component-by-Component Difference in the Two Solutions"/> <br> In the figure above we show the component-by-component difference between our rasterized and raymarched solutions. </p> <p align="center"> <img width=49% src="/docs/figures/bear-tangents.png" alt="Original Tangents"/> <img width=49% src="/docs/figures/bear-voxelized-tangents.png" alt="Voxelized Tangents"/> <br> The comparisons above shows the differences between the actual tangents (on the left) and their voxelized approximations. </p> <p align="center"> <img width=32% src="/docs/figures/ponytail-raytraced-ao-comparison.png" alt="Raytraced AO"/> <img width=32% src="/docs/figures/ponytail-rasterized-ao-comparison.png" alt="Rasterized AO"/> <img width=32% src="/docs/figures/ponytail-raymarched-ao-comparison.png" alt="Raymarched AO"/> <br> Above is a comparison of the ground-truth AO (on the left) from our raytracer and our approximation (middle and right). </p> <p align="center"> <img width=49% src="/docs/figures/ponytail-aliased.png" alt="Aliased Ponytail"/> <img width=49% src="/docs/figures/ponytail-anti-aliased.png" alt="Anti-Aliased Ponytail"/> <br> Here we show the difference between not handling anti-aliasing and transparency at all (on the left) and when doing so ;-). </p>