Volumetric Rendering: Raymarching

Volumetric Rendering: Raymarching

This post continues the tutorial on volumetric rendering, introducing one of the most used techniques: raymarching.

You can find here all the other posts in this series:

The full Unity package is available at the end of this article. 📦

Introduction

Loosely speaking, the standard behaviour of Unity 5’s lighting engine stops the rendering when a ray from the camera hits the surface of an object. There is no built-in mechanism for those rays to penetrate within the surface of an object. To compensate for this, we have introduced a technique called raymarch. What we have in a fragment shader is the position of the point we are rendering (in world coordinates), and the view direction from the camera. We can manually extend those rays, making them hit custom geometries that exist only within the shader code. The barebone shade that allows us to do this is:

struct v2f {
	float4 pos : SV_POSITION;	// Clip space
	float3 wPos : TEXCOORD1;	// World position
};

// Vertex function
v2f vert (appdata_full v)
{
	v2f o;
	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	o.wPos = mul(_Object2World, v.vertex).xyz; 
	return o;
}

// Fragment function
fixed4 frag (v2f i) : SV_Target
{
	float3 worldPosition = i.wPos;
	float3 viewDirection = normalize(i.wPos - _WorldSpaceCameraPos);
	return raymarch (worldPosition, viewDirection);
}

The rest of this post will provide different implementations for the raymarch function.

Raymarching with Constant Step

The first implementation of raymarching introduced in Volume Rendering used a constant step. Each ray is extended by STEP_SIZE in the view direction, until it hits something. If that’s the case, we draw a red pixel, otherwise a white one.

vol3

Raymarching with constant step can be implemented with the following code:

fixed4 raymarch (float3 position, float3 direction)
{
	for (int i = 0; i < STEPS; i++)
	{
		if ( sphereHit(position) )
			return fixed4(1,0,0,1); // Red
 
		position += direction * STEP_SIZE;
	}

	return fixed4(0,0,0,1); // White
}

As seen already, it renders unsexy, flat geometries:

rotate

The post Surface Shading will be dedicated entirely to give three-dimensionality to volumetric geometries. Before that, need to focus on a better implementation of the raymarch technique.

Distance Aided Raymarching

What makes raymarching with constant step very inefficient is the fact that rays advance by the same amount every time, regardless of the geometry that fills the volumetric world. The performance of a shader suffers immensely by adding loops. If we want to use real-time volumetric rendering, we need to find better a more efficient solution.

We would like a way of estimating how far a ray can travel without hitting a piece of geometry. In order for this technique to work, we need to be able to estimate the distance from our geometry. In the previous post we used a function called sphereHit which indicated whether a point was inside a sphere or not:

bool sphereHit (float3 p)
{
    return distance(p,_Centre) < _Radius;
}

We can change it in such a way that instead of a boolean value, it returns a distance:

float sphereDistance (float3 p)
{
    return distance(p,_Centre) - _Radius;
}

This now belongs to a family of functions called signed distance functions. As the name suggests, it provides a measure which can be positive or negative. When positive, we are outside the sphere; when negative we are inside and when zero we are exactly on its surface.

What sphereDistance allows us is to have a conservative estimation of how far our ray can travel without hitting a sphere. Without any proper shading, volumetric rendering is fairly uninteresting. Even if this example might seem trivial with a single sphere, it becomes a valuable technique with more complex geometries. The following image (from Distance Estimated 3D Fractals) shows how raymarch works.  Each ray travels for as long as its distance from the closest object. In such a way we can dramatically reduce the number of steps required to hit a volume.

ray

This brings us to the distance-aided implementation of raymarching:

fixed4 raymarch (float3 position, float3 direction)
{
	for (int i = 0; i < STEPS; i++)
	{
		float distance = sphereDistance(position);
		if (distance < MIN_DISTANCE)
			return i / (float) STEPS;

		position += distance * direction;
	}
	return 0;
}

To better understand how it works, we replaced the surface rendering with a colour gradient that indicates how many steps were required for raymarch to hit a piece of geometry:

vol11

It’s immediately obvious that the flat geometry that faces the camera can be identified immediately. The edges, instead, are much trickier. This technique also estimates how close we are to any nearby geometry. We will see in a future instalment, Ambient Occlusion, how this can be helpful to add details to our volume.

What’s next…

This post introduces the de-facto standard technique used for real-time raymarching shaders. The rays advance in the volumetric medium according to a conservative estimation of the closest nearby geometry.

The next post will focus on how to use distance functions to create geometrical primitives, and how they can be combined together to create whichever shape you want.

You can find the full list of articles in the series here:

