Help creating world-space cross-section shader?

I’m working on a game where a 3D terrain can be deformed by the player (digging tunnels, etc). I have an art style in mind for the terrain, but I’m not sure how to create it (or even if shaders are the solution).

I want the terrain to have different colored-layers as the player gets deeper. I also want these layers to be thick sine waves in shape, instead of just being flat. For example, if a perfect sphere were dug out from the dirt, it should look like this:

I want to be able to define the equations for the colored-layers (for example, maybe some levels will have a saw-tooth layering, some might have sine wave layering, etc).

I am very new to shaders, so I don’t really know where to start, or what this shading technique might be called. Does anyone know how I could achieve the described effect?

Shaders would be easily capable of this, though you’ll want to be careful with your equations as, if each layer has their own, that can get expensive quickly with a lot of layers.

The short version is you get the world position of the pixel being drawn, take your equation and apply it to the worldPos.xz components of the position, and then test the resulting value against the worldPos.z.

4082044--356140--upload_2019-1-9_15-8-25.png

Shader "Custom/GroundLayers" {
    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.0

        [Header(Base Layer)]
        _Color ("Top Color", Color) = (0.8,0.6,0.35,1)

        [Header(Layer 1)]
        _Color1 ("Color", Color) = (0.65,0,0,1)
        _LayerHeight1 ("Layer Height", Float) = 0.165
        _WaveScale1 ("Wave Height Scale", Float) = 0.025
        _WaveScaleOffset1 ("Wave Frequency and Horizontal Offset", Vector) = (20, 20, 0, 0.5)

        [Header(Layer 2)]
        _Color2 ("Color", Color) = (0.5,0.5,0.5,1)
        _LayerHeight2 ("Layer Height", Float) = -0.165
        _WaveScale2 ("Wave Height Scale", Float) = 0.025
        _WaveScaleOffset2 ("Wave Frequency and Horizontal Offset", Vector) = (19, 19, 0.33, 0.15)
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
            float3 worldPos;
        };

        half _Glossiness;

        fixed3 _Color, _Color1, _Color2;
        float4 _WaveScaleOffset1, _WaveScaleOffset2;
        float _WaveScale1, _WaveScale2, _LayerHeight1, _LayerHeight2;

        float calcLayerMask(float3 worldPos, float4 waveScaleOffset, float waveHeightScale, float layerHeight)
        {
            // scale & offset the xz world position
            float2 scaledWorldPlane = worldPos.xz * waveScaleOffset.xy + waveScaleOffset.zw;

            // calculate wave pattern from 2D position
            float waveHeight = sin(scaledWorldPlane.x) + cos(scaledWorldPlane.y);

            // scale wave pattern and apply world height offset
            waveHeight *= waveHeightScale;
            waveHeight += layerHeight;

            // screen space anti-aliasing
            float layerHeightDerive = fwidth(waveHeight) * 1.5;
            return smoothstep(worldPos.y - layerHeightDerive, worldPos.y + layerHeightDerive, waveHeight);
        }

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // lerp between base color and first layer's color
            fixed3 layerColor = lerp(_Color, _Color1, calcLayerMask(IN.worldPos, _WaveScaleOffset1, _WaveScale1, _LayerHeight1));

            // lerp between previous layers' colors and second layer's color
            layerColor = lerp(layerColor, _Color2, calcLayerMask(IN.worldPos, _WaveScaleOffset2, _WaveScale2, _LayerHeight2));

            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);

            o.Albedo = c.rgb * layerColor;
            o.Smoothness = _Glossiness;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

This uses the same sine & cosine based wave pattern equation for both layers for simplicity sake. Being able to choose what pattern to use for each layer, or having more layers, gets more difficult, but is possible to a degree. You’d need to have every equation you want to use in the shader itself and choose which one to use based on another material property. If you want to have dynamic control over the number of layers you’d need to pass in the layer parameters as an array of values, which means you’ll need to do this from a custom script as you can’t have arrays as material properties (those in the Properties at the top of the shader file, that show up in the inspector and are saved on the material asset), but can set array values on materials from a custom script.

Wow! That looks incredible! Exactly what I was looking for. Thanks very much, I will try this out!