Blending in Detail

Possibly. I'd have to think about this! I'm a bit doubtful since some simplifications came from the assumption that t is a unit vector, but we'll see.

If you're normalising up front, then at least you don't need the normalise at the end (if you use the original form with the division by t.z), so it's probably not that bad.

How to replicate UDN in Photoshop?

I’ve tried to implement this code in a Unity3D Surface Shader, overlay looks outright wrong and RNM seems to do the normal blending correctly but the lighting looks totally wrong. All the other ones look correct & pretty similar, but that might be because my test normal maps are pretty simple and on a sphere.

Here is the code for overlay:

float overlay(float x, float y)
{
    if (x < 0.5)
        return 2.0*x*y;
    else
        return 1.0 - 2.0*(1.0 - x)*(1.0 - y);
}

// Overlay Blending
float3 overlayBlend(float3 n1, float3 n2)
{
    float3 n = float3(overlay(n1.x, n2.x), overlay(n1.y, n2.y), overlay(n1.z, n2.z));
    return normalize(n*2.0 - 1.0);
}

RNM:

float3 rnmBlend(float3 n1, float3 n2)
{
    n1 = n1*float3( 2,  2, 2) + float3(-1, -1,  0);
    n2 = n2*float3(-2, -2, 2) + float3( 1,  1, -1);
    return normalize(n1*dot(n1, n2)/n1.z - n2);
}

and here is the pertinent surface shader part:

void surf (Input IN, inout SurfaceOutput o)
{
   float3 main_normal = UnpackNormal(tex2D (_MainNormalTex, IN.uv_MainTex));
   float3 target_normal = UnpackNormal(tex2D(_TargetNormalTex, IN.uv_MainTex));
// float3 targetBlendedNormal = linearBlend(main_normal, target_normal);
// float3 targetBlendedNormal = overlayBlend(main_normal, target_normal); //not working correctly
// float3 targetBlendedNormal = partialDerivativeBlend(main_normal, target_normal); 
// float3 targetBlendedNormal = whiteoutBlend(main_normal, target_normal); 
   float3 targetBlendedNormal = udnBlend(main_normal, target_normal); //using this for now
// float3 targetBlendedNormal = rnmBlend(main_normal, target_normal); //not working correctly
// float3 targetBlendedNormal = unityBlend(main_normal, target_normal); 
    o.Normal = lerp( main_normal, targetBlendedNormal, blendAmount);
}

not sure why its not working well, I can post pics if required.

Sorry, I somehow missed your comment at the time. You should be able to replicate UDN in PS through layer/channel operations (mask and add the appropriate channels – with pre-scaling by 0.5 if working in 8bits), followed by normalisation using, say, the NVIDIA Texture Tools (https://developer.nvidia.com/n... ). You could probably automate this via an action script.

Ideally someone would make a plugin to do all of this (particularly for RNM, since it's less trivial); unfortunately I don't have the time nor the patience to do this myself at the moment.

All of the examples in the article assume basic packed normals as input (x, y, z scaled and biased by ~ 0.5). Is that how your normals are packed? If so, you'll want to skip those calls to UnpackNormal. If you're using a different encoding (e.g. reconstructing z based on x and y), then you'll need to either repack before doing the blending, or adjust the methods so that they work with unpacked normals. Let me know!

Here is the unpack function from unity (from UnityCG.cginc)

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE)
    return packednormal.xyz * 2 - 1; 
#else
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
    return normal;
#endif
}

since I’m on windows I’m probably hitting the #else part which I think is what you mentioned
(e.g. reconstructing z based on x and y) …

