Tuesday 5 May 2020

Volumetric Clouds - Part 2 - Writing an Opacity Raymarcher

Quick update: A couple of days have passed since I wrote part one of the Volumetric Clouds and a few things have changed. We have decided to try and keep the clouds a bit more subtle to not distract as much from the buildings, so we will probably keep them low contrast and experiment a bit with the settings.

Clouds - Work in progress
Definitely needs tweaking

After figuring out the basics I had to dive into writing the Raymarcher, something I hadn't done before, in the Custom Node of Unreal's Material Editor. As mentioned before that meant learning HLSL from scratch. Fortunately it is very similar to C++ and the shader didn't require anything complex, so I was fine. Otherwise the time would have been way too short. Fortunately there are some great resources out there explaining the theory of raymarching. Here are some of the ones I used next to Guerilla Games Paper:

https://shaderbits.com/blog/creating-volumetric-ray-marcher (Ryan Brucks - Creating a Volumetric Ray Marcher)
https://computergraphics.stackexchange.com/questions/161/what-is-ray-marching-is-sphere-tracing-the-same-thing/163
https://www.youtube.com/watch?v=PGtv-dBi2wE (Art of Code - Raymarching for Dummies)
https://www.youtube.com/watch?v=Ff0jJyyiVyw (Art of Code - Raymarching simple Shapes)
https://www.youtube.com/watch?v=Cp5WWtMoeKg (Sebastian Lague - Coding Adventure - Raymarching)
http://www.diva-portal.org/smash/get/diva2:1223894/FULLTEXT01.pdf ( Fredrik Häggström - Real-Time Rendering of Volumetric Clouds)
http://jamie-wong.com/2016/07/15/ray-marching-signed-distance-functions/ (Jamie Wong - Raymarching and Signed Distance Functions)

Here is the full code I am going to break down, it is still subject to changes and improvements, and the Material setup in Unreal.


M_Clouds Overview

 

1 - Writing an Opacity Raymarcher

To sample the opacity of the volume textures a vector/ray between the camera and volume is used to define the marching direction and then the texture gets sampled whilst moving along that ray in increments. At the end the the samples get combined to get the accumulated density as seen from the camera's view.

Raymarching Visualisation

The code to do this looks something like this:

However to actually make sure, that the Raymarcher samples the points within the box containing the volume, we need to figure out  where it needs to start sampling and how far it needs to move within the box. A lot of the resources I found make use of the Ray-Box intersection Algorithm. To keep it simple I stuck with the Bounding Box being aligned to the world axis.

The following resources were really useful for understanding how the algorithm works:
https://www.youtube.com/watch?v=4h-jlOBsndU (SketchpunkLab -WebGL 2.0:046:Ray intersects Bounding Box (AABB))
https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection (Scratchapixel - Ray-Box Intersection)
https://developer.arm.com/docs/100140/0302/advanced-graphics-techniques/implementing-reflections-with-a-local-cubemap/ray-box-intersection-algorithm (ARM Developer - Ray-Box Intersection Algorithm)
https://medium.com/@bromanz/another-view-on-the-classic-ray-aabb-intersection-algorithm-for-bvh-traversal-41125138b525 (Roman Wiche - Another View on the Classic Ray-AABB Intersection Algorithm for BVH Traversal)

These all so a far better job at explaining how it works, than I could ever do, but I will try to give a brief overview. I have also annotated the code to hopefully make a few things a bit clearer.

So essentially the bounding box is defined by a minimum and maximum (X, Y, Z) position, which become the origin of corresponding new axis, as shown in the image below.

Bounding Box
The ray is defined by the following formula: f(t) = origin + Direction * t. The Intersection algorithm then checks where that ray intersects with the bounding axis and if the intersection is actually on the surface of the bounding box.
I further check, if part of the bounding box is occluded by something else by comparing Scene Depth to the further away intersection. Otherwise the volume is drawn on top of all other objects, without regarding depth. Here is a comparison between the Raymarcher with and without Depth Check.

Depth Check OFF vs Depth Check ON

Here is the HLSL Ray-Box Intersection Code:


The Algorithm returns both the point to start the sampling from, as well as the distance to travel within the box. I can now use the later to calculate the step size. I have visualised that step length throughout the box, which gets used by the Raymarcher, being shorter towards the thinner sections from the camera's view point.


I can now update the Raymarching code to this:

Unreal's Custom Node doesn't allow having functions, so the functions need to be put into a struct, that way they can be called.

4 comments:

  1. Just been catching up reading your blog, this is looking really great Sophie, I'm impressed with all the cool stuff you have squuezed into your FMP so far :D!

    ReplyDelete
    Replies
    1. Thanks, it was definitely a learning process, but a fun one :).

      Delete
  2. Great stuff, like that you post all your reference and inspiration too. Can't wait to see it all finished!

    ReplyDelete