Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

x.attribute

셰이더 전처리기를 사용하는 이유는 무엇입니까?

프로그래밍 언어에서 *전처리기*를 사용하면 컴파일러가 코드를 읽기 전에 코드를 변경할 수 있습니다. 컴파일러와 달리 전처리기는 전처리된 코드의 구문이 유효한지 여부에 신경 쓰지 않습니다. 전처리기는 항상 *지시문*이 지시하는 작업을 수행합니다. 지시어는 해시 기호(#)로 시작하는 명령문입니다. 이는 셰이더 언어(예: if 또는 for)의 *키워드*가 아니라 언어 내의 특별한 종류의 토큰입니다.

반복을 방지하고 코드 재사용을 개선하려면 텍스트 기반 셰이더 내에서 셰이더 전처리기를 사용할 수 있습니다. 구문은 대부분의 GLSL 셰이더 컴파일러가 지원하는 것과 유사합니다(이는 C/C++ 전처리기와 유사합니다).

참고

셰이더 전처리기는 :ref:`visual 셰이더 <doc_visual_shaders>`에서 사용할 수 없습니다. 시각적 셰이더에 전처리기 문을 도입해야 하는 경우 VisualShader 검사기 리소스 드롭다운에서 셰이더로 변환 옵션을 사용하여 텍스트 기반 셰이더로 변환할 수 있습니다. 이 변환은 단방향 작업입니다. 텍스트 셰이더를 시각적 셰이더로 다시 변환할 수 없습니다.

방향

일반 구문

  • 전처리기 지시문은 대괄호({})를 사용하지 않지만 괄호를 사용할 수 있습니다.

  • 전처리기 지시어는 결코 세미콜론으로 끝나지 않습니다(``#define``는 허용되지만 잠재적으로 위험할 수 있음).

  • 전처리기 지시문은 각 줄을 백슬래시(\)로 마무리하여 여러 줄에 걸쳐 있을 수 있습니다. 백슬래시가 포함되지 않은 첫 번째 줄 바꿈은 전처리기 명령문을 종료합니다.

#정의

구문: #define <identifier> [replacement_code].

해당 지시문 뒤의 식별자를 매크로로 정의하고 연속적으로 나타나는 모든 항목을 셰이더에 지정된 대체 코드로 바꿉니다. 대체는 "전체 단어" 기준으로 수행됩니다. 즉, 문자열이 다른 문자열의 일부인 경우(공백이나 연산자를 구분하지 않은 경우) 대체가 수행되지 않음을 의미합니다.

대체가 포함된 정의에는 하나 이상의 *인수*가 있을 수 있으며, 이는 정의를 참조할 때 전달될 수 있습니다(함수 호출과 유사).

대체 코드가 정의되지 않은 경우 식별자는 #ifdef 또는 #ifndef 지시어에만 사용할 수 있습니다.

대체 코드에 연결 기호(##)가 있으면 매크로 삽입 시 주변 공백과 함께 해당 기호가 제거되고 주변 단어와 인수가 새 토큰에 결합됩니다.

uniform sampler2D material0;

#define SAMPLE(N) vec4 tex##N = texture(material##N, UV)

void fragment() {
    SAMPLE(0);
    ALBEDO = tex0.rgb;
}

상수(const CONSTANT = value;)와 비교하여 ``#define``는 셰이더 내 어디에서나 사용할 수 있습니다(균일한 힌트 포함). ``#define``를 사용하면 임의의 셰이더 코드를 임의의 위치에 삽입할 수도 있지만 상수는 그렇게 할 수 없습니다.

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.
// If the directive ends with a semicolon, the semicolon is inserted in every usage
// of the directive, even when this causes a syntax error.
#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
}

이미 정의된 식별자에 대해 ``#define``를 정의하면 오류가 발생합니다. 이를 방지하려면 ``#undef <identifier>``를 사용하세요.

#undef

구문: #undef identifier

#undef 지시어는 이전에 정의된 #define 지시어를 취소하는 데 사용될 수 있습니다.

#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;
}

// Like in most preprocessors, undefining a define that was not previously defined is allowed
// (and won't print any warning or error).
#undef THIS_DOES_NOT_EXIST

위의 예에서 ``#undef``가 없으면 매크로 재정의 오류가 발생합니다.

if

구문: #if <condition>

#if 지시어는 ``condition``가 통과되었는지 여부를 확인합니다. 0이 아닌 값으로 평가되면 코드 블록이 포함되고, 그렇지 않으면 건너뜁니다.

올바르게 평가하려면 조건은 간단한 부동 소수점, 정수 또는 부울 결과를 제공하는 표현식이어야 합니다. &&``(AND) 또는 ``||``(OR) 연산자로 연결된 여러 조건 블록이 있을 있습니다. ``#else 블록으로 계속될 수 있지만 반드시 #endif 지시문으로 끝나야 합니다.

#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

defined() *전처리기 기능*을 사용하면 전달된 식별자가 해당 지시어 위에 배치된 #define``에 의해 정의되었는지 확인할 있습니다. 이는 동일한 파일에 여러 셰이더 버전을 생성하는 유용합니다. ``#else 블록으로 계속될 수 있지만 #endif 지시문으로 끝나야 합니다.

defined() 함수의 결과는 함수 앞에 ``!``(부울 NOT) 기호를 사용하여 무효화할 수 있습니다. 이는 정의가 설정되지 않았는지 확인하는 데 사용할 수 있습니다.

#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

``defined()``는 괄호 안에 단일 식별자만 포함해야 하며 그 이상은 포함할 수 없으므로 주의하세요.

// 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

셰이더 편집기에서 false``로 평가되는(따라서 최종 컴파일된 셰이더에서 제외되는) 전처리기 분기는 회색으로 표시됩니다. 이는 런타임 ``if 문에는 적용되지 않습니다.

#if 전처리기 대 if 문: 성능 주의사항

shading 언어 <doc_shading_언어>`는 런타임 ``if` 문을 지원합니다.

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 전처리기 문의 다음 사용법과 동일하게 동작합니다.

#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

그러나 특정 시나리오에서는 #if 변형이 더 빠를 수 있습니다. 이는 셰이더의 모든 런타임 분기가 여전히 컴파일되고 해당 분기 내의 변수가 실제로 실행되지 않더라도 여전히 레지스터 공간을 차지할 수 있기 때문입니다.

최신 GPU는 "정적" 분기 수행 시 매우 효과적 <https://medium.com/@jasonbooth_86226/branching-on-a-gpu-18bfc83694f2>`__입니다. "정적" 분기는 *모든* 픽셀/정점이 지정된 셰이더 호출에서 동일한 결과로 평가되는 ``if` 문을 나타냅니다. 그러나 :abbr:`VGPR(벡터 일반 목적 레지스터)`(분기가 너무 많아서 발생할 수 있음)이 너무 많으면 여전히 셰이더 실행 속도가 크게 느려질 수 있습니다.

elif

#elif 지시문은 "else if"를 나타내며 위의 #if``가 ``false``로 평가되면 통과된 조건을 확인합니다. ``#elif``는 ``#if 블록 내에서만 사용할 수 있습니다. #if 문 다음에 여러 개의 #elif 문을 사용할 수 있습니다.

#define VAR 2

#if VAR == 0
// Not included.
#elif VAR == 1
// Not included.
#elif VAR == 2
// Condition is `true`. Include this portion in the final shader.
#else
// Not included.
#endif

#if``와 마찬가지로 ``defined() 전처리기 기능을 사용할 수 있습니다.

#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

구문: #ifdef <identifier>

이는 #if defined(...)``의 약어입니다. 전달된 식별자가 해당 지시어 위에 배치된 ``#define``에 의해 정의되었는지 확인합니다. 이는 동일한 파일에 여러 셰이더 버전을 생성하는 유용합니다. ``#else 블록으로 계속될 수 있지만 #endif 지시문으로 끝나야 합니다.

#define USE_LIGHT

#ifdef USE_LIGHT
// USE_LIGHT is defined. Include this portion in the final shader.
#endif

프로세서는 #elif defined(...)``에 대한 바로 가기로 ``#elifdef``를 지원하지 *않습니다*. 대신, 2개 이상의 분기가 필요한 경우 다음 ``#ifdef#else 시리즈를 사용하세요.

#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

구문: #ifndef <identifier>

이는 ``#if !defined(...)``의 약어입니다. ``#ifdef``와 유사하지만 전달된 식별자가 해당 지시문 앞의 ``#define``에 의해 정의되지 않았는지 확인합니다.

이는 ``#ifdef``와 정반대입니다. ``#ifdef``가 절대 일치하지 않는 상황에서는 항상 일치하며 그 반대의 경우도 마찬가지입니다.

#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

구문: #else

이전에 정의된 #if, #elif, #ifdef 또는 #ifndef 지시어가 false로 평가될 때 포함되는 선택적 블록을 정의합니다.

shader_type spatial;

#define MY_COLOR vec3(1.0, 0, 0)

void fragment() {
#ifdef MY_COLOR
    ALBEDO = MY_COLOR;
#else
    ALBEDO = vec3(0, 0, 1.0);
#endif
}

#endif

구문: #endif

#if, #ifdef, #ifndef 또는 후속 #else 지시문에 대한 종결자로 사용됩니다.

오류

구문: #error <message>

#error 지시문은 전처리기가 선택적 메시지와 함께 오류를 내보내도록 강제합니다. 예를 들어 #if 블록 내에서 정의된 값에 대한 엄격한 제한을 제공하는 데 유용합니다.

#define MAX_LOD 3
#define LOD 4

#if LOD > MAX_LOD
#error LOD exceeds MAX_LOD
#endif

#포함

구문: #include "path"

#include 지시문은 셰이더에 셰이더 포함 파일의 전체 콘텐츠를 포함합니다. "path"``는 절대 ``res:// 경로이거나 현재 셰이더 파일에 대한 상대 경로일 수 있습니다. 상대 경로는 .gdshader 또는 .gdshaderinc 파일에 저장된 셰이더에서만 허용되는 반면, 절대 경로는 씬/resource 파일에 내장된 셰이더에서 사용할 수 있습니다.

셰이더 편집기의 파일 > 만들기 셰이더 포함 메뉴 옵션을 사용하거나 파일 시스템 도크에서 새 ShaderInclude 리소스를 생성하여 새 셰이더 포함을 생성할 수 있습니다.

셰이더 포함은 파일의 어느 지점에서나 셰이더 또는 기타 셰이더 포함 내에서 포함될 수 있습니다.

셰이더가 셰이더의 전역 범위에 포함되는 경우 초기 shader_type 문 다음에 수행하는 것이 좋습니다.

셰이더에는 본문 내에서 함수가 포함됩니다. 셰이더 편집기는 작성된 컨텍스트 외부에서는 유효하지 않을 수 있으므로 셰이더 포함 코드에 대한 오류를 보고할 가능성이 높습니다. 이러한 오류를 무시하도록 선택하거나(셰이더는 여전히 잘 컴파일됨) 셰이더에서 정의를 확인하는 #ifdef 블록에 포함을 래핑할 수 있습니다.

``#include``는 도우미 함수(또는 매크로) 라이브러리를 생성하고 코드 중복을 줄이는 데 유용합니다. ``#include``를 사용할 때는 함수나 매크로를 재정의하는 것이 허용되지 않으므로 이름 충돌에 주의하세요.

``#include``에는 몇 가지 제한 사항이 적용됩니다.

  • 셰이더 포함 리소스(.gdshaderinc``로 끝남)만 포함될 있습니다. ``.gdshader 파일은 다른 셰이더에 포함될 수 없지만 .gdshaderinc 파일에는 다른 .gdshaderinc 파일이 포함될 수 있습니다.

  • 순환 종속성은 허용되지 않으며 오류가 발생합니다.

  • 무한 재귀를 방지하기 위해 포함 깊이는 25단계로 제한됩니다.

이런 예시로는...

// 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);
}

