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.
Checking the stable version of the documentation...
당신의 첫 게임
귀하는 자신만의 맞춤형 Spatial 셰이더 작성을 시작하기로 결정했습니다. 어쩌면 온라인에서 셰이더로 수행된 멋진 트릭을 보셨거나 :ref:`StandardMaterial3D <class_StandardMaterial3D>`이 귀하의 요구 사항을 충족시키지 못한다는 사실을 발견하셨을 수도 있습니다. 어느 쪽이든, 당신은 직접 작성하기로 결정했으며 이제 어디서부터 시작해야 할지 파악해야 합니다.
이 튜토리얼에서는 Spatial 셰이더를 작성하는 방법을 설명하고 CanvasItem 튜토리얼보다 더 많은 주제를 다룰 것입니다.
Spatial 셰이더에는 CanvasItem 셰이더보다 더 많은 기능이 내장되어 있습니다. 공간 셰이더에 대한 기대는 Godot가 이미 일반적인 사용 사례에 대한 기능을 제공했으며 셰이더에서 사용자가 수행해야 하는 모든 작업은 적절한 매개변수를 설정한다는 것입니다. 이는 특히 PBR(물리 기반 렌더링) 워크플로우의 경우에 해당됩니다.
이것은 두 부분으로 구성된 튜토리얼입니다. 첫 번째 부분에서는 정점 함수의 하이트맵에서 정점 변위를 사용하여 지형을 생성합니다. :ref:`두 번째 부분 <doc_your_second_spatial_shader>`에서는 이 튜토리얼의 개념을 가져와 바닷물 셰이더를 작성하여 조각 셰이더에 사용자 정의 재질을 설정합니다.
참고
이 튜토리얼에서는 유형(vec2, float, sampler2D) 및 기능과 같은 몇 가지 기본 셰이더 지식을 가정합니다. 이러한 개념이 불편하다면 이 튜토리얼을 완료하기 전에 `셰이더 <https://thebookofshaders.com>`_의 책에서 간단한 소개를 받는 것이 가장 좋습니다.
내 자료를 할당할 위치
3D에서는 :ref:`Meshes <class_Mesh>`을 사용하여 객체를 그립니다. 메시는 "표면"이라는 단위에 형상(객체의 모양)과 재료(색상 및 객체가 빛에 반응하는 방식)를 저장하는 리소스 유형입니다. 메시는 여러 개의 표면을 가질 수도 있고 하나만 가질 수도 있습니다. 일반적으로 다른 프로그램(예: Blender)에서 메시를 가져옵니다. 그러나 Godot에는 메시를 가져오지 않고도 씬에 기본 형상을 추가할 수 있는 몇 가지 :ref:`PrimitiveMeshes <class_primitivemesh>`도 있습니다.
메시를 그리는 데 사용할 수 있는 노드 유형은 여러 가지가 있습니다. 주요 항목은 MeshInstance3D, MultiMeshes 포함)을 사용할 수도 있습니다. 다른 사람.
일반적으로 재질은 메시의 특정 표면과 연결되지만 MeshInstance3D와 같은 일부 노드를 사용하면 특정 표면 또는 모든 표면에 대한 재질을 재정의할 수 있습니다.
표면이나 메시 자체에 재질을 설정하면 해당 메시를 공유하는 모든 MeshInstance3D가 해당 재질을 공유합니다. 그러나 여러 메시 인스턴스에서 동일한 메시를 재사용하고 싶지만 각 인스턴스마다 재질이 다른 경우 MeshInstance3D에서 재질을 설정해야 합니다.
이 튜토리얼에서는 재료를 재정의하는 MeshInstance3D 기능을 활용하는 대신 메시 자체에 재료를 설정합니다.
설정하기
씬에 새로운 MeshInstance3D 노드를 추가합니다.
Inspector 탭에서 ``<empty>``를 클릭하고 New PlaneMesh**를 선택하여 MeshInstance3D의 **Mesh 속성을 새로운 PlaneMesh 리소스로 설정합니다. 그런 다음 나타나는 비행기 이미지를 클릭하여 리소스를 확장하세요.
이것으로 Mob 씬이 완성되었습니다.
그런 다음 뷰포트에서 왼쪽 상단 모서리에 있는 Perspective 버튼을 클릭합니다. 표시되는 메뉴에서 **와이어프레임 표시**를 선택합니다.
이렇게 하면 평면을 구성하는 삼각형을 볼 수 있습니다.
이제 :ref:`PlaneMesh <class_planemesh>`의 세분화 너비 및 **세분화 깊이**를 ``32``로 설정합니다.
이제 :ref:`MeshInstance3D<class_MeshInstance3D>`에 더 많은 삼각형이 있음을 알 수 있습니다. 이렇게 하면 작업할 정점이 더 많아지고 더 많은 세부 정보를 추가할 수 있습니다.
:ref:`PrimitiveMeshes <class_primitivemesh>`는 PlaneMesh와 마찬가지로 표면이 하나만 있으므로 재료 배열 대신 하나만 있습니다. **Material**을 새 ShaderMaterial로 설정한 다음 나타나는 구를 클릭하여 재질을 확장합니다.
참고
Material 리소스에서 상속된 재질(예: StandardMaterial3D 및 ParticleProcessMaterial)은 :ref:`class_ShaderMaterial`로 변환될 수 있으며 해당 기존 속성은 함께 제공되는 텍스트 셰이더로 변환됩니다. 이렇게 하려면 FileSystem 도크에서 재질을 마우스 오른쪽 버튼으로 클릭하고 **ShaderMaterial로 변환**을 선택합니다. 인스펙터에서 머티리얼에 대한 참조를 보유하는 속성을 마우스 오른쪽 버튼으로 클릭하여 이를 수행할 수도 있습니다.
이제 ``<empty>``를 클릭하고 **새 셰이더...**를 선택하여 재료의 **셰이더**를 새 셰이더로 설정합니다. 기본 설정을 그대로 두고 셰이더에 이름을 지정한 다음 **만들기**를 클릭하세요.
인스펙터에서 셰이더를 클릭하면 이제 셰이더 편집기가 팝업됩니다. 첫 번째 Spatial 셰이더 작성을 시작할 준비가 되었습니다!
셰이더 매직
새로운 셰이더는 이미 shader_type 변수, vertex() 함수 및 fragment() 함수를 사용하여 생성되었습니다. Godot 셰이더에 가장 먼저 필요한 것은 그들이 어떤 유형의 셰이더인지 선언하는 것입니다. 이 경우 ``shader_type``는 공간 셰이더이므로 ``spatial``로 설정됩니다.
shader_type spatial;
vertex() 함수는 :ref:`MeshInstance3D<class_MeshInstance3D>`의 정점이 최종 씬에 나타나는 위치를 결정합니다. 우리는 이를 사용하여 각 꼭지점의 높이를 상쇄하고 평평한 평면을 작은 지형처럼 보이게 만들 것입니다.
vertex() 함수에 아무것도 없으면 Godot는 기본 정점 셰이더를 사용합니다. 한 줄을 추가하여 변경을 시작할 수 있습니다.
void vertex() {
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}
이 줄을 추가하면 아래와 같은 이미지가 표시됩니다.
좋아, 이걸 풀어보자. VERTEX``의 ``y 값이 증가하고 있습니다. 그리고 VERTEX``의 ``x 및 z 구성 요소를 cos() 및 sin() <shader_func_sin>`에 대한 인수로 전달합니다. 이는 ``x` 및 z 축 전체에서 물결 모양의 모양을 제공합니다.
우리가 이루고 싶은 것은 작은 언덕의 모습입니다. 결국. cos() 및 sin()``는 이미 언덕처럼 보입니다. 이는 입력을 ``cos() 및 sin() 기능으로 확장하여 수행합니다.
void vertex() {
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
보기는 좋아졌지만 여전히 너무 뾰족하고 반복적이므로 좀 더 흥미롭게 만들어 보겠습니다.
노이즈 하이트맵
소음은 지형의 모양을 속이는 데 매우 널리 사용되는 도구입니다. 소음이 있는 경우 각 언덕의 높이가 다르다는 점을 제외하면 반복되는 언덕이 있는 코사인 함수와 유사하다고 생각하세요.
Godot는 셰이더에서 접근할 수 있는 노이즈 텍스처를 생성하기 위한 NoiseTexture2D 리소스를 제공합니다.
셰이더의 텍스처에 액세스하려면 vertex() 함수 외부의 셰이더 상단 근처에 다음 코드를 추가하세요.
uniform sampler2D noise;
이렇게 하면 노이즈 텍스처를 셰이더로 보낼 수 있습니다. 이제 재료 아래의 검사기를 살펴보십시오. **셰이더 매개변수**라는 섹션이 표시됩니다. 열면 "Noise"라는 매개변수가 표시됩니다.
이 Noise 매개변수를 새로운 :ref:`NoiseTexture2D <class_noisetexture2D>`로 설정합니다. 그런 다음 NoiseTexture2D에서 Noise 속성을 새로운 :ref:`FastNoiseLite <class_fastnoiselite>`로 설정합니다. FastNoiseLite 클래스는 NoiseTexture2D에서 하이트맵을 생성하는 데 사용됩니다.
코드에서는 이렇게 보일 것이다:
이제 _ready() 함수에서 Timer를 Sprite2D에 연결할 수 있습니다.
void vertex() {
float height = texture(noise, VERTEX.xz / 2.0 + 0.5).x;
VERTEX.y += height;
}
texture() <shader_func_texture>`는 텍스처를 첫 번째 인수로 사용하고 텍스처 위치에 대한 ``vec2``를 두 번째 인수로 사용합니다. ``VERTEX``의 ``x` 및 z 채널을 사용하여 텍스처에서 조회할 위치를 결정합니다.
PlaneMesh 좌표는 [-1.0, 1.0] 범위(2.0 크기) 내에 있고 텍스처 좌표는 [0.0, 1.0] 내에 있으므로 좌표를 평면 메시의 크기로 ``2.0``로 나누고 ``0.5``를 추가하여 다시 매핑합니다.
texture()``는 해당 위치에서 ``r, g, b, a 채널의 vec4``를 반환합니다. 노이즈 텍스처는 회색조이므로 모든 값이 동일하므로 채널 중 하나를 높이로 사용할 수 있습니다. 이 경우 ``r 또는 x 채널을 사용합니다.
참고
xyzw``는 GLSL의 ``rgba``와 동일하므로 위의 ``texture().x 대신 ``texture().r``를 사용할 수 있습니다. 자세한 내용은 `OpenGL 문서 <https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Vectors>`_를 참조하세요.
이 코드를 사용하면 텍스처가 무작위로 보이는 언덕을 생성하는 것을 볼 수 있습니다.
지금은 너무 뾰족한 부분이 있어서 언덕을 조금 부드럽게 만들고 싶습니다. 그러기 위해 우리는 유니폼을 사용할 것입니다. 위에서 노이즈 텍스처를 전달하기 위해 이미 유니폼을 사용했습니다. 이제 작동 방식을 살펴보겠습니다.
유니폼
균일 변수 <doc_shading_언어_uniforms>`를 사용하면 게임의 데이터를 셰이더로 전달할 수 있습니다. 셰이더 효과를 제어하는 데 매우 유용합니다. 유니폼은 셰이더에서 사용할 수 있는 거의 모든 데이터 유형이 될 수 있습니다. 유니폼을 사용하려면 ``uniform` 키워드를 사용하여 :ref:`Shader<class_Shader>`에서 유니폼을 선언합니다.
지형의 높이를 변경하는 유니폼을 만들어 보겠습니다.
uniform float height_scale = 0.5;
Godot를 사용하면 값으로 유니폼을 초기화할 수 있습니다; 여기서 ``height_scale``는 ``0.5``로 설정됩니다. 셰이더에 해당하는 재질에서 set_shader_parameter() 함수를 호출하여 GDScript에서 유니폼을 설정할 수 있습니다. GDScript에서 전달된 값은 셰이더에서 초기화하는 데 사용된 값보다 우선합니다.
# called from the MeshInstance3D
mesh.material.set_shader_parameter("height_scale", 0.5)
참고
Spatial 기반 노드에서 유니폼을 변경하는 것은 CanvasItem 기반 노드와 다릅니다. 여기서는 PlaneMesh 리소스 내부에 재질을 설정합니다. 다른 메시 리소스에서는 먼저 surface_get_material()``를 호출하여 자료에 액세스해야 할 수도 있습니다. MeshInstance3D에서는 ``get_surface_material() 또는 ``material_override``를 사용하여 재질에 액세스합니다.
``set_shader_parameter()``에 전달된 문자열은 셰이더의 균일 변수 이름과 일치해야 한다는 점을 기억하십시오. 셰이더 내부 어디에서나 균일 변수를 사용할 수 있습니다. 여기서는 ``0.5``를 임의로 곱하는 대신 높이 값을 설정하는 데 사용하겠습니다.
VERTEX.y += height * height_scale;
이제 훨씬 좋아 보입니다.
유니폼을 사용하면 프레임마다 값을 변경하여 지형 높이에 애니메이션을 적용할 수도 있습니다. :ref:`Tweens <class_Tween>`과 결합하면 애니메이션에 특히 유용할 수 있습니다.
빛과의 상호작용
먼저 와이어프레임을 끕니다. 이렇게 하려면 다시 뷰포트 왼쪽 상단의 Perspective 메뉴를 열고 **Display Normal**을 선택하세요. 또한 3D 씬 도구 모음에서 미리보기 햇빛을 끕니다.
메쉬 색상이 어떻게 균일해지는지 확인하세요. 그 이유는 조명이 평평하기 때문입니다. 조명을 추가해보자!
먼저 :ref:`OmniLight3D<class_OmniLight3D>`를 씬에 추가하고 지형 위에 위치하도록 위로 드래그합니다.
지형에 영향을 미치는 빛을 볼 수 있지만 이상하게 보입니다. 문제는 빛이 마치 평평한 평면인 것처럼 지형에 영향을 미친다는 것입니다. 이는 빛 셰이더가 :ref:`Mesh <class_mesh>`의 법선을 사용하여 빛을 계산하기 때문입니다.
법선은 메시에 저장되어 있지만 셰이더에서 메시의 모양을 변경하므로 법선이 더 이상 정확하지 않습니다. 이 문제를 해결하려면 셰이더에서 법선을 다시 계산하거나 노이즈에 해당하는 법선 텍스처를 사용할 수 있습니다. Godot는 우리에게 이 두 가지를 모두 쉽게 만들어줍니다.
정점 함수에서 새 법선을 수동으로 계산한 다음 NORMAL``를 설정하면 됩니다. ``NORMAL 세트를 사용하면 Godot가 우리를 위해 모든 어려운 조명 계산을 수행할 것입니다. 이 튜토리얼의 다음 부분에서 이 방법을 다룰 것입니다. 지금은 텍스처에서 법선을 읽어보겠습니다.
대신 우리는 다시 NoiseTexture를 사용하여 법선을 계산할 것입니다. 두 번째 노이즈 텍스처를 전달하여 이를 수행합니다.
uniform sampler2D normalmap;
이 두 번째 균일 텍스처를 또 다른 :ref:`FastNoiseLite <class_fastnoiselite>`을 사용하여 또 다른 :ref:`NoiseTexture2D <class_noisetexture2D>`로 설정합니다. 하지만 이번에는 **As Normal Map**을 선택하세요.
특정 정점에 해당하는 노멀이 있는 경우 NORMAL``를 설정하지만, 텍스처에서 가져온 노멀맵이 있는 경우 ``fragment() 함수에서 ``NORMAL_MAP``를 사용하여 노멀을 설정합니다. 이런 식으로 Godot는 메시 주변의 텍스처를 자동으로 감싸는 작업을 처리합니다.
마지막으로 노이즈 텍스처와 노멀맵 텍스처의 동일한 위치에서 읽고 있는지 확인하기 위해 VERTEX.xz 위치를 vertex() 함수에서 fragment() 함수로 전달합니다. :ref:`varying <doc_shading_언어_varyings>`을 사용하여 이를 수행합니다.
vertex() 위에는 tex_position``라는 ``varying vec2``가 정의되어 있습니다. 그리고 ``vertex() 함수 내에서 ``VERTEX.xz``를 ``tex_position``에 할당합니다.
varying vec2 tex_position;
void vertex() {
tex_position = VERTEX.xz / 2.0 + 0.5;
float height = texture(noise, tex_position).x;
VERTEX.y += height * height_scale;
}
이제 fragment() 함수에서 ``tex_position``에 액세스할 수 있습니다.
void fragment() {
NORMAL_MAP = texture(normalmap, tex_position).xyz;
}
법선이 제 위치에 있으면 이제 빛이 메시의 높이에 동적으로 반응합니다.
조명을 드래그하면 조명이 자동으로 업데이트됩니다.
컬 모드
이 튜토리얼의 전체 코드는 다음과 같습니다. Godot가 대부분의 어려운 일을 처리하는 데 그리 오랜 시간이 걸리지 않는다는 것을 알 수 있습니다.
shader_type spatial;
uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;
varying vec2 tex_position;
void vertex() {
tex_position = VERTEX.xz / 2.0 + 0.5;
float height = texture(noise, tex_position).x;
VERTEX.y += height * height_scale;
}
void fragment() {
NORMAL_MAP = texture(normalmap, tex_position).xyz;
}
이것이 이 부분의 전부입니다. 이제 Godot의 정점 셰이더의 기본을 이해하셨기를 바랍니다. 이 튜토리얼의 다음 부분에서는 이 정점 함수를 수반하는 조각 함수를 작성하고 이 지형을 움직이는 파도의 바다로 바꾸는 고급 기술을 다룰 것입니다.