Welcome to part 3 of this exciting series on how to beat a dead horse.
By the time I got to the end of the work for the last post, I was just about ready to put this project to bed (and by that, I mean P4 obliterate…).
There was just one thing I wanted to fix: The fact that I couldn’t rotate my models!
If I rotate the object, the lighting rotates with it.
Spaaaaaaace
To fix the rotating issue, in the UE4 lighting pass, I need to transform the light vector into the same space that I’m storing the SH data (object space, for example).
To do that, I need to pass through at least two of those object orientation vectors to the lighting pass (for example, the forward and right vectors of the object).
So, that’s another 6 floats (if I don’t compress them) that I need to pass through, and if you remember from last time, I’d pushed the limits of MRTs with my 16 spherical harmonics coefficients, I don’t have any space left!
This forced me to do one of the other changes I talked about: Use 3 band Spherical Harmonics for my depth values instead of 4 band.
That reduces the coefficients from 16 to 9, and gives me room for my vectors.
<Insert montage of programming and swearing here>
So yay, now I have 3 band SH, and room for sending more things through to lighting.
Quality didn’t really change much, either, and it helped drop down to 5 uv channels, which became very important a little later…
Going off on a tangent
I figured that since I was solving the problem for object orientation, maybe I could also do something for deforming objects too?
For an object where the depth from one side to the other doesn’t change much when it’s deforming, it should be ok to have baked SH data.
The most obvious way to handle that was to calculate and store the SH depth in Tangent space, similar to how Normal maps are usually stored for games.
I wanted to use the same tangent space that UE4 uses, and although Houdini 15 didn’t have anything native for generating that, there is a plugin!
https://github.com/teared/mikktspace-for-houdini
With that compiled and installed, I could plonk down a Compute Tangents node, and now I have Tangents and Binormals stored on each vertex, yay!
At this point, I create a matrix from the Tangent, Binormal and Normal, and store the transpose of that matrix.
Multiplying a vector against it will give me that vector in Tangent space. I got super lazy, and did this in a vertex wrangle:
matrix3 @worldToTangentSpaceMatrix; vector UE4Tang; vector UE4Binormal; vector UE4Normal; // Tangent U and V are in houdini coords UE4Tang = swizzle(v@tangentu, 0,2,1); UE4Binormal = swizzle(v@tangentv, 0,2,1); UE4Normal = swizzle(@N, 0,2,1); @worldToTangentSpaceMatrix = transpose(set(UE4Tang, UE4Binormal, UE4Normal));
The swizzle stuff is just swapping Y and Z (coordinate systems are different between UE4 and Houdini).
Viewing the Tangent space data
To make debugging easier, at this point I made a fun little debug node that displays Tangents, Binormals and Normals the same as the model viewer in UE4.
It runs per vertex, and creates new coloured line primitives:
Haven’t bothered cleaning it up much, but hopefully you get the idea:
And the vectorToPrim subnet:
So, add a point, add some length along the input vector and add another point, create a prim, create two verts from the points, set the colour.
I love how easy it is to do this sort of thing in Houdini 🙂
The next step was to modify the existing depth baking code.
For each vertex in the model, I was sending rays out through the model, and storing the depth when they hit the other side.
That mostly stays the same, except that when storing the rays in the SH coefficients, I need to convert them to tangent space first!
Getting animated
Since most of the point of a Tangent space approach was to show a deforming object not looking horrible, I needed an animated model.
I was going to do a bunch of animation in Modo for this, but I realized that transferring all my Houdini custom data to Modo, and then out to fbx might not be such a great idea.
Time for amazing Houdini animation learningz!!
Here’s a beautiful test that any animator would be proud of, rigged in Houdini and dumped out to UE4:
So, I spent some time re-rigging the Vortigaunt in Houdini, and doing some more fairly horrible animation that you can see at the top of this post.
Although the results aren’t great, I found this weirdly soothing.
Perhaps because it gave me a break from trying to debug shaders.
At some point in the future, I would like to do a bit more animation/rigging/skinning.
Then I can have all the animators at work laugh at my crappy art, in addition to all the other artists…
Data out
Hurrah, per-vertex Tangent space Spherical Harmonic depth data now stored on my animated model!
This was about the part where I realized I couldn’t find a way to get the Tangents and Binormals from the Houdini mesh into Unreal…
When exporting with my custom data, what ends up in the fbx is something like this:
UserDataArray: { UserDataType: "Float" UserDataName: "tangentu_x" UserData: *37416 {...
When I import that into UE4, it doesn’t know what that custom data is supposed to be.
If I export a mesh out of Modo, though, UE4 imports the Tangents and Binormals fine.
So I jumped over into Modo, and exported out a model with Tangents and Binormals, and had a look at the fbx.
This showed me I needed something more like this:
LayerElementTangent: 0 { Version: 102 Name: "Texture" MappingInformationType: "ByPolygonVertex" ReferenceInformationType: "Direct" Tangents: *112248 {...
C# to the rescue!!
I wrote an incredibly silly little WPF program that reads in a fbx, changes tangentu and tangentv user data into the correct layer elements.
Seriously, what’s with all the questions? What is this, the Spanish inquisition?
80% of them end up looking like this:

public string CreateLayerElementBlock(List<Vector3D> pVectors, string pTypeName) { string newBlock = ""; int numVectors = pVectors.Count; int numFloats = pVectors.Count * 3; newBlock += "\t\tLayerElement" + pTypeName + ": 0 {\n"; newBlock += "\t\t\tVersion: 102\n"; newBlock += "\t\t\tName: \"Texture\"\n"; newBlock += "\t\t\tMappingInformationType: \"ByPolygonVertex\"\n"; newBlock += "\t\t\tReferenceInformationType: \"Direct\"\n"; newBlock += "\t\t\t" + pTypeName + "s: *" + numFloats + " {\n"; newBlock += "\t\t\t\ta: "; ...
Gross. Vomit. That’s an afternoon of my life I’ll never get back.
But hey, it worked, so moving on…
UE4 changes
There weren’t many big changes on the UE4 side, just the switching over to 3 band SH, mostly.
Shader changes
Now to pass the Tangent and Binormal through to the lighting pass.
float3x3 TangentToWorld = { GBuffer.WorldTangent, GBuffer.WorldBinormal, cross(GBuffer.WorldTangent, GBuffer.WorldBinormal), }; float3 TangentL = mul(L, transpose(TangentToWorld)); float DepthFromPixelToLight = saturate(GetSH(SHCoeffs, TangentL));
Conclusion
If I were to ever do this for real, on an actual game, I’d probably build the SH generation into the import process, or perhaps when doing stuff like baking lighting or generating distance fields in UE4.