logo80lv
Articlesclick_arrow
Talentsclick_arrow
Events
Workshops
Aboutclick_arrow
profile_login
Log in
3
Save
Copy Link
Share

UE5 Triplanar Deep Dive: From WorldAlignedTexture to High-Quality Normals – Part 2

This is the second part of Alina Ledeneva's comprehensive breakdown of how WorldAlignedTexture and WorldAlignedNormal operate under the hood in Unreal Engine 5.

WorldAlignedNormal High Quality: Full Understanding and Modernization

The base version of WorldAlignedNormal is fast, but on surfaces angled away from the axes, the normals become distorted. To avoid that, Unreal includes a secondary version - WorldAlignedNormal_HighQuality (which, by the way, is the one used by default).

HighQuality is more computationally expensive, but it handles normals more accurately: it first brings all projections into a common space and only then blends them. As a result, the surface preserves its correct orientation, and lighting looks far more believable.

A little more about WorldAlignedNormal(Base)

Let's first understand what's wrong with the basic WorldAlignedNormal function. I'll illustrate it using a single point on a cube rotated by 60° around the Z axis.

So, let's say there is a point and three projections: XY, XZ, and YZ. Since I'm only analyzing rotation around the Z axis, I'll ignore the XY projection (it gives zero contribution for this point), meaning we'll look at the top view only. Let's go through it step by step, just like every previous triplanar projection. The first step is always preparing the UV space. For visualization, imagine that from the XZ and YZ planes, two normals are projected onto this point: N1 (0.91, 0, 0.4) and N2 (0, 0, 1).

The second step is computing blend masks, as usual: the surface's world-space normal at that point is decomposed into its components. Each component represents how strongly that axis contributes to the projection. In this case, the face is oriented so that NormalMask_R = 0.5 and NormalMask_G = 0.87. That means the projection from the XZ plane should appear brighter than the YZ one. As the cube rotates, one projection fades while the other gains strength, producing the smooth blending characteristic of triplanar mapping.

Now, take the normals N1 and N2 from the first screenshot and multiply them by the corresponding components NormalMask_R and NormalMask_G, as the function does. Since N1 and N2 remain aligned to world axes and don't account for the object's rotation, the result will be only approximate. After the transformations, two vectors are obtained in world coordinates, one from the XZ projection, the second from the YZ projection. Here, it's worth explaining why the function multiplies by the normal component value, not just its sign.

If you simply linearly Lerp between two unit-length normals, the result will respect their directions but ignore their relative strengths. You'd effectively get an average direction, as if both projections contributed equally. The lateral components would partially cancel each other out, flattening the relief when viewed at an angle.

If you weaken each normal by its weight before mixing, then the weaker projection contributes less detail, the stronger one contributes more, and the result naturally gravitates toward the dominant projection. The secondary projection's relief fades proportionally to its weight, producing a smoother and more contrast-preserving transition. In the example below, you can easily see the difference between mixing units and weighted normals.

After converting to Tangent Space, you can see that the Z component of the weighted normal is noticeably smaller, meaning it's pulled more toward the dominant direction.

The video below compares two rotating cubes: the left cube uses blending of weighted ("shortened") normals, the right cube uses unit normals. You can clearly see that on the left, one projection smoothly transitions into another without losing contrast, while on the right, the projections briefly "wash out" before blending, making the relief appear flatter.

Thus, it turns out that the original normal vectors N1 (0.91, 0, 0.4) and N2 (0, 0, 1) have turned into the vector N (-0.98, 0, 0.18), a vector that lost its true spatial direction relative to the cube. This means that the resulting normal only approximately points the right way: it's "almost N1, a bit of N2, something in between the two straight orientations," but it doesn't actually account for the face's rotation. 

How WorldAlignedNormal_HighQuality Works

To eliminate these inaccuracies, Unreal provides WorldAlignedNormal_HighQuality. In this version, the normals are first brought and blended into a shared coordinate basis that respects the face's rotation, and only after that are they written into the final result.

Let's go through this using the same cube, but let it be rotated in WorldSpace at angles (-20, 30, 20). I'll repeat the previous process: project an arbitrary surface point onto the world planes XZ, YZ, and XY, and read the normals from the texture. Let's say these are the vectors: Nxz(1, 0, 0), Nyz(-0.87, 0.3, 0.4), and Nxy(1, 0, 0). From the geometrical normal, extract the masks: NormalMask_R = 0.81, NormalMask_B = 0.5 (they'll be used for blending).

Now the task is to determine on what basis to blend the normals. The first intuitive idea is to blend them directly in Tangent Space and then rotate the resulting vector to the proper orientation. Let's test that. To construct the basis for the vector transform, Unreal uses the built-in function CreateThirdOrthogonalVector, which takes two input vectors and outputs an orthonormal basis derived from them.