예제 베이스 셰이더(위에서 생성한 포함 파일 사용):

// 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 value

#pragma 지시문은 전처리기 또는 컴파일러에 추가 정보를 제공합니다.

현재는 ``disable_preprocessor``라는 하나의 값만 가질 수 있습니다. 전처리기가 필요하지 않은 경우 해당 지시어를 사용하면 전처리기 단계를 제외하여 셰이더 컴파일 속도를 높일 수 있습니다.

#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

정적 함수

현재 렌더러

Godot 4.4부터 내장 정의 CURRENT_RENDERER, RENDERER_COMPATIBILITY, RENDERER_MOBILE``RENDERER_FORWARD_PLUS``를 통해 현재 어떤 렌더러가 사용되는지 확인할 수 있습니다:

  • CURRENT_RENDERER``는 현재 렌더러에 따라 ``0, 1 또는 ``2``로 설정됩니다.

  • ``RENDERER_COMPATIBILITY``는 항상 ``0``입니다.

  • ``RENDERER_MOBILE``는 항상 ``1``입니다.

  • ``RENDERER_FORWARD_PLUS``는 항상 ``2``입니다.

예를 들어, 다음 셰이더는 ``ALBEDO``를 각 렌더러에서 다른 색상으로 설정합니다.

shader_type spatial;

void fragment() {
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
    ALBEDO = vec3(0.0, 0.0, 1.0);
#elif CURRENT_RENDERER == RENDERER_MOBILE
    ALBEDO = vec3(1.0, 0.0, 0.0);
#else // CURRENT_RENDERER == RENDERER_FORWARD_PLUS
    ALBEDO = vec3(0.0, 1.0, 0.0);
#endif
}