Skip to content

Shaders in MagicaVoxel

Lachlan McDonald edited this page May 11, 2022 · 9 revisions

Writing shaders

Shader files are written in OpenGL Shader Language (GLSL), version 1.10. The Book of Shaders is a good beginners guide to the shader language.

Each shader has a map function which is executed once per voxel:

  • It receives the location of the voxel as its only parameter
  • Should return a float between 0.0 and 255.0 representing the voxel color in the palette.

For example, the following shader will fill the entire volume with voxels colored from palette index 1.

// xs_begin
// author : '@lachlanmcdonald'
// xs_end

float map(vec3 v) {
	return 1.0;
}
  • Shaders must contain the header (xs_begin and xs_end), even if there are no parameters.
  • author is optional. Whilst there is no standard for it value, a URL or Twitter handle is customary.
  • As shaders return a float, the value is rounded-up. For instance, 0.4999 will result in a voxel palette of 1.0.
  • Return values are clamped between 0.0 and 255.0, so it is safe to return a value outside of this range.

Parameters

Note that the shaders use the term arg (short for argument) when referring to the shader inputs you see within the UI.

However, to avoid confusion between a shader argument and a function argument (in the shader itself), shader arguments are instead referred as a parameter throughout this documentation.

  • All parameters are passed through to the shader as a float.
  • The order that the arg tags are defined is the order that they appear within the UI.
  • As of 0.99.7.0, a shader can have up to 16 parameters. If a shader defines more than 16 parameters, the shader may not compile or the additional parameters will not be set.
Key Description
name The label of the parameter as it appears within the UI.
var Exposes the parameter as a variable. For instance, a var of m_size will expose the variable m_size throughout the shader. Any valid GLSL variable name is accepted, it is conventional to use snake-case and prefix the variable name with m_
range The minimum and maximum value accepted by the field, separated by a space. For instance, '0 100' would enforce a minimum and maximum value of 0 and 100, respectively. If the user enters a value outside of this range, it will be clamped.
value The default value of the parameter
step The number to increase or decrease the value as the user scrubs the field. Users can still manually enter numbers which are not dividable by the step
precision The number of fractional digits allowed when entering a number. A value of 0 only accepts whole numbers. A value of 2 would allow numbers as small as 0.01

The following keys are deprecated but included for backward compatibility:

Key Description
id The index of the parameter, used to populate i_args. Replaced by var.
decimal Used to indicate whether the field should accept a whole (0) or fractional number (1). Regardless of this value, the field is always exposed to the shader as a float. This was replaced by the precision key to fix an issue where the field only accepted 1 significant digit, making it impossible to provide values smaller than 0.1.

Shader inputs

MagicaVoxel provides shaders a number of variables which can be accessed by the shader during execution, these are:

Variable Type Purpose
i_volume_size vec3 Size of the volume (1-256)
i_color_index float The selected color (0-255). This is provided for backward compatibility, as in later versions the user can select multiple colors
i_num_color_sels int The number of selected colors
i_axis vec3 The selected axis mode. Each component corresponds to the selected axis mode. If a mode is selected, the value will be 1.0, otherwise it will be 0.0. If all components are 0.0 or 1.0, an axis-mode is not selected.
i_mirror vec3 The selected mirror mode. Each component corresponds to the selected mirror mode. If a mode is selected, the value will be 1.0, otherwise it will be 0.0. If all components are 0.0 or 1.0, an mirror mode is not selected.
i_iter vec3 The current iteration, which is 0.0 unless the user has set a higher value when running the shader. This value only applies when the shader is run over the volume, and not used as a brush.
  • In previous versions of MagicaVoxel, prior to the introduction of the shader UI, shaders could only be executed via the console. MagicaVoxel provided the i_args variable (float[]) with each index corresponding to the provided parameters. This is provided for backward compatibility but should not be used. Instead, parameters should be defined within the shader header and assigned to a variable using var.
  • In previous versions of MagicaVoxel, these variables were written in camel-case not snake-case; i.e. iArgs instead of i_args. The camel-case variables are still provided for backward compatibility, but you should use the variables above to future-proof your shader.

Built-in functions

MagicaVoxel also provides a number of additional functions:

float voxel (vec3 v)

Returns the color of the voxel at the position v, in the range of 1-255, or 0 if there is no voxel at that position. These correspond to the X, Y and Z coordinates shown in the toolbar of the MagicaVoxel editor.

  • Providing an invalid position, such as one greater than the volume size, will return 0
  • voxel() can only be called when the shader is run over the entire volume, otherwise it will always return 0 (even when voxels are present)

float color_sel(float k);

Returns the k-th selected color.

  • The first color is index 0
  • The number of selected colors is defined by i_num_color_sels

vec4 palette(float k)