I don’t think I understand the math enough to rewrite all those methods with unpacked normals. = (
Maybe repacking is easier? Something like unpackedNormal.xyz / 2 + 1 ?

Yes, that repacking will work Update: sorry, I wasn’t paying attention; you need + 0.5 not + 1. Alternatively, you can use this for RNM with unpacked normals:

float3 rnmBlendUnpacked(float3 n1, float3 n2)
{
n1 += float3( 0, 0, 1);
n2 *= float3(-1, -1, 1);
return n1*dot(n1, n2)/n1.z - n2;
}

Note: I've skipped normalize() at the end, because the normals will already be unit length (due to the reconstruction).

Here are my three functions:

half3 rnmBlend(half3 n1, half3 n2)
{
    n1 = n1*float3( 2,  2, 2) + float3(-1, -1,  0);
    n2 = n2*float3(-2, -2, 2) + float3( 1,  1, -1);
    return normalize(n1*dot(n1, n2)/n1.z - n2);
}

half3 rnmBlendRepack(half3 n1, half3 n2)
{
    n1 = n1.xyz / 2 + 1;
    n2 = n2.xyz / 2 + 1;
    return rnmBlend(n1, n2); 
}

float3 rnmBlendUnpacked(float3 n1, float3 n2)
{
    n1 += float3( 0,  0, 1);
    n2 *= float3(-1, -1, 1);
    return n1*dot(n1, n2)/n1.z - n2;
}

rnmBlendRepack does not work very well.

rnmBlendUnpacked looks much better, but it has a strange problem that I also notice on the “unity blend” but not on the “udn blend”. In a scene with 1 light and a sphere, you can see some random highlights on the unlit side of a sphere. Here is a screenshot: http://www.inversethought.com/…

rnmBlendRepack does not work very well.

Yes, sorry, I gave bad advice earlier; you need to add 0.5 when repacking, not 1 (standard unpacking is nu = n*2 - 1; the reverse is (nu + 1)/2, which is the same as nu/2 + 0.5).

it has a strange problem ... you can see some random highlights on the unlit side of a sphere

It’s a bit hard to debug remotely, but there are a couple of potential issues that could creep in due to negative z values. Try the following functions (one at a time):

float3 UnpackNormalSafer(float4 packednormal)
{
float3 normal;
normal.xy = packednormal.wy*2 - 1;
float d = dot(normal.xy, normal.xy);
normal.z = (d <= 1) ? sqrt(1 - d) : 0;
return normalize(normal);
}

float3 rnmBlendUnpackedClampZ(float3 n1, float3 n2)
{
n1 += float3( 0, 0, 1);
n2 *= float3(-1, -1, 1);
float3 n = n1*dot(n1, n2)/n1.z - n2;
if (n.z < 0)
n = normalize(float3(n.x, n.y, 0));
return n;
}

I tried both rnmBlendUnpackedClampZ and UnpackNormalSafer and pipe that into rnmBlendUnpackedClampZ, didnt really get rid of all the highlights in the dark area,

however, if I turn on shadows, the shadows will attenuate the highlights in the dark sides, so its probably not a real issue or maybe a unity issue ?

I think those highlights being visible in rnm are actually extra normal detail that is lost in the udn one, because it depends on the light angle, moving the light slightly around one can see them come in and out.

I'm a bit confused about it though. Thank you for all the help though, I learned a lot !
I can post links to a unity project, if you would like to debug/ see it for yourself, should run in free version. Let me know.

Sure, I'd like to see it, plus it'd be a good excuse to finally try Unity.

Here is the link :

install unity, then

If you start from a new blank project then import this package:
http://www.inversethought.com/...

or just unzip this complete project and double click on test_secene.unity to open:
http://www.inversethought.com/...

Thanks. I'll try and take a look later this week.

Thank you for sharing!
I recently had to do 2-layer material blending, with an additional base map on top. RNM worked perfectly for keeping the high-frequency details of the normal maps!

RNM worked perfectly for keeping the high-frequency details of the normal maps!

Nice!

Just tried it in my engine, looks pretty great! But is there any way to have a blend factor [0-1] in there ?

Do you mean to control the 'strength' of the detail normal map? (If yes, a quick and dirty way to achieve that would be by scaling the x and y components of u, i.e.: u.xy *= strength).

Thanks, that seems to do the trick.

More elegant than my lerp(Tex0.Sample..., r, blendFactor) ;)

Lets start off with Awesome blog, and this and the specular showdown are two things I find really really nice here!
I’m thinking of making this into a photoshop filter/blend mode, never done anything such before, but would be fun and useful for artists, however:

As I couldn’t find any allowances for how your code is allowed to be used, (I’m assuming you havn’t patented it?), I was wondering if it would be allowed to use it as-is, without having to take the “idea”, and create something similar. (As copyright applies if nothing to the contrary is written.)

Lets start off with Awesome blog, and this and the specular showdown are two things I find really really nice here!

Thanks, it’s great to hear that!

I was wondering if it would be allowed to use it as-is

You’re free to use the code. Create away!