|
@@ -1,56 +1,252 @@
|
|
|
.. _doc_shader_preprocessor:
|
|
|
|
|
|
-Shader Preprocessor
|
|
|
+Shader preprocessor
|
|
|
===================
|
|
|
|
|
|
-The shader preprocessor is an optional step before shader compilation of text shaders in Godot.
|
|
|
-If you don't need it, you may ignore it, but it contains some useful macros which may speed up your productivity.
|
|
|
+Why use a shader preprocessor?
|
|
|
+------------------------------
|
|
|
+
|
|
|
+In programming languages, a *preprocessor* allows changing the code before the
|
|
|
+compiler reads it. Unlike the compiler, the preprocessor does not care about
|
|
|
+whether the syntax of the preprocessed code is valid. The preprocessor always
|
|
|
+performs what the *directives* tell it to do. A directive is a statement
|
|
|
+starting with a hash symbol (``#``). It is not a *keyword* of the shader
|
|
|
+language (such as ``if`` or ``for``), but a special kind of token within the
|
|
|
+language.
|
|
|
+
|
|
|
+From Godot 4.0 onwards, you can use a shader preprocessor within text-based
|
|
|
+shaders. The syntax is similar to what most GLSL shader compilers support
|
|
|
+(which in turn is similar to the C/C++ preprocessor).
|
|
|
+
|
|
|
+.. note::
|
|
|
+
|
|
|
+ The shader preprocessor is not available in :ref:`visual shaders <doc_visual_shaders>`.
|
|
|
+ If you need to introduce preprocessor statements to a visual shader, you can
|
|
|
+ convert it to a text-based shader using the **Convert to Shader** option in
|
|
|
+ the VisualShader inspector resource dropdown. This conversion is a one-way
|
|
|
+ operation; text shaders cannot be converted back to visual shaders.
|
|
|
+
|
|
|
+Directives
|
|
|
+----------
|
|
|
+
|
|
|
+General syntax
|
|
|
+^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+- Preprocessor directives do not use brackets (``{}``), but can use parentheses.
|
|
|
+- Preprocessor directives **never** end with semicolons.
|
|
|
+- Preprocessor directives can span several lines by ending each line with a
|
|
|
+ blackslash (``\``). The first line break *not* featuring a backslash will end
|
|
|
+ the preprocessor statement.
|
|
|
|
|
|
#define
|
|
|
^^^^^^^
|
|
|
-\ **Syntax:** `#define identifier <replacement_code>`.
|
|
|
+\ **Syntax:** ``#define <identifier> [replacement_code]``.
|
|
|
+
|
|
|
+Defines the identifier after that directive as a macro, and replaces all
|
|
|
+successive occurrences of it with the replacement code given in the shader.
|
|
|
+Replacement is performed on a "whole words" basis, which means no replacement is
|
|
|
+performed if the string is part of another string (without any spaces separating
|
|
|
+it).
|
|
|
+
|
|
|
+Defines with replacements may also have one or more *arguments*, which can then
|
|
|
+be passed when referencing the define (similar to a function call).
|
|
|
|
|
|
-Defines the identifier after that directive as a macro, and replaces all successive occurrence of it with the replacement code given in the shader.
|
|
|
-If the replacement code is not defined, it may only be used within `#ifdef` or `#ifndef` directives.
|
|
|
+If the replacement code is not defined, the identifier may only be used with
|
|
|
+``#ifdef` or ``#ifndef`` directives.
|
|
|
+
|
|
|
+Compared to constants (``const CONSTANT = value;``), ``#define``s can be used
|
|
|
+anywhere within the shader. ``#define``s can also be used to insert arbitrary
|
|
|
+shader code at any location, while constants can't do that.
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
|
shader_type spatial;
|
|
|
|
|
|
+ // Notice the lack of semicolon at the end of the line, as the replacement text
|
|
|
+ // shouldn't insert a semicolon on its own.
|
|
|
#define USE_MY_COLOR
|
|
|
#define MY_COLOR vec3(1, 0, 0)
|
|
|
|
|
|
+ // Replacement with arguments.
|
|
|
+ // All arguments are required (no default values can be provided).
|
|
|
+ #define BRIGHTEN_COLOR(r, g, b) vec3(r + 0.5, g + 0.5, b + 0.5)
|
|
|
+
|
|
|
+ // Multiline replacement using backslashes for continuation:
|
|
|
+ #define SAMPLE(param1, param2, param3, param4) long_function_call( \
|
|
|
+ param1, \
|
|
|
+ param2, \
|
|
|
+ param3, \
|
|
|
+ param4 \
|
|
|
+ )
|
|
|
+
|
|
|
void fragment() {
|
|
|
#ifdef USE_MY_COLOR
|
|
|
ALBEDO = MY_COLOR;
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+Defining a ``#define`` for an identifier that is already defined results in an
|
|
|
+error. To prevent this, use ``#undef <identifier>``.
|
|
|
+
|
|
|
+#undef
|
|
|
+^^^^^^
|
|
|
+
|
|
|
+**Syntax:** ``#undef identifier``
|
|
|
+
|
|
|
+The ``#undef`` directive may be used to cancel a previously defined ``#define`` directive:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define MY_COLOR vec3(1, 0, 0)
|
|
|
+
|
|
|
+ vec3 get_red_color() {
|
|
|
+ return MY_COLOR;
|
|
|
+ }
|
|
|
+
|
|
|
+ #undef MY_COLOR
|
|
|
+ #define MY_COLOR vec3(0, 1, 0)
|
|
|
+
|
|
|
+ vec3 get_green_color() {
|
|
|
+ return MY_COLOR;
|
|
|
+ }
|
|
|
+
|
|
|
+Without ``#undef`` in the above example, there would be a macro redefinition error.
|
|
|
+
|
|
|
#if
|
|
|
^^^
|
|
|
-\ **Syntax:** `#if condition`.
|
|
|
|
|
|
-The `#if` directive checks the condition and if it evaluates to a non-zero value, the code block is included, otherwise it is skipped.
|
|
|
-In order to evaluate, the condition must be an expression giving a simple floating-point, integer or boolean result. There may be multiple condition blocks connected by `||` or `&&` operators.
|
|
|
-It may be continued by a `#else` block, but must be ended with the `#endif` directive.
|
|
|
+**Syntax:** ``#if <condition>``
|
|
|
+
|
|
|
+The ``#if`` directive checks whether the ``condition`` passed. If it evaluates
|
|
|
+to a non-zero value, the code block is included, otherwise it is skipped.
|
|
|
+
|
|
|
+To evaluate correctly, the condition must be an expression giving a simple
|
|
|
+floating-point, integer or boolean result. There may be multiple condition
|
|
|
+blocks connected by ``&&`` (AND) or ``||`` (OR) operators. It may be continued
|
|
|
+by a ``#else`` block, but **must** be ended with the ``#endif`` directive.
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
|
#define VAR 3
|
|
|
- #define USE_LIGHT 0 // evaluates to false
|
|
|
- #define USE_COLOR 1 // evaluates to true
|
|
|
+ #define USE_LIGHT 0 // Evaluates to `false`.
|
|
|
+ #define USE_COLOR 1 // Evaluates to `true`.
|
|
|
|
|
|
#if VAR == 3 && (USE_LIGHT || USE_COLOR)
|
|
|
+ // Condition is `true`. Include this portion in the final shader.
|
|
|
+ #endif
|
|
|
+
|
|
|
+Using the ``defined()`` *preprocessor function*, you can check whether the
|
|
|
+passed identifier is defined a by ``#define`` placed above that directive. This
|
|
|
+is useful for creating multiple shader versions in the same file. It may be
|
|
|
+continued by a `#else` block, but must be ended with the ``#endif`` directive.
|
|
|
+
|
|
|
+The ``defined()`` function's result can be negated by using the ``!`` (boolean NOT)
|
|
|
+symbol in front of it. This can be used to check whether a define is *not* set.
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define USE_LIGHT
|
|
|
+ #define USE_COLOR
|
|
|
+
|
|
|
+ // Correct syntax:
|
|
|
+ #if defined(USE_LIGHT) || defined(USE_COLOR) || !defined(USE_REFRACTION)
|
|
|
+ // Condition is `true`. Include this portion in the final shader.
|
|
|
+ #endif
|
|
|
+
|
|
|
+Be careful, as ``defined()`` must only wrap a single identifier within parentheses, never more:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ // Incorrect syntax (parentheses are not placed where they should be):
|
|
|
+ #if defined(USE_LIGHT || USE_COLOR || !USE_REFRACTION)
|
|
|
+ // This will cause an error or not behave as expected.
|
|
|
+ #endif
|
|
|
|
|
|
+.. tip::
|
|
|
|
|
|
+ In the shader editor, preprocessor branches that evaluate to ``false`` (and
|
|
|
+ are therefore excluded from the final compiled shader) will appear grayed
|
|
|
+ out. This does not apply to run-time ``if`` statements.
|
|
|
+
|
|
|
+**#if preprocessor versus if statement: Performance caveats**
|
|
|
+
|
|
|
+The :ref:`shading language <doc_shading_language>` supports run-time ``if`` statements:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ uniform bool USE_LIGHT = true;
|
|
|
+
|
|
|
+ if (USE_LIGHT) {
|
|
|
+ // This part is included in the compiled shader, and always run.
|
|
|
+ } else {
|
|
|
+ // This part is included in the compiled shader, but never run.
|
|
|
+ }
|
|
|
+
|
|
|
+If the uniform is never changed, this behaves identical to the following usage
|
|
|
+of the ``#if`` preprocessor statement:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define USE_LIGHT
|
|
|
+
|
|
|
+ #if defined(USE_LIGHT)
|
|
|
+ // This part is included in the compiled shader, and always run.
|
|
|
+ #else
|
|
|
+ // This part is *not* included in the compiled shader (and therefore never run).
|
|
|
+ #endif
|
|
|
+
|
|
|
+However, the ``#if`` variant can be faster in certain scenarios. This is because
|
|
|
+all run-time branches in a shader are still compiled and variables within
|
|
|
+those branches may still take up register space, even if they are never run in
|
|
|
+practice.
|
|
|
+
|
|
|
+`Modern GPUs are quite effective at performing "static" branching <https://medium.com/@jasonbooth_86226/branching-on-a-gpu-18bfc83694f2>`__.
|
|
|
+"Static" branching refers to ``if`` statements where *all* pixels/vertices
|
|
|
+evaluate to the same result in a given shader invocation. However, high amounts
|
|
|
+of :abbr:`VGPR (Vector General-Purpose Register)`s (which can be caused by
|
|
|
+having too many branches) can still slow down shader execution significantly.
|
|
|
+
|
|
|
+#elif
|
|
|
+^^^^^
|
|
|
+
|
|
|
+The ``#elif`` directive stands for "else if" and checks the condition passed if
|
|
|
+the above ``#if`` evaluated to ``false``. ``#elif`` can only be used within an
|
|
|
+``#if`` block. It is possible to use several ``#elif``s in the same ``#if`` statement.
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define VAR 3
|
|
|
+ #define USE_LIGHT 0 // Evaluates to `false`.
|
|
|
+ #define USE_COLOR 1 // Evaluates to `true`.
|
|
|
+
|
|
|
+ #if VAR == 3 && (USE_LIGHT || USE_COLOR)
|
|
|
+ // Condition is `true`. Include this portion in the final shader.
|
|
|
+ #endif
|
|
|
+
|
|
|
+Like with ``#if``, the ``defined()`` preprocessor function can be used:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define SHADOW_QUALITY_MEDIUM
|
|
|
+
|
|
|
+ #if defined(SHADOW_QUALITY_HIGH)
|
|
|
+ // High shadow quality.
|
|
|
+ #elif defined(SHADOW_QUALITY_MEDIUM)
|
|
|
+ // Medium shadow quality.
|
|
|
+ #else
|
|
|
+ // Low shadow quality.
|
|
|
#endif
|
|
|
|
|
|
#ifdef
|
|
|
^^^^^^
|
|
|
-\ **Syntax:** `#ifdef identifier`.
|
|
|
|
|
|
-Checks whether the passed identifier is defined by `#define` before that directive. Useful for creating multiple shader versions in the same file.
|
|
|
-It may be continued by a `#else` block, but must be ended with the `#endif` directive.
|
|
|
+**Syntax:** ``#ifdef <identifier>``
|
|
|
+
|
|
|
+This is a shorthand for ``#if defined(...)``. Checks whether the passed
|
|
|
+identifier is defined by ``#define`` placed above that directive. This is useful
|
|
|
+for creating multiple shader versions in the same file. It may be continued by a
|
|
|
+``#else`` block, but must be ended with the `#endif` directive.
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
@@ -59,71 +255,130 @@ It may be continued by a `#else` block, but must be ended with the `#endif` dire
|
|
|
|
|
|
#endif
|
|
|
|
|
|
+The processor does *not* support ``#elifdef`` as a shortcut for ``#elif defined(...)``.
|
|
|
+Instead, use the following series of ``#ifdef`` and ``#else`` when you need more
|
|
|
+than two branches:
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define SHADOW_QUALITY_MEDIUM
|
|
|
+
|
|
|
+ #ifdef SHADOW_QUALITY_HIGH
|
|
|
+ // High shadow quality.
|
|
|
+ #else
|
|
|
+ #ifdef SHADOW_QUALITY_MEDIUM
|
|
|
+ // Medium shadow quality.
|
|
|
+ #else
|
|
|
+ // Low shadow quality.
|
|
|
+ #endif // This ends `SHADOW_QUALITY_MEDIUM`'s branch.
|
|
|
+ #endif // This ends `SHADOW_QUALITY_HIGH`'s branch.
|
|
|
+
|
|
|
#ifndef
|
|
|
^^^^^^^
|
|
|
-\ **Syntax:** `#ifndef identifier`.
|
|
|
|
|
|
-Similar to `#ifdef` but checks whether the passed identifier is not defined by `#define` before that directive.
|
|
|
+**Syntax:** ``#ifndef <identifier>``
|
|
|
+
|
|
|
+This is a shorthand for ``#if !defined(...)``. Similar to ``#ifdef``, but checks
|
|
|
+whether the passed identifier is **not** defined by `#define` before that
|
|
|
+directive.
|
|
|
+
|
|
|
+This is the exact opposite of ``#ifdef``; it will always match in situations
|
|
|
+where ``#ifdef`` would never match, and vice versa.
|
|
|
+
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ #define USE_LIGHT
|
|
|
+
|
|
|
+ #ifndef USE_LIGHT
|
|
|
+ // Evaluates to `false`. This portion won't be included in the final shader.
|
|
|
+ #endif
|
|
|
+
|
|
|
+ #ifndef USE_COLOR
|
|
|
+ // Evaluates to `true`. This portion will be included in the final shader.
|
|
|
+ #endif
|
|
|
|
|
|
#else
|
|
|
^^^^^
|
|
|
-\ **Syntax:** `#else`.
|
|
|
|
|
|
-Defines the optional block which is included when the previously defined `#if`, `#ifdef` or `#ifndef` directive evaluates to false.
|
|
|
+**Syntax:** ``#else``
|
|
|
+
|
|
|
+Defines the optional block which is included when the previously defined `#if`,
|
|
|
+``#ifdef` or `#ifndef`` directive evaluates to false.
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
|
shader_type spatial;
|
|
|
|
|
|
- #define MY_COLOR vec3(1, 0, 0)
|
|
|
+ #define MY_COLOR vec3(1.0, 0, 0)
|
|
|
|
|
|
void fragment() {
|
|
|
#ifndef MY_COLOR
|
|
|
ALBEDO = MY_COLOR;
|
|
|
#else
|
|
|
- ALBEDO = vec3(0, 0, 1);
|
|
|
+ ALBEDO = vec3(0, 0, 1.0);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
^^^^^^
|
|
|
-\ **Syntax:** `#endif`.
|
|
|
|
|
|
-Used as terminator for the `#if`, `#ifdef`, `#ifndef` or subsequent `#else` directives.
|
|
|
+**Syntax:** ``#endif``
|
|
|
|
|
|
-#undef
|
|
|
-^^^^^^
|
|
|
-\ **Syntax:** `#undef identifier`.
|
|
|
+Used as terminator for the ``#if``, ``#`ifdef``, ``#ifndef`` or subsequent ``#else`` directives.
|
|
|
|
|
|
-The `#undef` directive may be used to cancel the previously defined `#define` directive:
|
|
|
+#include
|
|
|
+^^^^^^^^
|
|
|
+\ **Syntax:** `#include "path"`.
|
|
|
|
|
|
-.. code-block:: glsl
|
|
|
+The ``#include`` directive includes the *entire* content of a shader include
|
|
|
+file in a shader. ``"path"`` can be an absolute ``res://`` path or relative to
|
|
|
+the current shader file. Relative paths are only allowed in shaders that are
|
|
|
+saved to ``.gdshader`` or ``.gdshaderinc`` files, while absolute paths can be
|
|
|
+used in shaders that are built into a scene/resource file.
|
|
|
|
|
|
- #define MY_COLOR vec3(1, 0, 0)
|
|
|
+This directive may be used in any place, but is recommended at
|
|
|
+the beginning of the shader file, after the ``shader_type`` to prevent possible
|
|
|
+errors. The shader include may be created by using a **File > Create Shader
|
|
|
+Include** menu option of the shader editor.
|
|
|
|
|
|
- vec3 get_red_color() {
|
|
|
- return MY_COLOR;
|
|
|
- }
|
|
|
+``#include`` is useful for creating libraries of helper functions (or macros)
|
|
|
+and reducing code duplication. When using ``#include``, be careful about naming
|
|
|
+collisions, as redefining functions or macros is not allowed.
|
|
|
|
|
|
- #undef MY_COLOR
|
|
|
- #define MY_COLOR vec3(0, 1, 0)
|
|
|
+``#include`` is subject to several restrictions:
|
|
|
|
|
|
- vec3 get_green_color() {
|
|
|
- return MY_COLOR;
|
|
|
- }
|
|
|
+- Only shader include resources (ending with ``.gdshaderinc``) can be included.
|
|
|
+ ``.gdshader`` files cannot be included by another shader, but a
|
|
|
+ ``.gdshaderinc`` file can include other ``.gdshaderinc`` files.
|
|
|
+- Cyclic dependencies are **not** allowed and will result in an error.
|
|
|
+- To avoid infinite recursion, include depth is limited to 25 steps.
|
|
|
|
|
|
-Without `#undef` in that case there will be a macro redefinition error.
|
|
|
+Example shader include file:
|
|
|
|
|
|
-#include
|
|
|
-^^^^^^^^
|
|
|
-\ **Syntax:** `#include "path"`.
|
|
|
+.. code-block:: glsl
|
|
|
+
|
|
|
+ // fancy_color.gdshaderinc
|
|
|
+
|
|
|
+ // While technically allowed, there is usually no `shader_type` declaration in include files.
|
|
|
+
|
|
|
+ vec3 get_fancy_color() {
|
|
|
+ return vec3(0.3, 0.6, 0.9);
|
|
|
+ }
|
|
|
|
|
|
-The `#include` directive includes the content of shader include to a shader. It may be used in any place, but is recommended at the beginning of the shader file,
|
|
|
-after the `shader_type` to prevent possible errors. The shader include may be created by using a `File → Create Shader Include` menu option of the shader editor.
|
|
|
+Example base shader (using the include file we created above):
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
|
- #include "my_shader_inc.gdshaderinc"
|
|
|
+ // material.gdshader
|
|
|
+
|
|
|
+ shader_type spatial;
|
|
|
+
|
|
|
+ #include "res://fancy_color.gdshaderinc"
|
|
|
+
|
|
|
+ void fragment() {
|
|
|
+ // No error, as we've included a definition for `get_fancy_color()` via the shader include.
|
|
|
+ COLOR = get_fancy_color();
|
|
|
+ }
|
|
|
|
|
|
#pragma
|
|
|
^^^^^^^
|
|
@@ -131,9 +386,15 @@ after the `shader_type` to prevent possible errors. The shader include may be cr
|
|
|
|
|
|
The `#pragma` directive provides additional information to the preprocessor or compiler.
|
|
|
|
|
|
-Currently, it may have only one value: `disable_preprocessor`.
|
|
|
-If you don't need the preprocessor, use that directive, and it will speed up the shader compilation by excluding the preprocessor step.
|
|
|
+Currently, it may have only one value: ``disable_preprocessor``. If you don't need
|
|
|
+the preprocessor, use that directive to speed up shader compilation by excluding
|
|
|
+the preprocessor step.
|
|
|
|
|
|
.. code-block:: glsl
|
|
|
|
|
|
#pragma disable_preprocessor
|
|
|
+
|
|
|
+ #if USE_LIGHT
|
|
|
+ // This causes a shader compilation error, as the `#if USE_LIGHT` and `#endif`
|
|
|
+ // are included as-is in the final shader code.
|
|
|
+ #endif
|