USW projects

Unreal Engine is a perfect toy to have some quality hobby time :slight_smile:
Figured I’d start posting some small projects with hints that might be helpful to the community.

1. Turn in place using distance curves


First you need to see these talks:

Also, as I was a complete animation newbie before I started this toy project, I have got a lot of information from “Animation Blueprint Setup & Walkthrough” live training series with Jay Hosfelt. Here is the link for the first video:

Make actor follow control rotation. Basically, you back out rotation using ‘Rotate Root Bone’ node and use distance curve to look up angle and to scrub animation.

Some more notes:
You would want to do control in C++, blueprint spaghetti would be too much. Also, I see now why Epic is not releasing Paragon nodes, they’re most likely too custom to be made generic anim nodes.
If you can, have only a two step turn animation for 90 degrees. For 180 have 2 step most of the arc and then tiny step in the end. My animations are 3 and 4 step respectively and foot sliding on small turn angles is unavoidable. With 2 step animations you can have feet perfectly planted with no sliding.
If you use motion builder, put DistanceCurve on root bone as integer custom parameter and do ‘save as’ for your fbx - for some reason if you ‘export motion file’ it wipes out custom parameters.
When accessing animation curves, use EvaluateCurveData method on UAnimSequence instead of accessing raw curve data. When packaged, raw data becomes not accessible due to compression.

2. Stop location prediction

You don’t really need iterative method to predict stop location.
If ground friction is zero, solving simple kinematic equation is enough: d = (Vf^2 - Vi^2) / 2a where Vf - final velocity - BRAKE_TO_STOP_VELOCITY, Vi - initial velocity, a= - braking deceleration
With ground friction, you need to integrate V/a(V)dV where a(V) = V*Fr + BD where Fr - ground friction, BD - braking deceleration

Note that internally Epic uses BRAKE_TO_STOP_VELOCITY on character movement component so use this as final velocity and not zero. Otherwise location prediction will be a bit off.

Sample code:

float Friction = GetCharacterMovement()->GroundFriction * GetCharacterMovement()->BrakingFrictionFactor;
float BrakingDeceleration = GetCharacterMovement()->BrakingDecelerationWalking;
FVector Velocity = GetVelocity();
StopLocation = GetActorLocation();

if (Friction == 0.f)
    // No friction - simple kinematic equation
    float Distance = (Velocity.SizeSquared() - UCharacterMovementComponent::BRAKE_TO_STOP_VELOCITY * UCharacterMovementComponent::BRAKE_TO_STOP_VELOCITY) / (2.f * BrakingDeceleration);
    StopLocation += Velocity.GetSafeNormal() * Distance;
    // Integrate
    float Distance = BrakingDeceleration * FMath::Loge(BrakingDeceleration + UCharacterMovementComponent::BRAKE_TO_STOP_VELOCITY * Friction) / (Friction * Friction) - (UCharacterMovementComponent::BRAKE_TO_STOP_VELOCITY / Friction);
    Distance -= BrakingDeceleration * FMath::Loge(BrakingDeceleration + Velocity.Size() * Friction) / (Friction * Friction) - (Velocity.Size() / Friction);
    StopLocation += Velocity.GetSafeNormal() * Distance;

3. Locomotion

So in typical fashion what starts as a weekend exercise ends up being year long on and off project. But finally: curve based locomotion start, stop prediction, curve based turn in place, orientation warping with procedural lean, curve based jump start, soft and hard landing, feet IK.
Was very interesting and satisfying to figure out. There is no speed or slope warping. Maybe some day.

huge work keap up

4. Fast tri-planar mapping

While building my landscape material by deconstructing wonderful work from Joe Garth (Brushify), tri-planar mapping got me thinking. Common approach is to use distance blends for global-macro-detail maps for example. But for slope blending this causes a bit of performance hit since you need to do tri-planar mapping. One can look up tri-planar mapping implementation in WorldAlignedTexture node and see that it uses 3 texture samplers. So having 3 blends each using 3 samplers and having all this for normals as well makes things not ideal. This is why I figured brushify auto material uses only top mapping and guards full tri-planar under switch.

There is a way to do tri-planar mapping having only one texture sampler. The only downside is that there are no blends/lerps, so seams are visible. But for slope application this is not really an issue due to usual type of the textures (rock/cliff) which hide seams pretty well. Especially at a distance.

See below 3 screens, I disabled normal mapping and noise to check how bad seams would look.

Fast tri-planar implementation, checker map

Fast tri-planar

True tri-planar, 3 samplers with lerps

As you can see results are pretty good and seams are not really noticeable. Also see the difference in shader complexity. Of course I shouldn’t compare shade of green in editor viewport and just do performance profile for real numbers. But this comparison will do for now. Left part: fast tri-planar mapping is used for global and macro texture blends, regular tri-planar is used for detail blend. Right part: all three blends use regular tri-planar mapping.

Below is the fast tri-planar implementation.

You can see blending in action. Global/macro - fast tri-planar, detail - regular tri-planar.


i like it ---- u remember me with this one

Yup, I used this one as reference.

1 Like