This is achieved through sequential cross products and normalizations. The first input preserves its direction and becomes the X axis; the second remains in the same plane relative to the first, and the angle between them is adjusted to 90° (forming Y); the third completes the left-handed orthobasis. In this case, the function takes the surface normal and the vector (0, 0, 1) (the world Z axis) to maintain tilt relative to the XY plane. The output is a new local basis aligned with the object's surface.

This basis will be used to transform the mixed normal (from Nxz and Nyz) into it. Therefore, we need to reorient it so that it matches Tangent Space: the Z axis should be perpendicular to the surface, X to the right, and Y down. X and Z can be remapped directly by swapping them, but the Y axis currently points opposite to Tangent Space Y, so they do not match.

There are several ways to fix this: Inside CreateThirdOrthogonalVector, you can swap the operands in the final cross product (so Y is generated in the correct direction), or you can manually invert Y by multiplying it by -1 during remap. In Unreal's standard implementation, the developers leave Y as-is in the basis and make corrections when transferring the normal from tangent space.

Let's rewind slightly and return to the blending step. To prepare a Tangent Space vector for the new basis, first invert its Y component, and also account for the face's orientation relative to the projected axis: depending on the sign of the normal, multiply X by +1 or -1. After this adjustment, the vector can be safely transformed into the new basis without losing its direction.

To include the third normal (from the XY projection), repeat the process in the same manner: build the orthonormal basis using the geometrical normal and the vector (0, 1, 0), the world Y axis, to preserve tilt relative to XZ. Apply the same remap and inversion where needed, then transform Nxy into that basis. Now these normals can be blended consistently and without loss.

WorldAlignedNormal_HighQuality Summary 

So, in the base WorldAlignedNormal, normals are blended along the world axes and partially lose their orientation on rotated surfaces: the relief visibly drifts, and the normal direction can shift. WorldAlignedNormal_HighQuality first brings all projections into a single, consistent basis and only then blends them, so relief and lighting remain stable on curved surfaces.

The difference is clear in the video below: when compared with a reference UV-mapped cube, the HighQuality version produces smoother and more realistic transitions than the base function.

WorldAlignedNormal_HQ_Modernized

Following the same logic as before, it's easy to apply the same improvements to WorldAlignedNormal_HighQuality, but be especially careful with the axis orientations of the new bases to ensure normals rotate correctly and scale compensation works as intended.

In this article, I've demonstrated how Unreal Engine 5's triplanar projection functions work and how to turn them into a flexible, production-ready tool. Now, WorldAlignedTexture_Modernized, WorldAlignedNormal_Modernized, and WorldAlignedNormalHQ_Modernized are fully synchronized: they support Local Space, a shared projection point, and custom projection rotation with mirroring. As a result, triplanar mapping behaves predictably across any object and maintains clean, seamless lighting.

Below is the final demonstration video: a material with Local Space, mirroring, and High Quality normals.

It's been an exciting deep dive. Next, I'll show you how to apply these principles to practical cases.

Practical Cases

Case 1. Rapid Prototyping with Rotation

Useful for objects with irregular, organic shapes or procedurally generated assets. In the video below, the texture rotation is demonstrated on a rock, with arrows indicating the UV directions. The rotation angle can be kept as a shared material parameter for all objects or exposed via Custom Primitive Data for per-mesh control.

Case 2. Breaking Up Tiling Across Multiple Objects

The next example shows how to apply this function to a stone wall. Here, the triplanar projection is calculated in Local Space through TransformPosition (rigidly attached to the object), but a slight randomization is added to fight visible repetition: each object receives its own offset and, optionally, a small rotation. This variation helps prevent the texture from looking identical across repeated meshes.

If the geometry is a single mesh (not instances) and variation is needed within it, each element can be packed into different texture pixels and given unique offsets to achieve local shifts.

Case 3. Modular Blocking with Seamless Alignment

The video below shows a setup for modular walls with a continuous pattern. This version uses triplanar mapping in Local Space via TransformVector. The alignment pivot (projection anchor) is passed through Custom Primitive Data, allowing textures to be offset for individual groups of elements without disrupting the seamlessness within the group.

With these tools in place, triplanar shading becomes predictable, consistent, and fully controllable across both textures and normals. I hope this breakdown helps streamline your workflow and provides a solid reference for anyone working with materials in UE5.

The full version of Material Functions can be downloaded here:

Ready to grow your game’s revenue?
Talk to us

Comments

3

arrow
  • Ledeneva Alina
    5 hours ago
    0
  • A Jim
    5 hours ago
    0
Show 1 comments
Leave Comment
Ready to grow your game’s revenue?
Talk to us

You might also like

We need your consent

We use cookies on this website to make your browsing experience better. By using the site you agree to our use of cookies.Learn more