Quick answer: Landscape materials use planar UV projection from the top down by default. When the terrain becomes steep, the texture is projected at a shallow angle, causing it to stretch along the slope face. The steeper the surface, the worse the distortion becomes because fewer texels cover more surface area.

Here is how to fix Unreal landscape material stretching slopes. You have painted a beautiful landscape in Unreal Engine, applied your grass and dirt materials, and everything looks great on flat ground. Then you sculpt a hillside or cliff face and the textures smear into an unrecognizable mess of stretched pixels. This is one of the most common landscape material problems and it has several reliable solutions depending on your performance budget and visual requirements.

The Symptom

Landscape material layers look correct on flat or gently sloping terrain but become visibly stretched on steep slopes. The texture appears to be pulled vertically along the cliff face, losing all detail and appearing blurry or smeared. The stretching gets worse as the slope approaches vertical. In some cases, you can see the individual texels of the texture stretched across meters of surface area.

This happens regardless of which layer you paint. Grass, rock, dirt — all of them stretch on steep faces. Increasing the texture resolution does not help because the problem is with the UV projection, not the texture quality. Tiling the texture more aggressively makes the flat areas look noisy while doing nothing for the stretched slopes.

What Causes This

1. Default landscape UVs project from the top down. Unreal Engine's landscape system generates UV coordinates by projecting from the world Z axis downward. On flat ground, this projection is perpendicular to the surface, giving a clean 1:1 texel-to-surface ratio. On steep slopes, the projection hits the surface at a grazing angle, spreading each texel across a much larger area. A surface approaching 90 degrees receives almost no UV coverage from a top-down projection.

2. Standard texture sampling uses a single projection axis. The default landscape material setup samples textures using the landscape's built-in UV channel, which is a single planar projection. There is no mechanism to detect slope angle and compensate. Every pixel on the landscape uses the same top-down UV regardless of surface orientation.

3. Landscape Layer Blend nodes do not account for slope by default. When using LandscapeLayerBlend nodes, the blend weights are entirely determined by what you paint in the landscape editor. There is no automatic slope detection or angle-based blending built into the standard layer blend. If you paint grass on a cliff, it will render grass on that cliff — stretched.

The Fix

Step 1: Implement tri-planar mapping in your landscape material. Tri-planar mapping projects the texture from all three world axes and blends between them based on surface normal direction. This eliminates stretching on any surface angle. Here is a utility component that computes tri-planar projection weights and UVs:

// TriPlanarLandscapeComponent.h
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "TriPlanarLandscapeComponent.generated.h"

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UTriPlanarLandscapeComponent : public UActorComponent
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, Category = "TriPlanar")
    float TextureScale = 0.01f;

    UPROPERTY(EditAnywhere, Category = "TriPlanar")
    float BlendSharpness = 8.0f;

    UFUNCTION(BlueprintCallable, Category = "TriPlanar")
    FVector ComputeTriPlanarWeights(FVector WorldNormal);

    UFUNCTION(BlueprintCallable, Category = "TriPlanar")
    FVector2D GetProjectionUV_X(FVector WorldPos);

    UFUNCTION(BlueprintCallable, Category = "TriPlanar")
    FVector2D GetProjectionUV_Y(FVector WorldPos);

    UFUNCTION(BlueprintCallable, Category = "TriPlanar")
    FVector2D GetProjectionUV_Z(FVector WorldPos);
};
// TriPlanarLandscapeComponent.cpp
#include "TriPlanarLandscapeComponent.h"

FVector UTriPlanarLandscapeComponent::ComputeTriPlanarWeights(
    FVector WorldNormal)
{
    // Compute blend weights from the absolute normal
    FVector Weights;
    Weights.X = FMath::Pow(
        FMath::Abs(WorldNormal.X), BlendSharpness);
    Weights.Y = FMath::Pow(
        FMath::Abs(WorldNormal.Y), BlendSharpness);
    Weights.Z = FMath::Pow(
        FMath::Abs(WorldNormal.Z), BlendSharpness);

    // Normalize so weights sum to 1.0
    float Sum = Weights.X + Weights.Y + Weights.Z;
    if (Sum > 0.0f)
    {
        Weights /= Sum;
    }
    return Weights;
}