So the new forum broke the images in previous posts and I see no way to edit them. No matter, moving on to the grass. I have an aging rig with 1070 and arbitrary decided that I want to try and fit my grass into 10ms tops on 2k resolution. Honestly, I was surprised with results. UE4 has come long ways…

I decided to use assets and material from Project Nature mainly because they have nice implementation for wind and interaction using Pivot Painter 2. Not only this looks very natural but also extremely performant. I also suggest watching Epic “From Ants To Outer Space” video (From Ants To Outer Space | Inside Unreal - YouTube) for foliage advice. I took foliage sinking trick from there as well as generic insights since I had no experience with foliage before.

For grass I used landscape procedural grass node. In project settings I set ‘Early Z-pass’ to ‘Opaque and masked meshes’ and switched on ‘Mask material only in early Z-pass’

I used 3 varieties for grass. Most dense set has the shortest cull distance, with density decreasing for others. Two closest sets use mesh sinking by setting cull start-stop values, last set has hard cull line. Here it is 9000. Along with this I set up grass LODs. I used 4 here and set the last LOD for 2% reduction at 0.01 screen size. Now balancing the above (cull distances on grass type vs lods) took quite some time, but the most performance gain is there. So it is important to find that sweet spot when grass looks good, does not ‘pop’, has good coverage and there is no significant overdraw. See sample screens below.

For material I used Project Nature one for wind/bending implementation but added fuzzy shader because I like its effect better. Material is very performant:

However, I still created simplified version of it without any world position offsets or subsurface effects and assigned to last 2 lods of my grass meshes. This shaved around 2-3ms.
I also reimplemented foliage-actor interaction (bending), but not because of performance in shader but rather for different use model.

In my project I use full dynamic lighting. Currently foliage generated with procedural landscape grass node does not support DFAO or DF shadows so for ‘grounding’ we must use cascaded dynamic shadows. Here I have distance of 6000 and 4 cascades. I tweaked exponent and transition fraction here to achieve best looking result for shadow transition. Unfortunately it is still very noticeable, but still surprisingly better than I anticipated.

Procedural grass here also uses 6 more varieties for flowers. But the last 2 islands of flowers are hand-painted. Those are usually clustered and need manual control anyway.

So in the end, I managed to get 5-10ms for my grass. This engine never ceases to amaze.

For actor interaction my implementation uses wobble marker that is trailing the actor with some drag and has delay on wobble power decay. It is a bit better than to look at actor speed as we want foliage to wobble a bit after actor has already stopped.

1 Like


A small hint for those who are implementing time-of-day. Having sun at zenith right above your head is not ideal for artistic purposes as all normal maps wash out, so we want to add some latitude. Sounds simple as one would add some north correction to X, set Z to latitude and start sweeping Y. Unfortunately transform suffers gimbal lock and you can’t just do setActorRotation with rotator from Euler angles. Instead you need to use quaternions. Sample code below.

float angle;
if (ProgradeRotation)
	angle = CurrentTime / (DayLength * 3600.f) * 360.f + 90.f;
	angle = -CurrentTime / (DayLength * 3600.f) * 360.f + 90.f;
FQuat SunRotation = FQuat(FRotator(angle, NorthDirection, 0.f)) * FQuat(FRotator(0.f, -Latitude, 0.f));


While messing around and moving sun around I realized another thing. Don’t derive specular from roughness or do any fancy logic with them. Ditch the magic numbers if you have them. Keep specular at 0.5 or just unplug it. For roughness have simple scaler for maps or min/max and lerp if you use red channel of the albedo. The reason is that it is better to stay as close to PBR as possible. I have made my scene look nice with some magic number settings but when I changed afternoon to late evening the scene looked pretty bad. When you find yourself going inside the texture settings to compensate for something you did to specular… means you’re in trouble.

Sparkling snow

Spent quite some time on this and I think might be useful for some.
I tried to look up other solutions and even read one paper but came to conclusion that here we have to limit what we fake and be pretty close to real thing. If we completely fake (using PP or emissive etc.) then deepest shadows still sparkle even during the night so completely unusable for open world full dynamic light setups. On the other hand, adding a lot of detail noise to normal map does not give even remotely ok result either. Due to filtering and TemporalAA sparks can be seen only close-up. And surface looks like ■■■■ due to extreme noise.
I decided to actually make ‘crystals’ on my snow surface by aligning select pixels on the normal map so sun light is reflected directly into the camera. Here is the result:

Below is the screen of the material function:

‘Sparks mask / roughness’ - this creates sparks mask. I create a grid of black squares with increasing size the farther away from camera. At the same time I sample a noise texture to mask and leave out random pattern of the squares. Camera movement and rotation contribute to this pseudo-randomness. Noise texture is a bunch of sparsely placed white pixels. No mipmaps, filtering is set to ‘Nearest’.

‘Sparks normal’ - this uses sun direction stored in material parameter collection, and camera vector. These two point from the pixel to the sun and to the camera. So in order to reflect light directly into the camera normal must be sum of these vectors divided by 2. This normal is interpolated into snow normal using above sparks mask.

Sparks mask is multiplied with roughness maps/lerps to have 0’s for ‘crystals’.

Up top this gets masked by landscape layer and so on.