⚠  Part 6 of this series is available for preview on Patreon, as its written content needs to be completed.

If you are interested in volumetric rendering for non-solid materials (clouds, smoke, …) or transparent ones (water, glass, …) the topic is resumed in detail in the Atmospheric Volumetric Scattering series!

By the end of this series you’ll be able to create objects like this one, with just three lines of code and a volumetric shader:

Download Unity Package 📦

Become a Patron!

The Unity package contains everything needed to replicate the visual seen in this tutorial, including the shader code, the assets and the scene.

Comments

24 responses to “Volumetric Rendering: Raymarching”

  1. Hi !
    thank you for sharing this is very intresting .
    I have a question can one have a raymarching system that support inputing a custom shader inside?
    I wanna use that effect but i want the shading to be using my own shader and material.
    thank you very much once again.

  2. […] raymarching is or how it works, and if you want to know more about the nitty-gritty of it there are several smarter people that can explain it far better than I ever could. Long story short its a form of […]

  3. Thanks so much for this wonderful series of tutorials! I’ve been meaning to learn about volumetric rendering for a long time. I’m having a slight issue which is that my cube gets distorted around edges, not sure what’s happening. Any ideas?

    https://imgur.com/ntES7Z8

    1. Mmm is very hard to tell what’s wrong without the shader code!

      1. Thuong Long avatar
        Thuong Long

        That’s because of the bounding box UV space, I think. I used exactly the same code in your tutorial and I got exactly the same error with Unity 5.6. Could you help us on this?

        Anyway, here is the screenshots:

        https://drive.google.com/open?id=1htzOCPKsdzfr2Wh_nIp_THjTr4rZOIbg

        And this is the shader code I used, upload from project’s Asset folder:

        https://drive.google.com/open?id=1_PlsMvQzhRRlhUdTRM_vF72T6-E9cjRf

        1. Alwin Joshy avatar
          Alwin Joshy

          Reduce the step size. that will do.

  4. Great tutorial, just one question. In an attempt to get the cube to still display while the camera is inside of it, I tried turning Culling to Front and instead of passing the geometry position(of the cube) into raymarcher, passing in the world camera position. Oddly, it seemed to work. Why do we use the Intersection position as the origin from our raymarching, and not the world camera position?

    1. Hey! It all depends on what you are trying to achieve.
      If you are inside a cube that is culling its back faces, nothing will be drawn.
      If you are inside a cube, you can have a two pass shader that uses the back faces to draw based on camera position. That, as you suggested, should work! 😀

  5. Seyed Morteza Kamaly avatar
    Seyed Morteza Kamaly

    Amazing!!!
    I make this shader here:
    https://github.com/smkplus/UnityRayMarching

    1. Ohh that’s so nice!
      I’m glad this tutorial has helped you!

  6. […] Part 2. Volumetric Rendering: Raymarching [🇨🇳] […]

  7. Very nice tutorial, but it would be nice if the animgif would show the actual expected output of the example and the boolean cube. It’s hard to see if I’m actually doing it right. 🙂

    1. edit: and NOT the boolean cube, ofcourse. No boolean pun intended! 🙂

  8. Eric Raposo avatar
    Eric Raposo

    How can you have a unity camera go within the volume contained inside the cube. Say for example I wanted to clip through it while the camera passes through it? I assume once the camera clips the mesh it no longer renders anything.

    1. Hey! Exactly, that’s a big issue.
      One solution is to use a billboard quad to render your volume.
      You can also play with Cull Off.
      Unfortunately there’s no one-line solution. Also, you have to take into account the camera position to interpolate the values properly; not just the view direction.

  9. […] 2: Raymarching | Focuses on the the implementation of distance-aided raymarching, the de-fact standard technique […]

  10. Ricardo Fonseca avatar
    Ricardo Fonseca

    I created a scene, put a cube in it, applied the material created with the shader of Raymarching with constant step, the code is correct, but the cube stays white all the time. Any reason for this?

    1. This code doesn’t seem to work. The raymarch method always returns 0 for all values of _Radius and MIN_DISTANCE.

      1. I had to fiddle around with this a lot to get results that looked even a little like Alan’s. The best MIN_DISTANCE value I found was 0.01. Declared like: #define MIN_DISTANCE 0.01

    2. direction need normalize

    3. lalaphoon avatar

      Hey I am not too sure what’s the problem. But I had the same issue and I found something interesting was that my code never went through my for loop. The reason was the variables STEPS. I did declare it before I used but it’s just not working. So I changed it into an actual value like 50 and then it’s working now.

  11. […] Volumetric Rendering: Raymarching […]

Leave a Reply

Your email address will not be published. Required fields are marked *