FVector2D UTriPlanarLandscapeComponent::GetProjectionUV_X(
    FVector WorldPos)
{
    // Project from X axis: use Y and Z
    return FVector2D(
        WorldPos.Y * TextureScale,
        WorldPos.Z * TextureScale);
}

FVector2D UTriPlanarLandscapeComponent::GetProjectionUV_Y(
    FVector WorldPos)
{
    // Project from Y axis: use X and Z
    return FVector2D(
        WorldPos.X * TextureScale,
        WorldPos.Z * TextureScale);
}

FVector2D UTriPlanarLandscapeComponent::GetProjectionUV_Z(
    FVector WorldPos)
{
    // Project from Z axis: use X and Y (default top-down)
    return FVector2D(
        WorldPos.X * TextureScale,
        WorldPos.Y * TextureScale);
}

In the material editor, you would sample your texture three times using the UV coordinates from each axis projection, then blend the results using the computed weights. The BlendSharpness parameter controls how quickly the blend transitions between projections. Higher values create sharper transitions, lower values create smoother blends but may show ghosting where projections overlap.

Step 2: Use the WorldAlignedTexture material function. Unreal Engine ships with a built-in WorldAlignedTexture material function that implements tri-planar mapping. You can use it directly in your landscape material instead of building the logic from scratch. However, if you need custom control over blend sharpness or want to apply it selectively per layer, the manual approach above gives you more flexibility.

Step 3: Add slope-based automatic layer blending. Rather than fixing stretching on every layer, you can automatically blend a cliff or rock material onto steep surfaces. This approach uses the world normal to detect slope angle and overrides the painted layer with an appropriate material:

// SlopeBlendHelper.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "SlopeBlendHelper.generated.h"

UCLASS()
class USlopeBlendHelper : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    // Returns 0.0 for flat ground, 1.0 for vertical cliff
    UFUNCTION(BlueprintPure, Category = "Landscape")
    static float GetSlopeBlendAlpha(
        FVector WorldNormal,
        float SlopeThreshold = 0.7f,
        float BlendRange = 0.1f);

    UFUNCTION(BlueprintPure, Category = "Landscape")
    static bool IsSteepSlope(
        FVector WorldNormal,
        float MaxSlopeAngleDegrees = 45.0f);
};
// SlopeBlendHelper.cpp
#include "SlopeBlendHelper.h"

float USlopeBlendHelper::GetSlopeBlendAlpha(
    FVector WorldNormal,
    float SlopeThreshold,
    float BlendRange)
{
    // WorldNormal.Z is 1.0 on flat ground, 0.0 on vertical
    float Slope = 1.0f - FMath::Abs(WorldNormal.Z);

    // Smooth blend between threshold and threshold + range
    float Alpha = FMath::SmoothStep(
        SlopeThreshold - BlendRange,
        SlopeThreshold + BlendRange,
        Slope);

    return FMath::Clamp(Alpha, 0.0f, 1.0f);
}

bool USlopeBlendHelper::IsSteepSlope(
    FVector WorldNormal,
    float MaxSlopeAngleDegrees)
{
    float DotUp = FMath::Abs(WorldNormal.Z);
    float ThresholdDot = FMath::Cos(
        FMath::DegreesToRadians(MaxSlopeAngleDegrees));
    return DotUp < ThresholdDot;
}

In your material graph, use VertexNormalWS to get the world-space normal of the landscape surface. Compare the Z component against your threshold to generate a blend alpha, then use a Lerp to blend between your painted layer result and a cliff rock texture. This approach is additive — it does not replace your painted layers but overlays rock on steep areas automatically.

Related Issues

If your landscape material compiles but the texture appears at the wrong scale after switching to world-aligned projection, check that your TextureScale parameter accounts for Unreal's centimeter unit scale. A scale of 0.01 maps one texture repeat to every 100 units (1 meter). If textures appear to tile differently on landscape components that were imported versus sculpted, verify that all components share the same section size and overall resolution.

If you see visible seams between tri-planar projection axes, increase the BlendSharpness value or add a slight overlap region. If performance is a concern, apply tri-planar mapping only to layers that actually appear on steep slopes (like rock or cliff layers) and keep standard UV projection for flat-only layers like grass or sand.

Tri-planar mapping costs 3x texture samples per layer. Use it only where stretching actually occurs if your material is already hitting instruction limits.