Shady is a GLSL preprocessor tool for GameMaker that allows you to include pieces of code from other shaders and generate shader variants for code reuse!
The tool is integrated into the compilation process via compiler scripts so you can write reusable shaders inside standard GameMaker shader files with built-in or any other code editor.
It is still in Beta and tested to work with GLSL ES language but should also work with GLSL. HLSL is not (yet?) supported.
- Download the latest executable from the releases page for your OS and architecture.
- Create a directory inside your project location alongside other resource directories and name it, for example,
#shady
. - Place the executable inside
#shady
directory. - Create or modify compiler scripts in your project location to include the code:
Windows Batch Files
pre_build_step.bat
"%~dp0\#shady\Shady" "%~dp0." --pre
post_textures.bat
"%~dp0\#shady\Shady" "%~dp0." --post
Linux / MacOS Shell Scripts
pre_build_step.sh
#!/bin/bash
${0%/*}/#shady/Shady ${0%/*} --pre
post_textures.sh
#!/bin/bash
${0%/*}/#shady/Shady ${0%/*} --post
- You may also want to add these lines to
.gitignore
to remove temp Shady files from Git:
*.fsh_mod
*.vsh_mod
Shady is using a custom #pragma
syntax with special directives. This isn't breaking the standard shader compiler as unknown custom pragmas are just ignored by it.
You can write shady directives right in the GameMaker shader files, both vertex and fragment.
Vertex and fragment shaders have separate databases so you can't import identifiers from vertex shader into fragment shader.
Note
Shader files that are only used as a library to import to other shaders are still required to have a main
function (possibly blank) to not generate errors on compilation.
Although, as those shaders are compiled and presented in the project it is possible to utilize them for something useful in the game.
Syntax:
#pragma shady: import(shader_name)
#pragma shady: import(shader_name.identifier_name)
Example
sh_functions.fsh
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
#define GRAYSCALE_FACTOR vec3(0.2126, 0.7152, 0.0722)
vec4 grayscale(vec4 color) {
return vec4(vec3(dot(color.rgb, GRAYSCALE_FACTOR)), color.a);
}
const vec2 textureScale = vec2(4096.0 / 1920.0, 4096.0 / 1080.0);
void main() {}
sh_shader.fsh
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
#pragma shady: import(sh_functions) // import everything from sh_functions (random, GRAYSCALE_FACTOR, grayscale, textureScale)
#pragma shady: import(sh_functions.random) // import specific function (random)
#pragma shady: import(sh_functions.textureScale) // import specific variable (textureScale)
void main() {
vec4 color = texture2D(gm_BaseTexture, v_vTexcoord);
gl_FragColor = v_vColour * grayscale(color); // you can then use imported stuff like it's there
}
You can import functions, variables and #define
s. varying
s, uniform
s and main
function are not exported.
Nested imports are also supported, so A
imports B
which imports C
, with duplicate imports resolved.
Syntax:
#pragma shady: macro_begin MACRO_NAME
#pragma shady: macro_end
#pragma shady: inline(shader_name.MACRO_NAME)
Example
sh_macros.fsh
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main() {
#pragma shady: macro_begin FRAGCOLOR
gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
#pragma shady: macro_end
}
sh_shader.fsh
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main() {
#pragma shady: inline(sh_macros.FRAGCOLOR) // inline code from sh_macros FRAGCOLOR macro
}
Nested macros are also supported, so this code:
#pragma shady: macro_begin INVERSE_GRAYSCALE
#pragma shady: macro_begin INVERSE
gl_FragColor = vec4(vec3(1.0 - gl_FragColor.rgb), gl_FragColor.a);
#pragma shady: macro_end
#pragma shady: macro_begin GRAYSCALE
gl_FragColor = grayscale(gl_FragColor);
#pragma shady: macro_end
#pragma shady: macro_end
Will generate three macros INVERSE_GRAYSCALE
, INVERSE
and GRAYSCALE
that will all work.
Syntax:
#pragma shady: variant(shader_name, [KEYWORD_NAME1], [KEYWORD_NAME2], ...)
Example
sh_megashader.fsh
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
#pragma shady: import(sh_effects)
void main()
{
// use #ifdef, #if defined() or #elif defined() to define variant keywords
#ifdef BLUR
vec4 color = texture2DBlur(gm_BaseTexture, v_vTexcoord);
#else
vec4 color = texture2D(gm_BaseTexture, v_vTexcoord);
#endif
#ifdef NOISE
color = noise(color);
#endif
#ifdef DARKEN
color = darken(color);
#endif
gl_FragColor = v_vColour * color;
}
sh_shader_variant.fsh
#pragma shady: variant(sh_shader_base, BLUR, DARKEN) // will generate a variant of sh_megashader with BLUR and DARKEN enabled
// any code after is ignored and will be replaced with the source shader code with enabled keywords
The variant directive with no keywords will create the exact copy of the original shader, which may be useful for generating code that shares the same vertex shader, for example.
The original shader can also be used as normal.
-
Defendertm is too defensive. Some anti-viruses may yield a false-positive warning on the binaries. There's nothing I can do for now besides waiting for binaries to get trusted over time or getting a paid code sign certificate, which is not cost-effective for the current state of the project. If you're not sure, you can compile the tool yourself from sources using Visual Studio and .NET 8.0.
-
"Project Directory Modified". To not see that GameMaker message-box every time any shader is processed navigate to
Preferences
>General Settings
and enableAutomatically reload changed files
.
- Xpanda – uses a custom syntax and is not integrated with the compilation process but supports HLSL.
- glslfy – older brother for non-GameMaker users.
- Unity Shader Variants.
Optimize caching to not rewrite unmodified shaders.- Support more shader languages?
Nikita Musatov - MusNik / KeeVee Games