My shader generates height maps for terrain chunks, can it calculate slope as well?

I’m completely new to shaders.
I have a noise-generating frag shader that I’m using to create height maps for terrains. Noise is calculated by pixel location + offset. The noise value fills the Red channel: float4(noise value, 1, 1, 1)

Is it possible for me to pass the shader the height value of the terrain (ex: 1000f) so it can also calculate the slope for each point between 0…1? Right now I just blit a full RGBAFloat png and would like to use another color channel to carry the slope calculations.
Something like: float4(noise value, slope value, 1, 1)

Current Slope Method:
I’m calculating the slope in a C# script that finds the delta of each heightmap pixel’s cardinal neighbors * terrain height, turning it into a vector3(dx, 2f, dy), normalizing it, grabbing the Vector3.Angle(deltaV3, vector3.up), and then dividing the results by 90f to get a slope value between 0…1. The script is far too slow, even when threaded, and doesn’t handle edge cases well.

More info:
I’m using a modified shader from this asset to create heightmaps for terrains. The shader has been converted to a frag shader, as discussed in the last page or so of the thread.

I can attach the shader if needed.

Looks like you need the derivative of whatever noise function you have, try this

The shader I’m using is producing ridge multifractal, so I’m assuming I need to look at the Fbm derivatives halfway down the page you linked. I can’t make heads or tails of it.
His code:

// returns 3D fbm and its 3 derivatives
vec4 fbm( in vec3 x, int octaves )
{
    float f = 1.98;  // could be 2.0
    float s = 0.49;  // could be 0.5
    float a = 0.0;
    float b = 0.5;
    vec3  d = vec3(0.0);
    mat3  m = mat3(1.0,0.0,0.0,
                   0.0,1.0,0.0,
                   0.0,0.0,1.0);
    for( int i=0; i < octaves; i++ )
    {
        vec4 n = noised(x);
        a += b*n.x;          // accumulate values      
        d += b*m*n.yzw;      // accumulate derivatives
        b *= s;
        x = f*m3*x;
        m = f*m3i*m;
    }
    return vec4( a, d );
}

Ridged multifractal is just regular perlin with a abs() I think?
So aside from the zero value it should the reversed slope?

Since the noise generation will only happen once per world save, I think I’ll just brute it the way I described above. Naturally I assumed I could ±1 or ±1/width the texcoords to get the neighboring samples, but all attempts failed. How exactly do I access the neighbors in a procedural shader? The texture2d images are created at runtime with a resolution of 129 x 129, but the resolution could change.

Current Shader:

Shader "Modified3DPerlinFrag" {

    Properties {
        _Octaves     ("Octaves"    , Float)  = 8
        _Frequency   ("Frequency"  , Float)  = 1
        _Amplitude   ("Amplitude"  , Float)  = 1
        _Lacunarity  ("Lacunarity" , Float)  = 1.92
        _Persistence ("Persistence", Float)  = 0.8
        _Offset      ("Offset"     , Vector) = (0, 0, 0, 0)
        _RidgeOffset ("Ridge Offset", Float) = 1.0
//        _HeightScalar  ("Height Scalar" , Float)  = 1000
//        _QuadScalar  ("Quad Scalar" , Float)  = 1.8
    }


    SubShader {


        Tags {
            "RenderType"  = "Opaque"
            "PreviewType" = "Plane"
        }
        LOD 100


        Cull     Off
        Lighting Off
        ZWrite   Off
        Fog { Mode Off }
        Blend One Zero


        Pass {

            CGPROGRAM

            #pragma vertex   vert            
            #pragma fragment frag

            struct vertInput {
                float4 pos       : POSITION;
                float2 texcoord  : TEXCOORD0;
//                float4 color     : COLOR;
            }; 

            struct vertOutput {
                float4 pos      : SV_POSITION;
                half2  texcoord    : TEXCOORD0; 
//                fixed4 color    : COLOR;
            };

            vertOutput vert (vertInput input) {
                vertOutput o;

                o.pos      = UnityObjectToClipPos (input.pos);
                o.texcoord = input.texcoord;
//                o.color    = input.color;

                return o;
            }

           void FAST32_hash_3D(     float3 gridcell,
                                out float4 lowz_hash_0,
                                out float4 lowz_hash_1,
                                out float4 lowz_hash_2,
                                out float4 highz_hash_0,
                                out float4 highz_hash_1,
                                out float4 highz_hash_2    )        //    generates 3 random numbers for each of the 8 cell corners
        {

            const float2 OFFSET = float2( 50.0, 161.0 );
            const float DOMAIN = 69.0;
            const float3 SOMELARGEFLOATS = float3( 635.298681, 682.357502, 668.926525 );
            const float3 ZINC = float3( 48.500388, 65.294118, 63.934599 );
       
            //    truncate the domain
            gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN;
            float3 gridcell_inc1 = step( gridcell, float3( DOMAIN - 1.5, DOMAIN - 1.5, DOMAIN - 1.5 ) ) * ( gridcell + 1.0 );
       
            //    calculate the noise
            float4 P = float4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy;
            P *= P;
            P = P.xzxz * P.yyww;
            float3 lowz_mod = float3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell.zzz * ZINC.xyz ) );
            float3 highz_mod = float3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell_inc1.zzz * ZINC.xyz ) );
            lowz_hash_0 = frac( P * lowz_mod.xxxx );
            highz_hash_0 = frac( P * highz_mod.xxxx );
            lowz_hash_1 = frac( P * lowz_mod.yyyy );
            highz_hash_1 = frac( P * highz_mod.yyyy );
            lowz_hash_2 = frac( P * lowz_mod.zzzz );
            highz_hash_2 = frac( P * highz_mod.zzzz );
        }

        float3 Interpolation_C2( float3 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }

        float Perlin3D( float3 P )
        {
            //    establish our grid cell and unit position
            float3 Pi = floor(P);
            float3 Pf = P - Pi;
            float3 Pf_min1 = Pf - 1.0;

            float4 hashx0, hashy0, hashz0, hashx1, hashy1, hashz1;
            FAST32_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );
       
            //    calculate the gradients
            float4 grad_x0 = hashx0 - 0.49999;
            float4 grad_y0 = hashy0 - 0.49999;
            float4 grad_z0 = hashz0 - 0.49999;
            float4 grad_x1 = hashx1 - 0.49999;
            float4 grad_y1 = hashy1 - 0.49999;
            float4 grad_z1 = hashz1 - 0.49999;
            float4 grad_results_0 = rsqrt( grad_x0 * grad_x0 + grad_y0 * grad_y0 + grad_z0 * grad_z0 ) * ( float2( Pf.x, Pf_min1.x ).xyxy * grad_x0 + float2( Pf.y, Pf_min1.y ).xxyy * grad_y0 + Pf.zzzz * grad_z0 );
            float4 grad_results_1 = rsqrt( grad_x1 * grad_x1 + grad_y1 * grad_y1 + grad_z1 * grad_z1 ) * ( float2( Pf.x, Pf_min1.x ).xyxy * grad_x1 + float2( Pf.y, Pf_min1.y ).xxyy * grad_y1 + Pf_min1.zzzz * grad_z1 );
       
            //    Classic Perlin Interpolation
            float3 blend = Interpolation_C2( Pf );
            float4 res0 = lerp( grad_results_0, grad_results_1, blend.z );
            float2 res1 = lerp( res0.xy, res0.zw, blend.y );
            float final = lerp( res1.x, res1.y, blend.x );
            final *= 1.1547005383792515290182975610039;        //    (optionally) scale things to a strict -1.0->1.0 range    *= 1.0/sqrt(0.75)
            return final;
        }

        float PerlinRidged(float3 p, int octaves, float3 offset, float frequency, float amplitude, float lacunarity, float persistence, float ridgeOffset)
        {
            float sum = 0;
            for (int i = 0; i < octaves; i++)
            {
                float h = 0;
                h = 0.5 * (ridgeOffset - abs(4*Perlin3D((p + offset) * frequency)));
                sum += h*amplitude;
                frequency *= lacunarity;
                amplitude *= persistence;
            }
            return sum;
        }

            fixed  _Octaves;
            float  _Frequency;
            float  _Amplitude;
            float  _Lacunarity;
            float  _Persistence;
            float3 _Offset;
            float  _CutoutThreshold;
            float  _RidgeOffset;
            float  _HeightScalar;
            float  _QuadScalar;

            half4 frag (vertOutput output) : COLOR {
                float ResultHeight = PerlinRidged (float3 (output.texcoord.x, output.texcoord.y, 0), _Octaves, _Offset, _Frequency, _Amplitude, _Lacunarity, _Persistence, _RidgeOffset);

                 ResultHeight = ResultHeight * 0.5;// + 0.5;
                 return float4 (ResultHeight, 0, 0, 1);
            }

            ENDCG
        }
    }
}