Post 2: Add Volumetric Material Support

Multiple volumetric objects in motion

Post 2: Add Volumetric Material Support

Introduction

Volume objects are a good fit for ATAA, because high luminance variance can be introduced by 1) randomization of ray marching start position to mitigate slicing effect, and 2) object motions that create blurry and ghosting effects within the volume object. If we replace those high variance pixels with correct tracing pixels, volume objects can be perfect rendered without any artifacts. The big picture above illustrates this point. In this post, I will show how I added correct ATAA support to custom volumetric material within the path tracer created in the last post.

Custom volumetric material in UE4

The image below shows the volumetric material to support ATAA. In the Custom function, a basic ray marching algorithm is implemented, which is directly provided to us by Marc. The basic idea is to trace a shadow ray marching to the light source for each marching step. The density of the volume is a Perlin noise controlled by the NoiseMin and NoiseMax. You can refer to Ryan’s blog on Creating a Volumetric Ray marcher for a complete guide.

A screenshot of the volumetric material.

The ray marching shader code for the Custom function is post here as below (got permission from Marc):

#define DENSITY(P) ( \
    saturate(smoothstep(1.0, 0.8, length(P) / Radius)) * \
    smoothstep(NoiseMin, NoiseMax, \
        MaterialExpressionNoise(P, 0.05, 1, 2, true, 6, \
            1.0, 0.0, 2.0, 0.0, false, 512.0)) \
    )

float3 PositionWorld = Parameters.AbsoluteWorldPosition.xyz;
float3 CenterWorld = Primitive.ObjectWorldPositionAndRadius.xyz;
float Radius = Primitive.ObjectWorldPositionAndRadius.w;
float3 P = PositionWorld - CenterWorld;

float3 Ray = -Parameters.CameraVector.xyz;
float RayStepSize = 2.0 * Radius / MaxSteps.x;
float3 RayStep = Ray * RayStepSize;

float3 ShadowRay = View.AtmosphericFogSunDirection.xyz;
if (dot(ShadowRay, ShadowRay) < 0.5) ShadowRay = normalize(float3(1,1,1));

float ShadowStepSize = 2.0 * Radius / ShadowSteps.x;
float3 ShadowStep = ShadowRay * ShadowStepSize;

int3 RandPos = int3(Parameters.SvPosition.xy, View.FrameNumber);
float Rand = float(Rand3DPCG16(RandPos).x) / 0x10000;
P += RayStep * Rand;

float4 Color = 0;
for(int i = 0;
    i < MaxSteps && length(P) < Radius;
    ++i, P += RayStep)
{
    float4 LocalColor = float4(Albedo.rgb, 1) * DENSITY(P);

    if (LocalColor.a > 0.0) {
        float3 SP = P + ShadowStep;
        float Shadow = 0;
        for(int j = 0;
            j < ShadowSteps && length(SP) < Radius;
            ++j, SP += ShadowStep)
        {
            Shadow += (1.0 - Shadow) * DENSITY(SP);
        }
        LocalColor.rgb *= (1.0 - Shadow);
    }

    Color += (1. - Color.a) * LocalColor;
}

return Color;

Problems

There are at least three problems to solve to make the path tracing post process produce the same output as UE4 rasterization pipeline does.

  1. How to trace the volumetric object UE4 friendly. For a path tracer, if a ray intersects a volumetric object, it will go in and follow the Radiative Transportation Equation, after attenuation and bouncing inside the volume, it will bounce out of the object. However, if we use the same technique here used in the film industry using either delta tracking, ratio tracking or ray marching etc., we will be unable to match the rendering result with the volumetric material, at least with limited rays. We need to apply a similar approach as used in UE4 - the volumetric objects are rendered in a different pass other than opaque objects.

  2. There is no shadow for the custom volumetric material. If we follow the path tracing framework, volumetric shadows will be created, which is a problem if we need to apply ATAA, since we do not have shadow for them in UE4 by default. So, we need to modify the path tracer to avoid shadow for volumetric material.

  3. Generic solution to support all volumetric materials. We can hard code the material shader into the post process without relying on the editor, but a more generic solution that can be automated, at least in a later phase, is preferred. Then, users can have volumetric objects supporting ATAA without extra efforts. However, their change to the material in the editor is directly reflected in the rendering.

