Shader Library
This is the fifth tutorial in a series that covers the creation of procedural patterns on the GPU with shaders, using the Godot Engine, version 4. It follows Hashing and introduces code reuse for our shaders.
This tutorial uses Godot 4.7, the regular version, but you could also use the .NET version.
Hasher
We currently have two shaders: one for fractal wave patters and another for hashing. Now we're going to combine both approaches to perform fractal hashing. To do so we ideally reuse our existing code as much as possible, avoiding code duplication.
We can share code between shaders via include files. These a Godot resource files like gdshader
resources except that they use the gdshaderinc
file extension. To demonstrate this we create such an include file for the Hasher code, so we can reuse it for future shaders.
First, create a shader_libary
folder in the File System panel. We'll put all gdshaderinc
files in there. Then create a new ShaderInclude
resource in that folder, named hasher.gdshaderinc
. Copy the Hasher struct definition and all functions related to it from hashing.gdshader
into it.
struct Hasher {
uint bits;
};
Hasher hasher(uint seed) {
return Hasher(seed + 374761393u);
}
Hasher hasher(Hasher h, float f) {
h.bits += floatBitsToUint(f) * 3266489917u;
h.bits = (h.bits << 17u) | (h.bits >> 32u - 17u);
h.bits *= 668265263u;
return h;
}
uint hash(Hasher h) {
h.bits ^= h.bits >> 15u;
h.bits *= 2246822519u;
h.bits ^= h.bits >> 13u;
h.bits *= 3266489917u;
h.bits ^= h.bits >> 16u;
return h.bits;
}
float hash_to_float(uint hash) {
return uintBitsToFloat(hash & 0x007FFFFFu | 0x3F800000u) - 1.0;
}
vec2 hash_to_vec2(uint hash) {
return uintBitsToFloat(uvec2(
hash << 7u,
hash >> 9u
) & uvec2(0x007FFF80u) | uvec2(0x3F800000u)) - 1.0;
}
vec3 hash_to_vec3(uint hash) {
return uintBitsToFloat(uvec3(
(hash << 12u) & 0x007FF000u,
(hash << 1u) & 0x007FF000u,
(hash >> 9u) & 0x007FE000u
) | uvec3(0x3F800000u)) - 1.0;
}
Now we can remove that code from hashing.gdshader
and instead instruct the shader compiler to include the contents of hasher.gdshaderinc
at the same spot. This is a literal text insertion, copying the code verbatim. We do this with the #include shader preprocessor directive, followed by a string identifying the file to include. We use the absolute path of the resource file for this.
uniform int visualization : hint_enum("Grayscale", "RG", "RGB") = 0;
//struct Hasher {
//…
#include "res://shader_library/hasher.gdshaderinc"
void fragment() { … }
Shader compilation is a two-step process. First the preprocessor runs, which in this case copies the Hasher code into the shader. Then the actual compilation process runs. So the result is still exactly the same.
Fractal Pattern
Let's also extract the code responsible for the fractal creation from sine_waves.gdshader
and put it in a new fractal_pattern.gdshaderinc
resource file. It are the normalize_fractal_sample() and sample_fractal_pattern() functions, which is generic code that should work for any pattern.
PatternSample normalize_fractal_sample(PatternSample s) {
float scale = 1.0 - persistence;
if (adaptive_fractal_scale) {
scale /= 1.0 - pow(persistence, float(octaves));
}
return multiply(s, scale);
}
PatternSample sample_fractal_pattern(vec2 uv) {
PatternSample sum = zero_pattern_sample();
vec2 frequency = base_frequency;
float amplitude = 1.0;
for (int i = 0; i < octaves; i++) {
sum = add(sum, multiply(sample_pattern(frequency, uv), amplitude));
frequency *= lacunarity;
amplitude *= persistence;
}
return normalize_fractal_sample(sum);
}
Include the new resource in sine_waves.gdshader
, replacing the original functions there.
PatternSample sample_pattern(vec2 frequency, vec2 uv) { … }
//PatternSample normalize_fractal_sample(PatternSample s) {
//…
#include "res://shader_library/fractal_pattern.gdshaderinc"
void vertex() { … }
Advanced Shader Preprocessing
Everything still works, but Godot indicates that there is an error in the fractal_pattern.gdshaderinc
file. The specific error is: Error at line 1: Expected constant, function, uniform or varying.
This happens because the code starts with PatternSample, but this struct type is not declared in the file so it's just nonsense according to the compiler when it tries to compile the file in isolation. This isn't a real problem because it's just a separate snippet of code that's supposed to be inserted in a shader. However, we ideally get no distracting compilation errors at all. So let's get rid of it by ensuring that fractal_pattern.gdshaderinc
compiles correctly in isolation.
The straightforward way to eliminate the error is to declare PatternSample inside fractal_pattern.gdshaderinc
. However, this will cause compilation to fail for the shader, as it also declares PatternSample, thus leading to an invalid duplicate definition.
So when compiled in isolation fractal_pattern.gdshaderinc
should declare PatternSample, but when included in a shader it shouldn't. We can make this happen via conditional compilation. The preprocessor can be instructed to include or skip parts of the code when processing it. These choices are controlled by defining preprocessor identifiers, also known as macros.
For example, let's say that if a shader includes our include file it must first define the PATTERN_SAMPLE indentifier, to signify that it has defined PatternSample itself. Then we can instruct the preprocessor to check whether that identifier is known when it processes the include file. Preprocessor identifiers use CONSTANT_CASE by convention.
An if-not-defined check can be done by using #ifndef, followed by the identifier to check. The code after that is included if the check passes and is skipped otherwise. The end of the relevant code section is indicated with #endif. Add such a check at the start of the file, initially without any code.
#ifndef PATTERN_SAMPLE
#endif
PatternSample normalize_fractal_sample(PatternSample s) { … }
Shouldn't we avoid preprocessor macros?
When used in moderation relying on the preprocessor is fine, but it can spiral out of control when macros with parameters are used to synthesize complex code. Such code can be very hard to debug because of the mistmatch between what's written and what gets compiled. The examples in this tutorial are about as complex as I'd like to make it.
To verify that this works let's let's trigger a custom error when PATTERN_SAMPLE is not defined, using #error followed by an error message. I write the error message as a quoted string for syntax highlighting purposes, even though that is not required.
#ifndef PATTERN_SAMPLE
#error "PATTERN_SAMPLE not defined."
#endif
The include file now gives us this error: Error at line 2: "PATTERN_SAMPLE not defined."
. And the shader file gives us the same error: Error at line 77 in include fractal_pattern.gdshaderinc:2: "PATTERN_SAMPLE not defined."
To get ride of the error in sine_waves.gdshader
define PATTERN_SAMPLE directly before including the file, using #define.
#define PATTERN_SAMPLE
#include "res://shader_library/fractal_pattern.gdshaderinc"
Now we know that it works we'll replace the custom error in pattern_sample.gdshaderinc
with the declaration of a PatternSample struct. We don't have to exactly duplicate the original PatternSample. Let's keep it as simple as possible, only giving it a value field. This gives us two different versions of PatternSample, but we'll simplify this later.
#ifndef PATTERN_SAMPLE
struct PatternSample {
float v;
};
#endif
We must also declare compatible functions that are used by the fractal code.
#ifndef PATTERN_SAMPLE
struct PatternSample {
float v;
};
PatternSample zero_pattern_sample() {
return PatternSample(0.0);
}
PatternSample add(PatternSample a, PatternSample b) {
return PatternSample(a.v + b.v);
}
PatternSample multiply(PatternSample a, float b) {
return PatternSample(a.v * b);
}
#endif
Fractal Pattern Settings
The include file still has errors because it doesn't contain the uniform variables used to configure the fractal. To make it easier to include it in arbitrary shaders we won't require these configuration options to be uniform variables. We instead introduce a FractalPatternSettings struct type that bundles all settings. Then we add it as the first parameter to both functions and use its configuration fields.
struct FractalPatternSettings {
vec2 base_frequency;
int octaves;
float lacunarity;
float persistence;
bool adaptive_fractal_scale;
};
PatternSample normalize_fractal_sample(
FractalPatternSettings settings,
PatternSample s
) {
float scale = 1.0 - settings.persistence;
if (settings.adaptive_fractal_scale) {
scale /= 1.0 - pow(settings.persistence, float(settings.octaves));
}
return multiply(s, scale);
}
PatternSample sample_fractal_pattern(FractalPatternSettings settings, vec2 uv) {
PatternSample sum = zero_pattern_sample();
vec2 frequency = settings.base_frequency;
float amplitude = 1.0;
for (int i = 0; i < settings.octaves; i++) {
sum = add(sum, multiply(sample_pattern(frequency, uv), amplitude));
frequency *= settings.lacunarity;
amplitude *= settings.persistence;
}
return normalize_fractal_sample(settings, sum);
}
Now we have to fill a FractalPatternSettings struct with the uniform variables in sine_waves.gdshader
and pass it to sample_fractal_pattern() in vertex() and fragment(). Let's introduce a get_fractal_pattern_settings() function for this to avoid code repetition.
FractalPatternSettings get_fractal_pattern_settings() {
return FractalPatternSettings(
base_frequency,
octaves,
lacunarity,
persistence,
adaptive_fractal_scale
);
}
void vertex() {
PatternSample pattern_sample = sample_fractal_pattern(
get_fractal_pattern_settings(),
UV + animation_speed * TIME
);
…
}
void fragment() {
PatternSample pattern_sample = sample_fractal_pattern(
get_fractal_pattern_settings(),
UV + animation_speed * TIME
);
…
}
Sample Pattern Function
There is still one error in the include file: it doesn't have the sample_pattern() function. We can solve this by again checking whether an identifier is defined, in this case using SAMPLE_PATTERN_FUNCTION. If it is not defined then we insert a dummy sample_pattern() function.
#ifndef PATTERN_SAMPLE
…
#endif
#ifndef SAMPLE_PATTERN_FUNCTION
PatternSample sample_pattern(vec2 frequency, vec2 uv) {
return zero_pattern_sample();
}
#endif
But this time we go a step further than just checking whether a specific function exists. We're going to support any function name, so it's easier to swap out pattern functions. This is done by following the identifier definition with whatever we want it to represent. In this case we simply add a function name. Let's define SAMPLE_PATTERN_FUNCTION to be dummy_sample_pattern and rename our dummy function to match.
#ifndef SAMPLE_PATTERN_FUNCTION
#define SAMPLE_PATTERN_FUNCTION dummy_sample_pattern
PatternSample dummy_sample_pattern(vec2 frequency, vec2 uv) {
return zero_pattern_sample();
}
#endif
When the shader processor encounters the identifier in code it will replace it with whatever we defined it to be. This is purely a textual replacement. So in sample_fractal_pattern we have to replace sample_function with SAMPLE_PATTERN_FUNCTION.
PatternSample sample_fractal_pattern(FractalPatternSettings settings, vec2 uv) {
…
for (int i = 0; i < settings.octaves; i++) {
sum = add(sum, multiply(
SAMPLE_PATTERN_FUNCTION(frequency, uv), amplitude));
…
}
return normalize_fractal_sample(settings, sum);
}
Now the include file on its own ends up using dummy_sample_pattern and there are finally no more errors in it. To use the correct function in sine_waves.gdshader
we have to define SAMPLE_PATTERN_FUNCTION to be sample_pattern before including the file.
#define PATTERN_SAMPLE
#define SAMPLE_PATTERN_FUNCTION sample_pattern
#include "res://shader_library/fractal_pattern.gdshaderinc"
Pattern Frequency Type
The purpose of putting the fractal code in an include file is to reuse it so we can also create fractal hash patterns. However, there is a difference in approach that currently makes the fractal code incompatible with hashing.gdshader
: for hashes we use a single float for the frequence while for sine waves we use vec2. While we could just double up the hash frequence to get a vec2, but it is better if fractal_pattern.gdshaderinc
can support both approaches. This is easily done by defining a PATTERN_FREQUENCY_TYPE and using it instead of vec2 everywhere we declare a frequency. If it is not defined we define it to be float by default.
#ifndef PATTERN_SAMPLE
…
#endif
#ifndef PATTERN_FREQUENCY_TYPE
#define PATTERN_FREQUENCY_TYPE float
#endif
#ifndef SAMPLE_PATTERN_FUNCTION
#define SAMPLE_PATTERN_FUNCTION dummy_sample_pattern
PatternSample dummy_sample_pattern(PATTERN_FREQUENCY_TYPE frequency, vec2 uv) {
return zero_pattern_sample();
}
#endif
struct FractalPatternSettings {
PATTERN_FREQUENCY_TYPE base_frequency;
int octaves;
float lacunarity;
float persistence;
bool adaptive_fractal_scale;
};
…
PatternSample sample_fractal_pattern(…) {
PatternSample sum = zero_pattern_sample();
PATTERN_FREQUENCY_TYPE frequency = settings.base_frequency;
…
}
Now we have to define PATTERN_FREQUENCY_TYPE as vec2 in sine_waves.gdshader
.
#define PATTERN_SAMPLE
#define PATTERN_FREQUENCY_TYPE vec2
#define SAMPLE_PATTERN_FUNCTION sample_pattern
#include "res://shader_library/fractal_pattern.gdshaderinc"
Fractal Hashing
After all that work we can move on to adding fractal support to hashing.gdshader
. Begin by replacing the single uniform frequency variable with copies of the fractal variables from sine_waves.gdshader
, with the only difference that base_frequency is a float.
//uniform float frequency = 1.0;
uniform uint hash_seed = 0u;
uniform int visualization : hint_enum("Grayscale", "RG", "RGB") = 0;
uniform float base_frequency = 1.0;
uniform int octaves : hint_range(1, 10) = 1;
uniform float lacunarity : hint_range(1.0, 5.0, 0.1) = 2.0;
uniform float persistence : hint_range(0.0, 0.95, 0.05) = 0.5;
uniform bool adaptive_fractal_scale = false;
Then declare a PatternSample with accompanying functions that has a vec3 value.
uniform bool adaptive_fractal_scale = false;
struct PatternSample {
vec3 v;
};
PatternSample zero_pattern_sample() {
return PatternSample(vec3(0.0));
}
PatternSample add(PatternSample a, PatternSample b) {
return PatternSample(a.v + b.v);
}
PatternSample multiply(PatternSample a, float b) {
return PatternSample(a.v * b);
}
Adapt fragment() so it calls a sample_hash() function with shifted UV coordinates, which returns a PatternSample with the final color.
#include "res://shader_library/hasher.gdshaderinc"
//void fragment() {
PatternSample sample_hash(float frequency, vec2 uv) {
vec2 coordinates = floor(frequency * uv);
Hasher h = hasher(hash_seed);
h = hasher(h, coordinates.x);
h = hasher(h, coordinates.y);
uint hash = hash(h);
PatternSample s = zero_pattern_sample();
switch (visualization) {
case 0: // Grayscale
s.v = vec3(hash_to_float(hash));
break;
case 1: // RG
s.v = vec3(hash_to_vec2(hash), 0.0);
break;
case 2: // RGB
s.v = hash_to_vec3(hash);
break;
}
return s;
}
void fragment() {
ALBEDO = sample_hash(base_frequency, UV - 0.5).v;
}
Define SAMPLE_PATTERN and the appropriate SAMPLE_PATTERN_FUNCTION before including fractal_pattern.gdshaderinc
. We don't need to define PATTERN_FREQUENCY_TYPE because we use the default. Follow that with a copy of get_fractal_pattern_settings(). Then replace the single sample call in fragment() with calling sample_fractal_pattern().
#define PATTERN_SAMPLE
#define SAMPLE_PATTERN_FUNCTION sample_hash
#include "res://shader_library/fractal_pattern.gdshaderinc"
FractalPatternSettings get_fractal_pattern_settings() {
return FractalPatternSettings(
base_frequency,
octaves,
lacunarity,
persistence,
adaptive_fractal_scale
);
}
void fragment() {
ALBEDO = sample_fractal_pattern(
get_fractal_pattern_settings(),
UV - 0.5
).v;
}
The same fractal code is now used to create fractals from sine waves and from hashes.
Seed per Octave
Although the fractal hash might appear fine at first glance it has a flaw. Because each octave is generated with the same seed we're combining copies of the same pattern at different scales. This can produce stripes that radiate outward from the UV origin, where the pattern reinforces itself. This also happens with sine waves, but those patterns are regular so it is intentional.

