Thin Film Iridescence, HDRP and Shadergraph ramblings.

Physcially-based Thin Film Iridescence in the HDRP

I’ve been working at implementing Thin Film Iridescence in to the HDRP in Unity, mostly for some turntable stuff (so it doesn’t have to be implemented perfectly, its just for proof-of-concept work).

I also used it as an excuse to get familiar with the deeper parts of the HDRP and Shadergraph in the 2018.3 beta. And boy, has it been an interesting journey…

In principle it works. It’s based on code and research from Laurent Belcour (Unity Technologies) and Pascal Barla (INRA), and their whitepaper A Practical Extension to Microfacet Theory for the Modeling of Varying Iridesence. That said, I ran into a few problems along the way:

The Shadergraph custom node implementation is awful when compared to Unreal Engine

In UE4 you can simply define a custom node as a struct, that can contain as many functions as you care. It creates the node structure by taking the function that your node returns, and its return type & inputs. Like so (some random code):

#pragma once
struct MyCustomNode
{
    float sqr(float a)
    {
        return a * a;
    }
    float3 Main(float a, float b, float c)
    {
        float3 total = float3(0,1,0);
        total += Construction(a,b,c);
        float3 foo = float3(sqr(a) + sqr(foo), sqr(sin(sqr(total.y))), sqr(c));
        return total * foo;
    }
}
MyCustomNode material;
return material.Main(a,b,c);

That will give you a node with 3 inputs, a,b,c and one output, a float3. Note we have an internal function that we re-use

So, why is Shadergraph worse? Because you use C# and reflection.

This means that you define your input and outputs in C#, and define your HLSL as a string returned from the C# function. That string is the function, so you can’t declare internal functions. In Shadergraph we need all of this…

using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;

[Title("Custom", "MyCustomNode")]
public class MyCustomNode : CodeFunctionNode
{
    public MyCustomNode()
    {
        name = "My Custom Node";
    }


    protected override MethodInfo GetFunctionToConvert()
    {
        return GetType().GetMethod("MyCustomThing",
            BindingFlags.Static | BindingFlags.NonPublic);
    }
	static string MyCustomThing(
        [Slot(0, Binding.None)] float a,
		[Slot(1, Binding.None)] float b,
		[Slot(2, Binding.None)] float c
        [Slot(8, Binding.None)] out Vector3 Out)
    {
        Out = Vector3.zero;
        return @"
        {
            Out = float3(0,1,0);
            float3 foo = float3(pow(a,2) + pow(foo,2), pow(sin(pow(total.y,2)),2), pow(c,2));
			Out *= foo;
        }
    ";
    }
}

With this we have a few problems:

  1. In UE4 we can make a custom sqr() function and re-use it. In the Unity example we have to use a lot of pow(x,2). This is manageable, but in the in the case of the thin film iridescence, where there is a lot of re-use of functions, it becomes completely unmanageable
  2. We return a string, not actual HLSL code files. Good luck if you missed a closing ), or mis-typed a , somewhere, because Unity can’t tell you what line and column you made your mistake – you’ll just get something to tell you that its expecting something different on some irrelevant line number.
  3. We can’t input or output a matrix! We have to return a series of Vector2/3/4s and then combine them in Shadergraph. This has been a PITA.

What else?

  1. Shadergraph doesn’t yet expose what I consider essential nodes, such as light direction. I can’t calculate NdotL, or halfway vectors without horrible C# script hacks.
  2. It supports a Metallic Anisotropic shader, but no a Specular Color version… Unless (like myself) you go through the hassle of adding that feature into the Lit shader code. Its literally a few lines of code, and it works without any changes to the fundamental code. Why haven’t they added this?!
  3. I’d like some additional exposure for Tangent maps. The Lit shader supports this in Anisotropic shaders, but I’d like to re-use this logic in other shaders without having to re-implement it all myself again.

Ramblings over. I can’t release the iridescence stuff as its internal code, but I simply used the open-source code from the whitepaper and plugged it into a custom node that affects Albedo. Its not exactly ground-breaking/hard to do/top secret skills required.

Cheers,

M.

Leave a Reply

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