Returns information about the color the k-th index in the palette. The rgb components correspond to the RGB values of the color, in the range of 0-1. The a component appears to be unused.

Note: Vector components in GLSL can be accessed with either xyzw or rgba. These behave identically but are useful to use when working with coordinates and textures/colors, respectively.

Observations

Overloading internal functions

Some hardware will not allow the extension of internal functions with a different signature. For maximum portability, a function's name should never match an internal function.

For instance, the following code (which attempts to create a new version of mod to work with only ints) will not work on all systems:

// Error
int mod(int a, int b) {
	return int(mod(float(a), float(b)));
}

Instead, it should be given a different name:

// Correct
int imod(int a, int b) {
	return int(mod(float(a), float(b)));
}

Type casting

Some hardware cannot coerce between int and float , and will require an explicit cast. You should always explicitly cast from int to float and vice versa.

i.e.

float a = 1.0;
int b = int(a * 2.0); // will work on all hardware
int b = a * 2.0;      // will not work on all hardware

float c = 2 * 6;        // will not work on all hardware
float c = float(2 * 6); // will work on all hardware

Similarly, a function must always return the expected return type (the value is not coerced):

float a() {
	return 1; // will not work
}
float a() {
	return float(1); // will work
}

Voxel coordinates

The map function is passed the centre-point of the voxel. So, a voxel as position 0, 0, 0 will be passed to the map function as vec3(0.5, 0.5, 0.5).

If this is undesirable, you can floor the entire vec3 in one operation:

vec3 v = floor(v);

voxel always refers to original model

The shader is run once per voxel in the volume or brush, in a non-specified order. As such, no data is shared between each execution of the shader and voxel() will always refer to the original model.

Functions must be declared before they are used

For example, b() must be defined before a(), or it will not be defined when a() tries to call it.

float b() {
	return 1.0;
}

float a() {
	return b();
}

float map(vec3 v) {
	return a();
}

Limit on array size

There are platform-dependant limits on the size of arrays. When an array size exceeds this limit, MagicaVoxel won't display an error and voxels will not be added by the shader. However, in some circumstances random voxels will appear.

It is best to initialise arrays with fewer than 255 elements.

Using the voxel function beyond the volume size

voxel() will return 0.0 when addressing beyond the volume size. Therefore, it is not necessary to check whether the co-ordinates are out-of-bounds before calling voxel.

voxel(500.0, 500.0, 500.0); // 0.0
voxel(-1.0, -1.0, -1.0); // 0.0

Marquee tool

MagicaVoxel's Marquee tool (where you can select voxels to limit your drawing) also applies to shaders. Shaders can only access the selected voxels. MagicaVoxel treats unselected voxels the same as an empty location, having a index of 0.0, which can cause issues with shaders which rely on checking adjacent voxels.

Snippets

Determine if a axis mode is set

no_axis_mode will be true when no axis modes are set, false otherwise.

bool no_axis_mode = all(equal(ivec3(i_axis), ivec3(0)));

Determine which axis mode is set

bvec3 axis_mode = equal(ivec3(i_axis), ivec3(1));

axis_mode is a bvec3 indicating which axis mode is set.

For example: axis_mode.x will be true if the X-axis mode is set.

mix() for selected colors

float pal_mix(float p) {
	float f = floor(mix(0.0, float(i_num_color_sels), p));
	return color_sel(f);
}

Determine if the provided color is one of the selected colors

bool is_sel_color(float p) {
	for (int i = 0; i < i_num_color_sels; i += 1) {
		if (p == color_sel(float(i))) {
			return true;
		}
	}
	return false;
}

Logging

MagicaVoxel provides a simple logging tool which can be used to identify issues with a shader. To access the log output, you will need to run MagicaVoxel from within a terminal:

MacOS

In Terminal, navigate to your MagicaVoxel installation and run:

./MagicaVoxel.app/Contents/MacOS/MagicaVoxel

Compile errors will be logged whenever the shader is selected in the UI. At the moment, the line numbers do not correspond to the source file.

Windows

Not yet determined.

Editing

Changing MagicaVoxel's default shader directory

By default, shaders are loaded from MagicaVoxel's shader director. However, you can set a new path by changing the dir_xs_shader parameter within config/config.txt and specifying the new path:

dir_xs_shader : "/Users/lachlan/magicavoxel-shaders/shader"

Visual Studio Code

In Visual Studio Code, GLSL shader syntax-highlighting can be enabled with the Shader languages support for VS Code extension.

.txt files are not automatically detected as shaders. The following snippet can be added to the workspace settings to override the associations for .txt files:

Preferences: Open Workspace Settings (JSON)

{
	"files.associations": {
		"**/*.txt": "glsl"
	}
}
Clone this wiki locally