mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-05 22:09:56 +03:00
Improve Shader preprocessor documentation (#6563)
This commit is contained in:
@@ -1,101 +1,100 @@
|
||||
.. _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 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.
|
||||
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).
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#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.
|
||||
|
||||
.. 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)
|
||||
|
||||
|
||||
#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.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
#define USE_LIGHT
|
||||
#ifdef USE_LIGHT
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef
|
||||
^^^^^^^
|
||||
\ **Syntax:** `#ifndef identifier`.
|
||||
|
||||
Similar to `#ifdef` but checks whether the passed identifier is not defined by `#define` before that directive.
|
||||
|
||||
#else
|
||||
^^^^^
|
||||
\ **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)
|
||||
|
||||
void fragment() {
|
||||
#ifndef MY_COLOR
|
||||
ALBEDO = MY_COLOR;
|
||||
#else
|
||||
ALBEDO = vec3(0, 0, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
^^^^^^
|
||||
\ **Syntax:** `#endif`.
|
||||
|
||||
Used as terminator for the `#if`, `#ifdef`, `#ifndef` or subsequent `#else` directives.
|
||||
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 the previously defined `#define` directive:
|
||||
**Syntax:** ``#undef identifier``
|
||||
|
||||
The ``#undef`` directive may be used to cancel a previously defined ``#define`` directive:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
@@ -112,18 +111,274 @@ The `#undef` directive may be used to cancel the previously defined `#define` di
|
||||
return MY_COLOR;
|
||||
}
|
||||
|
||||
Without `#undef` in that case there will be a macro redefinition error.
|
||||
Without ``#undef`` in the above example, there would be a macro redefinition error.
|
||||
|
||||
#if
|
||||
^^^
|
||||
|
||||
**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`.
|
||||
|
||||
#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>``
|
||||
|
||||
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
|
||||
|
||||
#define USE_LIGHT
|
||||
#ifdef USE_LIGHT
|
||||
|
||||
#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>``
|
||||
|
||||
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.
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
shader_type spatial;
|
||||
|
||||
#define MY_COLOR vec3(1.0, 0, 0)
|
||||
|
||||
void fragment() {
|
||||
#ifndef MY_COLOR
|
||||
ALBEDO = MY_COLOR;
|
||||
#else
|
||||
ALBEDO = vec3(0, 0, 1.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
^^^^^^
|
||||
|
||||
**Syntax:** ``#endif``
|
||||
|
||||
Used as terminator for the ``#if``, ``#`ifdef``, ``#ifndef`` or subsequent ``#else`` directives.
|
||||
|
||||
#include
|
||||
^^^^^^^^
|
||||
\ **Syntax:** `#include "path"`.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
``#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.
|
||||
|
||||
``#include`` is subject to several restrictions:
|
||||
|
||||
- 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.
|
||||
|
||||
Example shader include file:
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
#include "my_shader_inc.gdshaderinc"
|
||||
// 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);
|
||||
}
|
||||
|
||||
Example base shader (using the include file we created above):
|
||||
|
||||
.. code-block:: glsl
|
||||
|
||||
// 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
|
||||
|
||||
@@ -512,7 +512,7 @@ Godot Shading language supports the most common types of flow control:
|
||||
|
||||
} while (cond);
|
||||
|
||||
Keep in mind that, in modern GPUs, an infinite loop can exist and can freeze
|
||||
Keep in mind that in modern GPUs, an infinite loop can exist and can freeze
|
||||
your application (including editor). Godot can't protect you from this, so be
|
||||
careful not to make this mistake!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user