# Simplex Derivatives

Analytical Normals and Tangents

- Add derivative data to noise samples.
- Derive tangents and normals from derivatives.
- Calculate derivatives for simplex noise.
- Create a smooth turbulence variant.

This is the second tutorial in a series about pseudorandom surfaces. In it we will calculate derivatives of simplex noise and use them to generate normal and tangent vectors.

This tutorial is made with Unity 2020.3.35f1.

## Simplex Noise

We can ask Unity to recalculate normal and tangent vectors, but this isn't a perfect approach. Recalculation requires a complete mesh and thus has to wait until all Burst jobs have finished and takes place on the main thread. Also, recalculating tangents requires UV coordinates and takes a long time.

It would be much better if normals and tangents could be generated directly by `SurfaceJob`

. This is possible by relying on the derivatives of the noise function, so we're going to calculate those derivatives ourselves. As simplex noise has the simplest derivatives we'll begin with that noise type.

### Noise Configuration

We'll work our way up from one to three dimensions, and also from the simpler simplex value noise to the more complex simplex gradient noise. Let's reintroduce a noise selection configuration option for this, like we made in the Pseudorandom Noise series.

Create a `SurfaceJobScheduleDelegate`

type for the `SurfaceJob.ScheduleParallel`

method. As it doesn't belong to a specific noise type it must exist outside the generic `SurfaceJob`

so let's put it directly below it in the same file.

public struct SurfaceJob<N> : IJobFor where N : struct, INoise { … } public delegate JobHandle SurfaceJobScheduleDelegate ( Mesh.MeshData meshData, int resolution, Settings settings, SpaceTRS domain, float displacement, JobHandle dependency );

Before we add a second static jobs array to `ProceduralSurface`

refactor rename the `jobs`

array to `meshJobs`

for clarity.

static AdvancedMeshJobScheduleDelegate[] meshJobs = { … };

Then add a two-dimensional array of surface job delegates for all three dimensions of regular simplex and simplex value noise, along with a noise type and dimensions configuration option, matching the apporach used in the Pseudorandom Noise series.

static SurfaceJobScheduleDelegate[,] surfaceJobs = { { SurfaceJob<Simplex1D<Simplex>>.ScheduleParallel, SurfaceJob<Simplex2D<Simplex>>.ScheduleParallel, SurfaceJob<Simplex3D<Simplex>>.ScheduleParallel }, { SurfaceJob<Simplex1D<Value>>.ScheduleParallel, SurfaceJob<Simplex2D<Value>>.ScheduleParallel, SurfaceJob<Simplex3D<Value>>.ScheduleParallel } }; public enum NoiseType { Simplex€, SimplexValue } [SerializeField] NoiseType noiseType; [SerializeField, Range(1, 3)] int dimensions = 1;

Now adjust `GenerateMesh`

so it schedules the configured surface job.

~~//SurfaceJob<Lattice2D<LatticeNormal, Perlin>>.ScheduleParallel(~~surfaceJobs[(int)noiseType, dimensions - 1]( meshData, resolution, noiseSettings, domain, displacement, meshJobs[(int)meshType]( mesh, meshData, resolution, default, new Vector3(0f, Mathf.Abs(displacement)), true ) ).Complete();

## Noise Derivative Data

In order to work with the derivatives of the noise we have to change our noise code so it provides this data along with the noise values.

### Vectorized Noise Sample

Each time we sample the noise function we get a value. Now we also want to get the value of the derivatives of that function. But a single derivative value isn't enough, because our noise function can have up to three dimensions, and thus the noise function could have a derivative is each of those dimensions. So each noise sample must contain four values, all of which are vectorized. We'll introduce a new `Noise.Sample4`

struct type for this, which we put in a new *Noise.Sample* partial class asset file in the *Noise* folder. Begin by only including a field for the sample value that we already provide, simply naming it `v`

for value.

using Unity.Mathematics; using static Unity.Mathematics.math; public static partial class Noise { public struct Sample4 { public float4 v; } }

Let's give it an implicit cast operator from `float4`

to `Sample4`

, simply passing along the value. This makes sense because the derivative of a constant is zero.

public static implicit operator Sample4 (float4 v) => new Sample4 { v = v };

### Interface Adjustment

Now we can upgrade our noise by changing the return type of `INoise.GetNoise4`

to `Sample4`

.

public interface INoise { Sample4 GetNoise4 (float4x3 positions, SmallXXHash4 hash, int frequency); }

We also have to change the implementations of this method to match. I only show this once, but it has to be done for all `INoise`

structs.

[MethodImpl(MethodImplOptions.AggressiveInlining)] public Sample4 GetNoise4(float4x3 positions, SmallXXHash4 hash, int frequency) { … }

This change will result in compiler errors where `GetNoise4`

is invoked. Fix these by accessing the value of the sample.

….GetNoise4(…).v…

### Derivative Data

Let's now expand our `Sample4`

data to also include the derivatives. If there were only a single derivative function then we could suffice with adding a single extra field, which we could name `dv`

, standing for the delta of the value, indicating how fast it changes.

public float4 v, dv;

But because our noise can vary in up to three dimensions there are three such values. We'll name them `dx`

, `dy`

, and `dz`

.

public float4 v, dx, dy, dz;

### Derivative Math

We already know that the derivatives of values can be added just like the values themselves. Mathematically, we can say if `f(x)=g(x)+h(x)` then `f'(x)=g'(x)+h'(x)`.

Thus we have a well-defined addition for our `Sample4`

type, so let's give it a custom addition operator method.

public static Sample4 operator + (Sample4 a, Sample4 b) => new Sample4 { v = a.v + b.v, dx = a.dx + b.dx, dy = a.dy + b.dy, dz = a.dz + b.dz };

And the same goes for subtraction.

public static Sample4 operator - (Sample4 a, Sample4 b) => new Sample4 { v = a.v - b.v, dx = a.dx - b.dx, dy = a.dy - b.dy, dz = a.dz - b.dz };

Scaling also applies to the derivative normally. We can see this mathematically via `2f(x)=f(x)+f(x)` thus `(2f(x))'=f'(x)+f'(x)=2f'(x)`.

So let's add multiplication methods for `Sample4`

and `float4`

operands and the other way around.

public static Sample4 operator * (Sample4 a, float4 b) => new Sample4 { v = a.v * b, dx = a.dx * b, dy = a.dy * b, dz = a.dz * b }; public static Sample4 operator * (float4 a, Sample4 b) => b * a;

And let's also add a method to support division by a `float4`

, because `f(x)/2=1/2f(x)`.

public static Sample4 operator / (Sample4 a, float4 b) => new Sample4 { v = a.v / b, dx = a.dx / b, dy = a.dy / b, dz = a.dz / b };

We won't add an operator for division with a sample, nor for sample-sample multiplication, because those are more complicated and not something that we currently need.

### Fractal Noise Method

We currently have two jobs that both contain code for a fractal noise loop. Let's consolidate them into a single static generic `Noise.GetFractalNoise`

method that returns the fractal sample. Use the relevant code from `SurfaceJob.Execute`

.

using System; using System.Runtime.CompilerServices; … public static partial class Noise { … public interface INoise { Sample4 GetNoise4 (float4x3 positions, SmallXXHash4 hash, int frequency); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Sample4 GetFractalNoise<N> ( float4x3 position, Settings settings ) where N : struct, INoise {~~//float4x3 position = domainTRS.TransformVectors(transpose(positions[i]));~~var hash = SmallXXHash4.Seed(settings.seed); int frequency = settings.frequency; float amplitude = 1f, amplitudeSum = 0f; float4 sum = 0f; for (int o = 0; o < settings.octaves; o++) { sum += amplitude * default(N).GetNoise4(position, hash + o, frequency).v; amplitudeSum += amplitude; frequency *= settings.lacunarity; amplitude *= settings.persistence; }~~//noise[i] = sum / amplitudeSum;~~return sum / amplitudeSum; } … }

`Noise.Job.Execute`

can then be reduced to a single assignment.

public void Execute (int i) => noise[i] = GetFractalNoise<N>( domainTRS.TransformVectors(transpose(positions[i])), settings ).v;

And `SurfaceJob.Execute`

can also be simplified.

public void Execute (int i) { Vertex4 v = vertices[i];~~//…~~float4 noise = GetFractalNoise<N>( domainTRS.TransformVectors(transpose(float3x4( v.v0.position, v.v1.position, v.v2.position, v.v3.position ))), settings ).v * displacement;~~//noise *= displacement;~~v.v0.position.y = noise.x; v.v1.position.y = noise.y; v.v2.position.y = noise.z; v.v3.position.y = noise.w; vertices[i] = v; }

Now we can make `GetFractalNoise`

return a proper sample by changing the type of the sum and accumulating the entire samples.

Sample4 sum = default; for (int o = 0; o < settings.octaves; o++) { sum += amplitude * default(N).GetNoise4(position, hash + o, frequency);~~//.v;~~amplitudeSum += amplitude; frequency *= settings.lacunarity; amplitude *= settings.persistence; }

Adjust the type of `noise`

in `SurfaceJob.Execute`

as well.

Sample4 noise = GetFractalNoise<N>( … ) * displacement;~~//.v * displacement;~~v.v0.position.y = noise.v.x; v.v1.position.y = noise.v.y; v.v2.position.y = noise.v.z; v.v3.position.y = noise.v.w;

### Analytical Tangents

The tangent vectors of the mesh can be derived for the analytical noise derivatives of the X dimension. The X derivative represents the rate of change per unit along X. As we use the noise to offset the Y position this derivative represents the elevation change. Thus we have a 2D vector `t=[[1],[d_x]]` where `d_x` is the X derivative, which we can expand to a tangent vector. Let's initially do this only for the first out of four vertices.

v.v3.position.y = noise.v.w; v.v0.tangent = float4(1f, noise.dx.x, 0f, -1f); vertices[i] = v;

To make this a proper tangent vector it has to be normalized. Explicitly for our vector, `||t||=[[1],[d_x]]1/sqrt(d_x^2+1)`. If we extract the normalizer factor we can directly use it for the X component of the vector.

float4 normalizer = rsqrt(noise.dx * noise.dx + 1f); float4 tangentY = noise.dx * normalizer; v.v0.tangent = float4(normalizer.x, tangentY.x, 0f, -1f);

Expand this approach to set the tangents of all four vertices.

v.v0.tangent = float4(normalizer.x, tangentY.x, 0f, -1f); v.v1.tangent = float4(normalizer.y, tangentY.y, 0f, -1f); v.v2.tangent = float4(normalizer.z, tangentY.z, 0f, -1f); v.v3.tangent = float4(normalizer.w, tangentY.w, 0f, -1f);

If we disable the recalculation options of our procedural mesh we'll now see it use tangent vectors derived from analytical derivatives instead of a mesh-based approximation. But because all derivative data is still zero at this point all tangent vectors will be flat.

## 1D Derivatives

We begin with 1D noise so we only have to worry about a single dimension.

### Simplex Value Noise

Let's initially consider simplex value noise. Because its gradient is a constant value and derivatives can be added, we only have to consider the individual kernel falloff function `f(x)=(1-x^2)^3` scaled by some value that is constant per kernel. Adjust `Simplex1D.Kernel`

so it returns this as a `Sample4`

. Use `(1-x^2)` as the base function and raise it to the third power and scale it when setting the sample value.

static Sample4 Kernel (SmallXXHash4 hash, float4 lx, float4x3 positions) { float4 x = positions.c0 - lx; float4 f = 1f - x * x;~~//f = f * f * f;~~float4 g = default(G).Evaluate(hash, x); return new Sample4 { v = f * f * f * g }; }

By applying the chain rule we find `f'(x)=-6x(1-x^2)^2`. Use that to set the X derivative, also scaling it by the same gradient, which we consider to be constant.

return new Sample4 { v = f * f * f * g, dx = f * f * -6f * x * g };

At this point we again get a compilation error, because `EvaluateCombined`

expects a `float4`

argument. Change the `IGradient.EvaluateCombined`

interface method declaration so it acts on a `Sample4`

instead.

Sample4 EvaluateCombined (Sample4 value);

Adjust all the gradient implementations to match. This is trivial for all gradients except the `Turbulence`

variant.

public Sample4 EvaluateCombined (Sample4 value) => value;

`Turbulence`

takes the absolute value of the noise. We'll deal with this later, simply pass through the unadjusted sample for now.

public Sample4 EvaluateCombined (Sample4 value) =>~~//abs(default(G).EvaluateCombined(value));~~default(G).EvaluateCombined(value);

We finally get to see analytical tangent vectors. However, they are incorrect unless the frequency is set to 1. This happens because the frequency acts like a scalar for the rate of change of the noise. Effectively, if the frequency is set to 4 we're using `f(g(x))` with `g(x)=4x` and have to apply the chain rule.

So to fix this `Simplex1D.GetNoise4`

has to factor the frequency into its derivative.

Sample4 s = default(G).EvaluateCombined( Kernel(hash.Eat(x0), x0, positions) + Kernel(hash.Eat(x1), x1, positions) ); s.dx *= frequency; return s;

Because derivatives can be added the tangents are also correct when multiple octaves of noise are added.

Note that because the analytical tangent vectors are based on the noise and not the mesh they might become weird when the frequency becomes too high relative to the mesh resolution, or when there are too many octaves. Recalculated mesh-based tangents will automatically conform to the mesh surface, but the analytical tangents won't. This shouldn't be a problem because in such cases the noise pattern gets under-sampled anyway and result will be bad.

### Simplex Noise

If we switch to simplex noise the tangents will be wrong again, because we assumed that the gradients are constant, which is only true for simplex value noise.

Gradients have derivatives as well, so change the `IGradient`

interface so all `Evaluate`

methods return `Sample4`

values.

Sample4 Evaluate (SmallXXHash4 hash, float4 x); Sample4 Evaluate (SmallXXHash4 hash, float4 x, float4 y); Sample4 Evaluate (SmallXXHash4 hash, float4 x, float4 y, float4 z);

Adjust all implementations to match and fix all compiler errors by extracting the sample values from the `Evaluate`

invocations.

The 1D simplex gradient forwards its invocation to `BaseGradients.Line`

, only scaling the result. So we'll adjust that method, making it return a `Sample4`

value. Our straight line is simply the function `f(x)=lx` where `l` is some constant that we base on the hash, thus `f'(x)=l`. Adjust `Line`

to return both.

public static Sample4 Line (SmallXXHash4 hash, float4 x) { float4 l = (1f + hash.Floats01A) * select(-1f, 1f, ((uint4)hash & 1 << 8) == 0); return new Sample4 { v = l * x, dx = l }; }

With the gradient no longer constant the function in `Simplex1D.Kernel`

becomes `f(x)=(1-x^2)^3g(x)`. By applying the product rule we find that `f'(x)=(1-x^2)^2((1-x^2)g'(x)-6xg(x))`.

Sample4 g = default(G).Evaluate(hash, x);~~//.v;~~return new Sample4 { v = f * f * f * g.v, dx = f * f * (f * g.dx - 6f * x * g.v) };

We can simplify the code a bit by multiplying the entire sample with `(1-x^2)^2`.

return new Sample4 { v = f * g.v, dx = f * g.dx - 6f * x * g.v } * f * f;

### Analytical Normals

Now that we have correct derivatives for simplex noise let's also use them to generate the normal vectors in `SurfaceJob.Execute`

. Because we're only dealing with one dimension we can currently suffice with rotating the tangent vector 90° counterclockwise.

v.v0.tangent = float4(normalizer.x, tangentY.x, 0f, -1f); v.v1.tangent = float4(normalizer.y, tangentY.y, 0f, -1f); v.v2.tangent = float4(normalizer.z, tangentY.z, 0f, -1f); v.v3.tangent = float4(normalizer.w, tangentY.w, 0f, -1f); v.v0.normal = float3(-v.v0.tangent.y, v.v0.tangent.x, 0f); v.v1.normal = float3(-v.v1.tangent.y, v.v1.tangent.x, 0f); v.v2.normal = float3(-v.v2.tangent.y, v.v2.tangent.x, 0f); v.v3.normal = float3(-v.v3.tangent.y, v.v3.tangent.x, 0f);

There will almost always be a difference between analytical and recalculated normals and tangents, but the higher the resolution of the mesh the more they look alike. The difference is most obvious when under-sampling the noise.

### Domain Rotation

Our analytical normal and tangent vectors should also correctly adapt to domain rotation, but this is currently not the case. This can be verified by applying a 90° domain rotation around the Y axis. The tangents should become flat and the normals should have also rotated, but this doesn't happen.

The noise code is unaware of the transformation. This isn't a problem for translation, because that simply shifts the sample position. But in the case of rotation we have to compensate by applying the inverse rotation to the derivative vector.

To apply a rotation we need a 3×3 matrix, constructed by rotating in the opposite direction and in reverse order of the domain rotation. So we need a negative YXZ rotation. We can construct such a matrix via `float3x3.EulerYXZ`

and let's provide it via a new `SpaceTRS.DerivativeMatrix`

property.

public float3x3 DerivativeMatrix => float3x3.EulerYXZ(-math.radians(rotation));

To apply this matrix to vectorized derivatives add a variant `TransformVectors`

method to `MathExtensions`

that acts on a 3×3 matrix instead of on a 3×4 matrix.

public static float4x3 TransformVectors (this float3x3 m, float4x3 v) => float4x3( m.c0.x * v.c0 + m.c1.x * v.c1 + m.c2.x * v.c2, m.c0.y * v.c0 + m.c1.y * v.c1 + m.c2.y * v.c2, m.c0.z * v.c0 + m.c1.z * v.c1 + m.c2.z * v.c2 );

Let's also add a convenient `Sample4.Derivatives`

property that provides the derivatives as a vectorized `float4x3`

package.

public float4x3 Derivatives => float4x3(dx, dy, dz);

Now add a field for the derivative matrix to `SurfaceJob`

and set it in `ScheduleParallel`

.

float3x3 derivativeMatrix; … public static JobHandle ScheduleParallel (…) => new SurfaceJob<N>() { vertices = meshData.GetVertexData<SingleStream.Stream0>().Reinterpret<Vertex4>(12 * 4), settings = settings, domainTRS = domain.Matrix, derivativeMatrix = domain.DerivativeMatrix, displacement = displacement }.ScheduleParallel(meshData.vertexCount / 4, resolution, dependency);

Then transform the derivative vectors of the noise and use the X components of the result to construct the tangent vectors in `Execute`

.

float4x3 dNoise = derivativeMatrix.TransformVectors(noise.Derivatives); float4 normalizer = rsqrt(dNoise.c0 * dNoise.c0 + 1f); float4 tangentY = dNoise.c0 * normalizer;

This fixed the tangents. To also construct correct normal vectors we have to switch to a 2D approach, because the derivative vector can now point in any direction in the XZ plane. So it becomes `[[-d_x],[1],[-d_z]]1/sqrt(d_x^2+d_z^2+1)`.

normalizer = rsqrt(dNoise.c0 * dNoise.c0 + dNoise.c2 * dNoise.c2 + 1f); float4 normalX = -dNoise.c0 * normalizer; float4 normalZ = -dNoise.c2 * normalizer; v.v0.normal = float3(normalX.x, normalizer.x, normalZ.x); v.v1.normal = float3(normalX.y, normalizer.y, normalZ.y); v.v2.normal = float3(normalX.z, normalizer.z, normalZ.z); v.v3.normal = float3(normalX.w, normalizer.w, normalZ.w);

### Domain Scale

Domain scaling should also affect the tangent and normal vectors. However, before we deal with that we have to first observe that the `float4.TRS`

method doesn't provide the expected transformation. It scales after rotation, so it does TSR instead of TRS. This doesn't matter when scaling is uniform, but nonuniform scaling goes wrong. To make our domain behave the same way as Unity's game object transformation let's create the matrix provided by `SpaceTRS.Matrix`

ourselves.

public float3x4 Matrix { get {~~//float4x4 m = float4x4.TRS(~~~~// translation, quaternion.EulerZXY(math.radians(rotation)), scale~~~~//);~~~~//return math.float3x4(m.c0.xyz, m.c1.xyz, m.c2.xyz, m.c3.xyz);~~float3x3 m = math.mul( float3x3.Scale(scale), float3x3.EulerZXY(math.radians(rotation)) ); return math.float3x4(m.c0, m.c1, m.c2, translation); } }

Now we can observe a combination of nonuniform scaling and rotation, which leads to incorrect analytical tangents and normals.

Scaling this way is effectively changing the frequency in each dimension independently, so we should scale the derivatives by the same amount. We have to swap the order of rotation and scaling to arrive at the final derivative matrix.

public float3x3 DerivativeMatrix => math.mul(float3x3.EulerYXZ(-math.radians(rotation)), float3x3.Scale(scale));

## 2D Derivatives

Because each dimension has its own derivative they can be calculated separately. So we can use the same approach for 2D noise that we used for 1D noise.

### Simplex Value Noise

Adjust `Simplex2D.GetNoise4`

so it also treats the evaluated kernels as a `Sample4`

and appropriately scales its two derivatives.

public Sample4 GetNoise4 (float4x3 positions, SmallXXHash4 hash, int frequency) { positions *= frequency * (1f / sqrt(3f)); … Sample4 s = default(G).EvaluateCombined( Kernel(h0.Eat(z0), x0, z0, positions) + Kernel(h1.Eat(z1), x1, z1, positions) + Kernel(hC.Eat(zC), xC, zC, positions) ); s.dx *= frequency * (1f / sqrt(3f)); s.dz *= frequency * (1f / sqrt(3f)); return s; }

Then change `Kernel`

so it sets its X derivative just like for 1D noise. Besides that, clamp the entire sample so its falloff doesn't go below zero.

static Sample4 Kernel ( SmallXXHash4 hash, float4 lx, float4 lz, float4x3 positions ) { float4 unskew = (lx + lz) * ((3f - sqrt(3f)) / 6f); float4 x = positions.c0 - lx + unskew, z = positions.c2 - lz + unskew; float4 f = 0.5f - x * x - z * z;~~//f = f * f * f * 8f;~~~~//return max(0f, f) * default(G).Evaluate(hash, x, z).v;~~Sample4 g = default(G).Evaluate(hash, x, z); return new Sample4 { v = f * g.v, dx = f * g.dx - 6f * x * g.v } * f * f * select(0f, 8f, f >= 0f); }

At this point the tangent vectors are already correct if no rotation is applied. This makes sense, because Z is constant in the X dimension. If we ignore the gradient for a moment, we've taken the partial derivative of `f(x,z)=(1/2-x^2-z^2)^3` in the X dimension, which is `f'_x(x,z)=-6x(1/2-x^2-z^2)^2`. After that we can incorporate the gradient via the product rule exactly as we did for 1D noise.

The partial derivative in the Z dimension `f_z'(x,z)` is found the same way, but with `z` variable and `x` constant. Add it to the sample.

return new Sample4 { v = f * g.v, dx = f * g.dx - 6f * x * g.v, dz = f * g.dz - 6f * z * g.v } * f * f * select(0f, 8f, f >= 0f);

### Simplex Gradients

To get correct derivatives for simplex gradient noise we have to make `BaseGradients.Circle`

provide derivatives as well. In this case we have `f(x,z)=ax+by` so the partial derivatives are `f_x'(x,z)=a` and `f_z'(x,z)=b`.

public static Sample4 Circle (SmallXXHash4 hash, float4 x, float4 y) { float4x2 v = SquareVectors(hash); return new Sample4 { v = v.c0 * x + v.c1 * y, dx = v.c0, dz = v.c1 } * rsqrt(v.c0 * v.c0 + v.c1 * v.c1); }

At this point it is easy to illustrate an additional benefit of analytical derivatives. Because recalculated tangents and normals depend on the mesh data, vertices at the edge of the mesh have only partial data to work with, so these vectors will be biased toward the inside of the mesh. The result of this is that when you use separate meshes to tile the surface there will be normal and tangent discontinuities along the seams. The analytical approach does not have this problem.

You can see this by putting multiple procedural meshes next to each other and translating their domains so they form a continuous surface.

## 3D Derivatives

Going from two to three dimensions is as simple as going from one to two.

### Simplex Value Noise

First adjust `Simplex3D.GetNoise4`

.

public Sample4 GetNoise4 (float4x3 positions, SmallXXHash4 hash, int frequency) { positions *= frequency * 0.6f; … Sample4 s = default(G).EvaluateCombined( Kernel(h0.Eat(y0).Eat(z0), x0, y0, z0, positions) + Kernel(h1.Eat(y1).Eat(z1), x1, y1, z1, positions) + Kernel(hA.Eat(yCA).Eat(zCA), xCA, yCA, zCA, positions) + Kernel(hB.Eat(yCB).Eat(zCB), xCB, yCB, zCB, positions) ); s.dx *= frequency * 0.6f; s.dy *= frequency * 0.6f; s.dz *= frequency * 0.6f; return s; }

Then change `Kernel`

so it provides all three partial derivatives.

static Sample4 Kernel ( SmallXXHash4 hash, float4 lx, float4 ly, float4 lz, float4x3 positions ) { … float4 f = 0.5f - x * x - y * y - z * z;~~//f = f * f * f * 8f;~~~~//return max(0f, f) * default(G).Evaluate(hash, x, y, z).g;~~Sample4 g = default(G).Evaluate(hash, x, y, z); return new Sample4 { v = f * g.v, dx = f * g.dx - 6f * x * g.v, dy = f * g.dy - 6f * y * g.v, dz = f * g.dz - 6f * z * g.v } * f * f * select(0f, 8f, f >= 0f); }

This is enough to get correct 3D simplex value noise.

Note that if we do not rotate the domain around the X or Z axes we don't need the derivative in the Y dimension, because we never leave the XZ plane. But when an arbitrary rotation is applied all three derivatives are needed.

### Simplex Gradients

To complete simplex gradient noise we have to add derivatives to `BaseGradients.Sphere`

.

public static Sample4 Sphere (SmallXXHash4 hash, float4 x, float4 y, float4 z) { float4x3 v = OctahedronVectors(hash); return new Sample4 { v = v.c0 * x + v.c1 * y + v.c2 * z, dx = v.c0, dy = v.c1, dz = v.c2 } * rsqrt(v.c0 * v.c0 + v.c1 * v.c1 + v.c2 * v.c2); }

## Turbulence

Simplex noise is now complete, but earlier we skipped providing a correct solution for the turbulence variant. We're going to deal with that now.

### Derivative of Absolute Function

Let's add a turbulence variant for simplex gradient noise only, leaving out simplex turbulence value noise. Add it to `ProceduralSurface`

.

static SurfaceJobScheduleDelegate[,] surfaceJobs = { { … }, { SurfaceJob<Simplex1D<Turbulence<Simplex>>>.ScheduleParallel, SurfaceJob<Simplex2D<Turbulence<Simplex>>>.ScheduleParallel, SurfaceJob<Simplex3D<Turbulence<Simplex>>>.ScheduleParallel }, { … } }; public enum NoiseType { Simplex€, SimplexTurbulence, SimplexValue }

Then make `Turbulence.EvaluateCombined`

return the absolute value of the sample, leaving the derivatives unchanged for now.

public Sample4 EvaluateCombined (Sample4 value) { Sample4 s = default(G).EvaluateCombined(value); s.v = abs(s.v); return s; }

The analytical results are obviously incorrect, because the derivatives are not negated when they should be. This negation is based on the value, not the derivatives themselves.

Sample4 s = default(G).EvaluateCombined(value); s.dx = select(-s.dx, s.dx, s.v >= 0f); s.dy = select(-s.dy, s.dy, s.v >= 0f); s.dz = select(-s.dz, s.dz, s.v >= 0f); s.v = abs(s.v);

The sign flip of the noise causes a sudden change in direction. We simply pick one option for the derivatives at that point. The recalculated vectors produce smoother results in this case.

### Smoothstep

The sudden sign flip of turbulence noise tends to create ugly jagged seams in the mesh. We could smooth that a bit by applying a different modification. The simplest option would be to use `f(x)=x^2` instead of `f(x)=|x|`, but that would weaken the noise a lot. A better choice would be the smoothstep function `f(x)=3x^2-2x^3` applied on top of turbulence.

So let's add a `Smoothstep`

gradient modifier to *Noise.Gradient*. The derivative of `f(x)=3g(x)^2-2g(x)^3` is `f'(x)=6g(x)(1-g(x))g'(x)`.

public struct Smoothstep<G> : IGradient where G : struct, IGradient { public Sample4 Evaluate (SmallXXHash4 hash, float4 x) => default(G).Evaluate(hash, x); public Sample4 Evaluate (SmallXXHash4 hash, float4 x, float4 y) => default(G).Evaluate(hash, x, y); public Sample4 Evaluate (SmallXXHash4 hash, float4 x, float4 y, float4 z) => default(G).Evaluate(hash, x, y, z); public Sample4 EvaluateCombined (Sample4 value) { Sample4 s = default(G).EvaluateCombined(value); float4 d = 6f * s.v * (1f - s.v); s.dx *= d; s.dy *= d; s.dz *= d; s.v *= s.v * (3f - 2f * s.v); return s; } }

Now adjust `ProceduralSurface`

so it produces a smoothed turbulence variant.

static SurfaceJobScheduleDelegate[,] surfaceJobs = { { … }, { SurfaceJob<Simplex1D<Smoothstep<Turbulence<Simplex>>>>.ScheduleParallel, SurfaceJob<Simplex2D<Smoothstep<Turbulence<Simplex>>>>.ScheduleParallel, SurfaceJob<Simplex3D<Smoothstep<Turbulence<Simplex>>>>.ScheduleParallel }, { … } }; public enum NoiseType { Simplex€, SimplexSmoothTurbulence, SimplexValue }

The smoothing flattens the noise when it approaches zero, eliminating the discontinuity of the derivatives. How effective this is depends on the resolution of the mesh relative to the noise frequency. The recalculated results will still be smoother, but the analytical results of the smooth variant can also be acceptable.

The next tutorial is Perlin derivatives.