2
\$\begingroup\$

I'm writing an rts-style game, so I have an overhead camera, typically 20-50m above the terrain.

Most ground textures (grass, dirt, etc.) I find are obviously made for a much closer, probably FPS-style, perspective. Using them for an RTS game you either get lots of tiling or you scale them up and when you get close you see individual grass blades way too large to fit the rest of the scenery.

Looking at AAA RTS titles, the grass seems to be mostly a greenish noise texture with both small and large scale color variations, much like perlin noise works.

How is that done? Is it a texture or a procedural system? Are there sources for rts-style terrain textures? Do you simply high-pass filter your textures? Do you mix in a detail map with the regular texture? What's the approach?

My engine is Unity 3D, though I don't think the question is engine-specific.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ Every game has it's own approach. In my RTS I'm using Substance Designer procedural generated textures that are detailed enough and a macro texture to break the monotony on zoom-out. \$\endgroup\$
    – Kromster
    Nov 8, 2021 at 11:22
  • \$\begingroup\$ @Kromster so the macro texture is scaled large and the detail textures are scaled small and you blend them together? \$\endgroup\$
    – Tom
    Nov 8, 2021 at 11:25
  • 2
    \$\begingroup\$ I used a custom tri-planar shader which blended multiple copies of each texture at different scales. When zoomed out, the larger copies help obscure the tiling pattern, but up close when looking at the more detail-scaled, the large scale variations are barely distinguishable. \$\endgroup\$
    – Basic
    Nov 8, 2021 at 11:33
  • \$\begingroup\$ @Basic that sounds much like what MicroSplat's Texture Clusters do. I was looking for something like that - a simple solution to a simple problem. Can you elaborate? \$\endgroup\$
    – Tom
    Nov 8, 2021 at 13:38
  • 1
    \$\begingroup\$ maybe you can fix tiling problem with this way iquilezles.org/www/articles/texturerepetition/… \$\endgroup\$
    – Emre Kaya
    Nov 9, 2021 at 7:53

1 Answer 1

2
\$\begingroup\$

Apply multiple "octaves" of textures/noise to avoid repetition.

(You can read more about a related technique as used for generating procedural terrains here: https://developer.nvidia.com/gpugems/gpugems3/part-i-geometry/chapter-1-generating-complex-procedural-terrains-using-gpu - particularly "1.3.3 Making an Interesting Density Function" which mentions other ways to break up repetition like warping coordinates for higher octaves)

But the key takeaway is that you blend multiple copies of the (texture|noise) at different scales with varying weights.

As an example, you might pick (pseudocode)

color = texture_at(x, y) * .6
      + texture_at(x/5, y/5) * .2
      + texture_at(x/10, y/10) * .1
      + texture_at(x/50, y/50) * .1;

That way, when you're up close, the detail is clearly visible and the lower amplitude blurs of lower octaves are barely visible, whilst at longer range, the lower frequency features -that span multiple tiles- tend to dominate.

Obviously you can tweak the number of samples and relative weighting to get the effect you want.

A separate technique I've used elsewhere is to use the normal of the surface to blend between three textures [Triplanar texturing].

This was quite some time ago, so excuse the vagueness but...

When texturing a mesh-based procedural terrain, I used a custom shader which calculated the normal vector of the surface in world space and used the relative weight of the x, y and z vectors to blend between three textures specified by the user.

This allowed me to specify textures for a grass floor, a rock wall and a cave roof.

I managed to dig out this shader code but please bear in mind this is one of the first things I did in Unity, I'm no shader expert and I haven't reviewed it in detail...

Properties{
    //_MainTex ("Albedo (RGB)", 2D) = "white" {}
    _TopWeighting("Vertical Falloff", Range(0.1, 10)) = 5
    _UpTex("Top", 2D) = "red" {}
    _SideTex("Side", 2D) = "green" {}
    _DownTex("Bottom", 2D) = "blue" {}
}

// ....

struct Input {
    float3 worldPos : SV_POSITION;
    float3 worldNormal;
    float3 pos;
};

void vert(inout appdata_full v, out Input o) {
    UNITY_INITIALIZE_OUTPUT(Input, o);
    o.pos = mul(_Object2World, v.vertex);
}

// ....

void surf(Input IN, inout SurfaceOutputStandard o) {
    // Sample the various textures we're using
    
    fixed4 up = tex2D(_UpTex, fmod(IN.pos.xz * _UpTex_ST, 1));
    fixed4 sidex = tex2D(_SideTex, IN.pos.yz * _SideTex_ST);
    fixed4 sidez = tex2D(_SideTex, IN.pos.xy * _SideTex_ST);
    fixed4 down = tex2D(_DownTex, IN.pos.xz * _DownTex_ST);

    // Work out if we're doing the top or bottom
    float top = clamp(IN.worldNormal.y * 100000, 0, 1);

    // Work out how much weight should be given to the various textures
    float3 blending = abs(float3(IN.worldNormal.x / _TopWeighting,
                                 IN.worldNormal.y,
                                 IN.worldNormal.z / _TopWeighting));

    // Force weights to sum to 1.0
    blending = normalize(max(blending, 0.00001)); 

    // scale the various weights proportionally
    float b = (blending.x + blending.y + blending.z);
    blending /= float3(b, b, b);

    // Combine the textures with the appropriate weighting
    float4 tex = sidex * blending.x +
                 (top) * up * blending.y +
                 (1-top) * down * blending.y +
                 sidez * blending.z;
    o.Albedo = tex;

    // Metallic and smoothness aren't currently used,
    // set them to some neutral values

    o.Metallic = 0;
    o.Smoothness = .1;
    o.Alpha = 0;
}

You should be able to combine the two techniques fairly easily by updating the colour sampling at the top of the surface shader, hopefully giving you some more varied terrain.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ +1. But I would de-prioritize (or even remove) the part about the normal mapping as it was not in the question. Multi-octave approach is good. I would also add a bit about possibility of blending using camera distance as input too. \$\endgroup\$
    – Kromster
    Nov 9, 2021 at 5:21
  • \$\begingroup\$ @Kromster actually, normal mapping seems to be key. Several commercial anti-tiling tools including MicroSplat are using it prominently. \$\endgroup\$
    – Tom
    Nov 9, 2021 at 5:32
  • \$\begingroup\$ @Tom okay. I'm just pointing out that this was not mentioned in the question :-) \$\endgroup\$
    – Kromster
    Nov 9, 2021 at 6:51
  • 1
    \$\begingroup\$ @Kromster Good point. Done. \$\endgroup\$
    – Basic
    Nov 11, 2021 at 23:23
  • 1
    \$\begingroup\$ Accepting this as the best answer, because my actual solution was to use Microsplat and it's stochastic anti-tiling feature, which does something roughly similar. \$\endgroup\$
    – Tom
    Aug 7, 2022 at 20:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .