Skip to main content
2 of 5
added 246 characters in body
Basic
  • 1.3k
  • 10
  • 27

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

When texturing a mesh 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...

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; } 

A separate technique I've used elsewhere is to 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"]

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, you the detail is clearly visible, whilst at longer range, the lower frequency features -that span across tiles- become more visible.

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

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.

Basic
  • 1.3k
  • 10
  • 27