Skip to main content
added 90 characters in body
Source Link
Basic
  • 1.3k
  • 10
  • 27

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...

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

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" 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.

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

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

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" 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.

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...

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; } 
edited body
Source Link
Basic
  • 1.3k
  • 10
  • 27

[You(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 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"]Function" which mentions other ways to break up repetition like warping coordinates for higher octaves)

That way, when you're up close, you 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 acrossmultiple tiles- become more visibletend to dominate.

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.

[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"]

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.

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.

(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)

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.

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.

added 246 characters in body
Source Link
Basic
  • 1.3k
  • 10
  • 27

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.

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

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.

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

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.

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; } 
added 246 characters in body
Source Link
Basic
  • 1.3k
  • 10
  • 27
Loading
Source Link
Basic
  • 1.3k
  • 10
  • 27
Loading