We can avoid this by using a different seed per octave. This necessitates to know which octave we're sampling for. So we add the octave as the first parameter of the sample function and pass the octave index to it in fractal_pattern.gdshaderinc
.
PatternSample dummy_sample_pattern(int octave, vec2 frequency, vec2 uv) {
return zero_pattern_sample();
}
…
PatternSample sample_fractal_pattern(FractalPatternSettings settings, vec2 uv) {
…
for (int i = 0; i < settings.octaves; i++) {
sum = add(sum, multiply(
SAMPLE_PATTERN_FUNCTION(i, frequency, uv), amplitude));
…
}
return normalize_fractal_sample(settings, sum);
}
Add the required parameter to the sample functions in both hashing.gdshader
and sine_waves.gdshader
.
PatternSample sample_pattern(int octave, vec2 frequency, vec2 uv) { … }
Then we add it to the seed in the sample_pattern() function of hashing.gdshader
.
Hasher h = hasher(hash_seed + uint(octave));

Unified Pattern Sample
Although we're reusing the fractal code we current have three different versions of PatternSample with slight variantions. Let's consolidate them by introducing pattern_sample.gdshaderinc
. It starts as a copy of PatternSample and accompanying functions from the other include file. Replace its value type with PATTERN_SAMPLE_VALUE_TYPE and define it as float by default.
#ifndef PATTERN_SAMPLE_VALUE_TYPE
#define PATTERN_SAMPLE_VALUE_TYPE float
#endif
struct PatternSample {
PATTERN_SAMPLE_VALUE_TYPE v;
};
PatternSample zero_pattern_sample() {
return PatternSample(PATTERN_SAMPLE_VALUE_TYPE(0.0));
}
PatternSample add(PatternSample a, PatternSample b) {
return PatternSample(a.v + b.v);
}
PatternSample multiply(PatternSample a, float b) {
return PatternSample(a.v * b);
}
From now on we'll just include this file in fractal_pattern.gdshaderinc
instead of checking if PATTERN_SAMPLE is defined. We can use the relative path for the include file here because they should be in the same folder.
#ifndef PATTERN_SAMPLE
//…
#endif
#include "pattern_sample.gdshaderinc"
In hashing.gdshader
replace its PatternSample code with defining PATTERN_SAMPLE_VALUE_TYPE as vec3 and then including pattern_sample.gdshaderinc
. Also remove the definition of PATTERN_SAMPLE as it is no longer useful.
uniform bool adaptive_fractal_scale = false;
//struct PatternSample {
//…
#define PATTERN_SAMPLE_VALUE_TYPE vec3
#include "res://shader_library/pattern_sample.gdshaderinc"
#include "res://shader_library/hasher.gdshaderinc"
PatternSample sample_pattern(int octave, float frequency, vec2 uv) { … }
//#define PATTERN_SAMPLE
#define PATTERN_FREQUENCY_TYPE float
Note that we are now including pattern_sample.gdshaderinc
twice. However, Godot's shader preprocessor only includes each file once and subsequent inclusion directives are ignored. So as hashing.gdshader
includes it first fractal_pattern.gdshaderinc
doesn't include it a second time.
To also make this work for the sine waves we have to add derivatives to pattern_sample.gdshaderinc
. Let's be flexible and support derivatives for up to three dimensions. Because the derivatives have the same type as the value we have to use separate fields for each, which we name dx, dy, and dz. If these fields end up not being used for the final result then the shader compiler will optimize them away.
struct PatternSample {
PATTERN_SAMPLE_VALUE_TYPE v, dx, dy, dz;
};
Include these derivatives in the creation of a PatternSample in zero_pattern_sample().
PatternSample zero_pattern_sample() {
return PatternSample(
PATTERN_SAMPLE_VALUE_TYPE(0.0),
PATTERN_SAMPLE_VALUE_TYPE(0.0),
PATTERN_SAMPLE_VALUE_TYPE(0.0),
PATTERN_SAMPLE_VALUE_TYPE(0.0)
);
}
Also add the derivatives to the other functions.
PatternSample add(PatternSample a, PatternSample b) {
return PatternSample(
a.v + b.v,
a.dx + b.dx,
a.dy + b.dy,
a.dz + b.dz
);
}
PatternSample multiply(PatternSample a, float b) {
return PatternSample(
a.v * b,
a.dx * b,
a.dy * b,
a.dz * b
);
}
Now we can replace the PatternSample code in sine_waves.gdshader
with including pattern_sample.gdshaderinc
. Also get rid of the definition of PATTERN_SAMPLE.
//struct PatternSample {
//…
#include "res://shader_library/pattern_sample.gdshaderinc"
PatternSample sample_pattern(vec2 frequency, vec2 uv) { … }
//#define PATTERN_SAMPLE
We have to adapt sample_pattern() to work with the new derivative fields. Let's also initialize the sample properly using zero_pattern_sample().
PatternSample sample_pattern(int octave, vec2 frequency, vec2 uv) {
vec2 f = frequency * TAU;
vec2 t = f * uv;
PatternSample s = zero_pattern_sample();
switch (function) {
case 0: // U
s.v = sin(t.x);
s.dx = f.x * cos(t.x);
s.dy = 0.0;
break;
case 1: // V
s.v = sin(t.y);
s.dx = 0.0;
s.dy = f.y * cos(t.y);
break;
case 2: // UV Average
s.v = (sin(t.x) + sin(t.y)) * 0.5;
s.dx = f.x * cos(t.x) * 0.5;
s.dy = f.y * cos(t.y) * 0.5;
break;
case 3: // UV Product
s.v = sin(t.x) * sin(t.y);
s.dx = f.x * cos(t.x) * sin(t.y);
s.dy = f.y * sin(t.x) * cos(t.y);
break;
}
return s;
}
Adjust vertex() and fragment() as well.
void vertex() {
…
if (derivatives_per_vertex) {
derivatives =
vec2(pattern_sample.dx, pattern_sample.dy) *
displacement * bumpiness;
}
}
void fragment() {
…
else {
d =
vec2(pattern_sample.dx, pattern_sample.dy) *
displacement * bumpiness;
}
…
}
Settings Revisited
Providing the fractal settings still requires us to write quite a bit of code that is the same for both shaders, so let's introduce fractal_pattern_settings.gdshaderinc
and put the declaration of FractalPatternSettings there, along with the definition of PATTERN_FREQUENCY_TYPE.
#ifndef PATTERN_FREQUENCY_TYPE
#define PATTERN_FREQUENCY_TYPE float
#endif
struct FractalPatternSettings {
PATTERN_FREQUENCY_TYPE base_frequency;
int octaves;
float lacunarity;
float persistence;
bool adaptive_fractal_scale;
};
Both shaders use the same uniform variables to create the fractal settings. Let's declare these in the include file as well, along with a standard_fractal_pattern_settings() function that uses them to construct the settings. Then we only activate that code if USE_STANDARD_FRACTAL_PATTERN_SETTINGS is defined, so shaders can either use the standard configuration fields or do something else.
struct FractalPatternSettings { … };
#ifdef USE_STANDARD_FRACTAL_PATTERN_SETTINGS
uniform PATTERN_FREQUENCY_TYPE base_frequency = PATTERN_FREQUENCY_TYPE(1.0);
uniform int octaves : hint_range(1, 10) = 1;
uniform float lacunarity : hint_range(1.0, 5.0, 0.1) = 2.0;
uniform float persistence : hint_range(0.0, 0.95, 0.05) = 0.5;
uniform bool adaptive_fractal_scale = false;
FractalPatternSettings standard_fractal_pattern_settings() {
return FractalPatternSettings(
base_frequency,
octaves,
lacunarity,
persistence,
adaptive_fractal_scale
);
}
#endif
Let's also provide a convenient default_fractal_pattern_settings() function when the standard settings aren't desired, using #else.
FractalPatternSettings standard_fractal_pattern_settings() {
return FractalPatternSettings(
base_frequency,
octaves,
lacunarity,
persistence,
adaptive_fractal_scale
);
}
#else
FractalPatternSettings default_fractal_pattern_settings() {
return FractalPatternSettings(
PATTERN_FREQUENCY_TYPE(1.0),
1,
2.0,
0.5,
false
);
}
#endif
We now get a warning about the struct not being using in the include file, even though it is. This isn't as annoying as an error, but still less than ideal. If it bothers you then you can disable those warnings globally via the Project Settings. Search for unused
to quickly find the relevant settings. But in this case we can satisfy the compiler by explicitly using a variable of the FractalPatternSettings type in default_fractal_settings().
FractalPatternSettings default_fractal_pattern_settings() {
FractalPatternSettings settings = FractalPatternSettings(
PATTERN_FREQUENCY_TYPE(1.0),
1,
2.0,
0.5,
false
);
return settings;
}
Include this file in fractal_patern.gdshaderinc
to replace its own declaration of FractalPatternSettings and related code. Like for the pattern sample, this inclusion is supposed to be overruled by an earlier inclusion in a shader.
#include "fractal_pattern_settings.gdshaderinc"
#include "pattern_sample.gdshaderinc"
//#ifndef PATTERN_FREQUENCY_TYPE
//#define PATTERN_FREQUENCY_TYPE float
//#endif
…
//struct FractalPatternSettings { … };
Include fractal_pattern_settings.gdshaderinc
in hashing.gdshader
, indicating that we want to use the standard settings, replacing the duplicate code.
shader_type spatial;
uniform uint hash_seed = 0u;
uniform int visualization : hint_enum("Grayscale", "RG", "RGB") = 0;
//uniform float base_frequency = 1.0;
//…
//uniform bool adaptive_fractal_scale = false;
#define USE_STANDARD_FRACTAL_PATTERN_SETTINGS
#include "res://shader_library/fractal_pattern_settings.gdshaderinc"
#define PATTERN_SAMPLE_VALUE_TYPE vec3
#include "res://shader_library/pattern_sample.gdshaderinc"
#include "res://shader_library/hasher.gdshaderinc"
PatternSample sample_pattern(int octave, float frequency, vec2 uv) { … }
#define SAMPLE_PATTERN_FUNCTION sample_pattern
#include "res://shader_library/fractal_pattern.gdshaderinc"
//FractalPatternSettings get_fractal_pattern_settings() { … }
void fragment() {
ALBEDO = sample_fractal_pattern(
standard_fractal_pattern_settings(),
UV - 0.5
).v;
}
Do the same for sine_waves.gdshader
. PATTERN_FREQUENCY_TYPE must now be defined before including the settings.
shader_type spatial;
uniform vec2 animation_speed = vec2(0.0);
uniform vec2 bumpiness = vec2(1.0);
uniform float displacement : hint_range(-1.0, 1.0) = 0.2;
//uniform float base_frequency = 1.0;
//…
//uniform bool adaptive_fractal_scale = false;
#define PATTERN_FREQUENCY_TYPE vec2
#define USE_STANDARD_FRACTAL_PATTERN_SETTINGS
#include "res://shader_library/fractal_pattern_settings.gdshaderinc"
uniform int function : hint_enum("U", "V", "UV Average", "UV Product") = 0;
uniform bool colors_per_vertex = false;
uniform bool derivatives_per_vertex = false;
uniform bool vertex_displacement = true;
varying vec2 derivatives;
vec3 colorize(float v) { … }
#include "res://shader_library/pattern_sample.gdshaderinc"
PatternSample sample_pattern(int octave, vec2 frequency, vec2 uv) { … }
//#define PATTERN_FREQUENCY_TYPE vec2
#define SAMPLE_PATTERN_FUNCTION sample_pattern
#include "res://shader_library/fractal_pattern.gdshaderinc"
//FractalPatternSettings get_fractal_pattern_settings() { … }
void vertex() {
PatternSample pattern_sample = sample_fractal_pattern(
standard_fractal_pattern_settings(),
UV + animation_speed * TIME
);
…
}
void fragment() {
PatternSample pattern_sample = sample_fractal_pattern(
standard_fractal_pattern_settings(),
UV + animation_speed * TIME
);
…
}
Sample Dimensions
Although we're only sampling in two dimensions, let's already support sampling in a configurable amount of dimensions, by using SAMPLE_PATTERN_COORDINATES_TYPE for the uv type in fractal_pattern.gdshaderinc
, with vec2 as the default. Because the uv name is specifically for two dimensions, let's rename it to coordinates.
#ifndef SAMPLE_PATTERN_COORDINATES_TYPE
#define SAMPLE_PATTERN_COORDINATES_TYPE vec2
#endif
#ifndef SAMPLE_PATTERN_FUNCTION
#define SAMPLE_PATTERN_FUNCTION dummy_sample_pattern
PatternSample dummy_sample_pattern(
PATTERN_FREQUENCY_TYPE frequency,
SAMPLE_PATTERN_COORDINATES_TYPE coordinates
) {
return zero_pattern_sample();
}
#endif
…
PatternSample sample_fractal_pattern(
FractalPatternSettings settings,
SAMPLE_PATTERN_COORDINATES_TYPE coordinates
) {
…
for (int i = 0; i < settings.octaves; i++) {
sum = add(sum, multiply(
SAMPLE_PATTERN_FUNCTION(i, frequency, coordinates), amplitude));
frequency *= settings.lacunarity;
amplitude *= settings.persistence;
}
return normalize_fractal_sample(settings, sum);
}
Code Documentation
Finally, our include files form a small shader library for reusable code, so it is proper to describe how they are supposed to be used. Add code documentation blocks in between /* */ at the top of the files. There is no documentation standard for this, so we just write something concise. First is pattern_sample.gdshaderinc
.
/*
Declares the PatternSample struct used by fractal_pattern.gdshaderinc.
Define PATTERN_SAMPLE_VALUE_TYPE to set the sample value's type.
The value field is named v.
The default is float. Other options are vec2, vec3, vec4.
Up to three derivatives are supported, with the same type as the value.
The derivative fields are named dx, dy, dz.
*/
#ifndef PATTERN_SAMPLE_VALUE_TYPE
#define PATTERN_SAMPLE_VALUE_TYPE float
#endif
Second is fractal_pattern_settings.gdshaderinc
.
/*
Declares the FractalPatternSettings struct used by fractal_pattern.gdshaderinc.
The following settings are supported:
- base_frequency: Frequency of first octave (default 1.0 for all components).
- octaves: The amount of octaves (default 1).
- lacunarity: Frequency multiplier for successive octaves (default 2.0).
- persistence: Amplitude multiplier for successive octaves (default 0.5).
- adaptive_fractal_scale: How to scale the total amplitude (default false):
- true: Based on actual octaves.
- false: Based on infinite octaves.
Define PATTERN_FREQUENCY_TYPE to set the type of base_frequency.
The default is float. Other options are float2, float3, float4.
Define USE_STANDARD_FRACTAL_PATTERN_SETTINGS to declare:
- Uniform variables for all fractal pattern settings.
- The standard_fractal_pattern_settings function to grab these settings.
*/
#ifndef PATTERN_FREQUENCY_TYPE
#define PATTERN_FREQUENCY_TYPE float
#endif
And we wrap up with fractal_pattern.gdshaderinc
.
/*
Declares the sample_fractal_pattern function and whatever else it needs.
Its parameters are:
- FractalPatternSettings for the fractal settings.
- SAMPLE_PATTERN_COORDINATES_TYPE for the sample coordinates.
Include fractal_pattern_settings.gdshaderinc and pattern_sample.gdshaderinc first.
Define SAMPLE_PATTERN_COORDINATES_TYPE to set the sample coordinates type.
The default is vec2. Other options are float, vec3, vec4.
Define SAMPLE_PATTERN_FUNCTION to set the function for sampling an octave.
Only define the function name, without parameter list.
The matching function declaration must have three parameters:
- int for the octave's index.
- PATTERN_FREQUENCY_TYPE for the octave's sample frequency.
- SAMPLE_PATTERN_COORDINATES_TYPE for the sample coordinates.
Note that the frequency and coordinates can have different types.
For example, float for a uniform frequency and vec2 for 2D sample coordinates.
*/
#include "fractal_pattern_settings.gdshaderinc"
#include "pattern_sample.gdshaderinc"
We'll rely on this shader library to create new patterns in the future.