Hacking solution

To solve the first problem, we can directly call the same shader code when a ray hit a volumetric object, and keep the resulting rgba component as transparent color. Then we decrease the number of bouncing and set the ray to the far ray object intersection point. If we keep on hitting another volumetric object, we can accumulate the new color to the old one with alpha blending. In this way, we can support multiple transparent objects. The drawback here is that we cannot handle rays that does not travel in a straight line.

To solve the second problem, we can add a flag to the bouncing ray. Once it bounces to an opaque object, we will disable the accumulation of volumetric objects. However, when UE4 supports shadow for custom volumetric material by default, we can remove this hack solution.

The third problem requires an API to access the material parameters as well as the custom shader code before going into the default pass. For example, in the material figure above, we need to get the Albedo parameter and the shader code before going into Emissive Color and Opacity. We also need to know which material has been interacted when a ray hit an object. The MaterialID is my current hack for this purpose. For this volumetric object, it has MaterialID = 4.

After exploration in the codebase, I achieved half of it. I was able to get parameters automatically with the material interface. For example, it supports dynamic change of the parameter Albedo. But I did not have the luck to get material shader code automatically regenerated into the tracing post process - I added the shader code manually for volumetric material.

Lighting mismatch problem

So I implemented the solution without supporting multiple volumetric objects at first in hope that it works. Yet another problem showed up: the path tracing color of the floor did not match the color of the floor in UE4. It is the mismatch that I have not fixed in the last post.

a) Sparse traced scene color b) Floor color mismatch after combining TAA and ATAA pixels

Image a) shows the sparsely traced scene. Since the volume is moving, pixels with high variance are path traced. However, the background color is different from that in UE4 deferred rendering as shown in image b). We still need to solve the lighting mismatch problem.

Lighting re-configuration

In the demo scene above, I did not alter post process settings in the editor. So the global illuminance and sunlight are both used. And the albedo of the floor material is the same because the configuration is automatically uploaded to the shader. So I think it’s the time to downgrade configurations to make them the same. My best guess was to disable the global illuminance, because in the last post, it did not lead to a matching result.

First trial: So I disabled global illuminance by setting ShowFlag.GlobalIllumination 0 in the standalone game, and disabled GI in the tracing shader code, and do not use the MIS for the BRDF, but using the sunlight alone. Then I get the result in image a):

a) ATAA without GI b) Sparse tracing without soft shadow

I still got the mismatch problem there. I almost give up until…

Nth trial: So I disabled the soft shadow in the path tracer code apart from what I did in trial one. Behold! See the result in image b) above. They finally match. To have a close look, I take two screen shots: one with TAA and the other with ATAA on as below:

a) TAA b) ATAA

The ATAA is rendered with 64 bounce and with a max bounce per ray as 1, which is equal to 32 spp. Only pixels with variance higher than 0.005 are traced. You can open them in anothe tab to see the improvement.

Transparency mismatch problem

Then I added another volumetric object to test if the transparency is correct by applying alpha blending formula if a ray hits two consecutive volumetric objects:

$$ src_{RGBA}+=dst_{RGBA}(1-src_A) $$

However, UE4 does not apply this simplified alpha blending formula between two consecutive transparent objects directly.

a) Default transparency combination in UE4 b) Transparency using the above blending equation in the tracer
c) ATAA with corrected blending

You can compare the difference between image a) and b) above within the overlapping region. They are not the same. After exploration, I find that UE4 applies Porter and Duff’s over operator to blend transparent objects. The formula is as below:

$$ out_{A}=src_{A}+(1-src_{A})\cdot dst_A $$

$$ out_{RGB}=(src_{RGB}\cdot src_{A}+(1-src_{A})\cdot dst_A \cdot dst_{RGB})/out_{A} $$

$$ out_{A}=0 \rightarrow out_{RGB}=0 $$

With this over operator, a rendering result that matches the UE4 output can be created. Image c) shows the final rendering with corrected blending. I also find that, in order to use the simplified alpha blending, the color should be premultiplied with its alpha channel.

Future works

I am very excited when it works. However, I have somehow oversimplified the lighting configuration. The correct global illuminance should be added back at least. Otherwise, we cannot support even the default scene setup in UE4.


comments powered by Disqus