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.

고급 후처리

소개

이 튜토리얼은 Godot의 후처리를 위한 고급 방법을 설명합니다. 특히, 깊이 버퍼를 사용하는 후처리 셰이더를 작성하는 방법에 대해 설명합니다. 일반적인 후처리, 특히 :ref:`사용자 정의 후처리 튜토리얼 <doc_custom_postprocessing>`에 설명된 방법에 이미 익숙해야 합니다.

전체 화면 사변형

맞춤형 후처리 효과를 만드는 한 가지 방법은 뷰포트를 사용하는 것입니다. 그러나 뷰포트를 사용하면 두 가지 주요 단점이 있습니다.

  1. 깊이 버퍼에 접근할 수 없습니다

  2. 후처리 셰이더의 효과가 편집기에 표시되지 않습니다.

깊이 버퍼 사용에 대한 제한 사항을 해결하려면 QuadMesh 기본 요소와 함께 :ref:`MeshInstance3D <class_MeshInstance3D>`을 사용하세요. 이를 통해 우리는 셰이더를 사용하고 씬의 깊이 텍스처에 액세스할 수 있습니다. 다음으로, 정점 셰이더를 사용하여 쿼드가 항상 화면을 덮도록 만들어 편집기를 포함하여 후처리 효과가 항상 적용되도록 합니다.

먼저 새 MeshInstance3D를 만들고 해당 메시를 QuadMesh로 설정합니다. 그러면 너비와 높이가 ``1``이고 위치 ``(0, 0, 0)``에 중심을 둔 쿼드가 생성됩니다. 너비와 높이를 ``2``로 설정하고 **Flip Faces**를 활성화합니다. 현재 쿼드는 월드 공간의 원점에서 한 위치를 차지하고 있습니다. 그러나 우리는 그것이 항상 전체 화면을 덮도록 카메라와 함께 움직이기를 원합니다. 이를 위해 차등 좌표 공간을 통해 정점 위치를 변환하는 좌표 변환을 우회하고 정점이 이미 클립 공간에 있는 것처럼 처리합니다.

정점 셰이더는 화면 왼쪽과 아래쪽의 -1``부터 화면 위쪽과 오른쪽의 ``1 범위의 좌표인 클립 공간에 좌표가 출력될 것으로 예상합니다. 이것이 QuadMesh의 높이와 너비가 ``2``여야 하는 이유입니다. Godot는 모델에서 보기 공간, 장면 뒤에서 클립 공간으로의 변환을 처리하므로 Godot의 변환 효과를 무효화해야 합니다. 내장된 ``POSITION``를 원하는 위치로 설정하여 이를 수행합니다. ``POSITION``는 내장된 변환을 우회하고 클립 공간에서 정점 위치를 직접 설정합니다.

shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;

void vertex() {
  POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}

참고

4.3 이전 Godot 버전에서 이 코드는 평면 근처의 클립 공간이 0.0``에 있다고 암시적으로 가정하는 ``POSITION = vec4(VERTEX, 1.0); 사용을 권장합니다. 해당 코드는 이제 올바르지 않으며 이제 근거리 평면이 ``1.0``에 있는 "역방향 Z" 깊이 버퍼를 사용하므로 버전 4.3 이상에서는 작동하지 않습니다.

이 정점 셰이더가 있어도 쿼드가 계속 사라집니다. 이는 CPU에서 수행되는 절두체 컬링(frustum culling) 때문입니다. 절두체 컬링은 카메라 매트릭스와 메시의 AABB를 사용하여 메시가 GPU에 전달되기 전에 표시되는지 여부를 결정합니다. CPU는 정점으로 무엇을 하는지 알지 못하므로 지정된 좌표가 클립 공간 위치가 아닌 월드 위치를 참조한다고 가정합니다. 이로 인해 우리가 씬의 중심에서 멀어질 때 Godot가 쿼드를 컬링하게 됩니다. 쿼드가 컬링되는 것을 방지하기 위한 몇 가지 옵션이 있습니다:

  1. QuadMesh를 카메라에 하위 항목으로 추가하면 카메라가 항상 이를 향하게 됩니다.

  2. QuadMesh에서 형상 속성 ``extra_cull_margin``를 가능한 한 크게 설정하십시오.

두 번째 옵션은 쿼드가 편집기에 표시되도록 보장하고, 첫 번째 옵션은 카메라가 컬 마진 밖으로 이동하더라도 여전히 표시되도록 보장합니다. 두 옵션을 모두 사용할 수도 있습니다.

깊이 텍스처

깊이 텍스처를 읽으려면 먼저 ``hint_depth_texture``를 사용하여 깊이 버퍼에 설정된 텍스처 유니폼을 만들어야 합니다.

uniform sampler2D depth_texture : hint_depth_texture;

일단 정의되면 texture() 함수를 사용하여 깊이 텍스처를 읽을 수 있습니다.

float depth = texture(depth_texture, SCREEN_UV).x;

참고

화면 텍스처에 액세스하는 것과 마찬가지로 깊이 텍스처에 액세스하는 것은 현재 뷰포트에서 읽을 때만 가능합니다. 렌더링한 다른 뷰포트에서는 깊이 텍스처에 액세스할 수 없습니다.

depth_texture``에서 반환된 값은 ``1.0``와 ``0.0 사이에 있으며("reverse-z" 깊이 버퍼를 사용하기 때문에 각각 근거리 평면과 원거리 평면에 해당) 비선형입니다. ``depth_texture``에서 깊이를 직접 표시할 때 비선형성으로 인해 매우 가깝지 않으면 모든 것이 거의 검게 보입니다. 깊이 값을 세계 또는 모델 좌표와 일치시키려면 값을 선형화해야 합니다. 정점 위치에 투영 행렬을 적용할 때, z 값은 비선형이 되므로 이를 선형화하기 위해 투영 행렬의 역수를 곱합니다. Godot에서는 변수 ``INV_PROJECTION_MATRIX``로 접근할 수 있습니다.

먼저 화면 공간 좌표를 가져와 정규화된 장치 좌표(NDC)로 변환합니다. NDC는 Vulkan 백엔드를 사용할 때 xy 방향으로 -1.0``에서 ``1.0``로 실행하고, Vulkan 백엔드를 사용할 경우 ``0.0``에서 ``1.0 방향으로 실행합니다. xy 축에 대해 ``SCREEN_UV``를 사용하고 ``z``에 대한 깊이 값을 사용하여 NDC를 재구성합니다.

void fragment() {
  float depth = texture(depth_texture, SCREEN_UV).x;
  vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
}

참고

이 튜토리얼에서는 [0.0, 1.0] 범위의 Vulkan NDC를 사용하는 Forward+ 또는 모바일 렌더러를 사용한다고 가정합니다. 이와 대조적으로 호환성 렌더러는 [-1.0, 1.0] 범위의 OpenGL NDC를 사용합니다. 호환성 렌더러의 경우 NDC 계산을 다음으로 바꾸세요.

vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;

모든 렌더러에서 작동하는 셰이더에 대해 CURRENT_RENDERERRENDERER_COMPATIBILITY 내장 정의를 사용할 수도 있습니다.

#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
#else
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
#endif

NDC에 INV_PROJECTION_MATRIX``를 곱하여 NDC를 공간으로 변환합니다. 공간은 카메라를 기준으로 위치를 제공하므로 ``z 값은 지점까지의 거리를 제공합니다.

void fragment() {
  ...
  vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
  view.xyz /= view.w;
  float linear_depth = -view.z;
}

카메라가 음의 z 방향을 향하고 있기 때문에 위치는 음의 z 값을 갖게 됩니다. 사용 가능한 깊이 값을 얻으려면 ``view.z``를 무효화해야 합니다.

월드 위치는 뷰 공간에서 월드 공간으로 위치를 변환하는 ``INV_VIEW_MATRIX``를 사용하여 다음 코드를 사용하여 깊이 버퍼에서 구성할 수 있습니다.

void fragment() {
  ...
  vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
  vec3 world_position = world.xyz / world.w;
}

예시:

``ALBEDO``에 출력할 라인을 추가하면 다음과 같은 완전한 셰이더가 생성됩니다. 이 셰이더를 사용하면 주석 처리된 라인에 따라 선형 깊이 또는 월드 공간 좌표를 시각화할 수 있습니다.

shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;

uniform sampler2D depth_texture : hint_depth_texture;

void vertex() {
  POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}

void fragment() {
  float depth = texture(depth_texture, SCREEN_UV).x;
  vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
  vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
  view.xyz /= view.w;
  float linear_depth = -view.z;

  vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
  vec3 world_position = world.xyz / world.w;

  // Visualize linear depth
  ALBEDO.rgb = vec3(fract(linear_depth));

  // Visualize world coordinates
  //ALBEDO.rgb = fract(world_position).xyz;
}

애니메이션

전체 화면 쿼드를 사용하는 것보다 하나의 큰 삼각형을 사용하면 이점을 얻을 수 있습니다. 그 이유는 `여기 <https://michaldrobot.com/2014/04/01/gcn-execution-patterns-in-full-screen-passes>`_에 설명되어 있습니다. 그러나 이점은 매우 작으며 특히 복잡한 조각 셰이더를 실행할 때만 유익합니다.

MeshInstance3D의 메시를 :ref:`ArrayMesh <class_ArrayMesh>`로 설정합니다. ArrayMesh는 정점, 법선, 색상 등에 대한 배열에서 메시를 쉽게 구성할 수 있는 도구입니다.

KineticBody2D에 스크립트를 연결하고 다음 코드를 추가합니다:

extends MeshInstance3D

func _ready():
  # Create a single triangle out of vertices:
  var verts = PackedVector3Array()
  verts.append(Vector3(-1.0, -1.0, 0.0))
  verts.append(Vector3(3.0, -1.0, 0.0))
  verts.append(Vector3(-1.0, 3.0, 0.0))

  # Create an array of arrays.
  # This could contain normals, colors, UVs, etc.
  var mesh_array = []
  mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array
  mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array

  # Create mesh from mesh_array:
  mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)

참고

삼각형은 정규화된 장치 좌표로 지정됩니다. NDC는 xy 방향 모두에서 -1.0``에서 ``1.0``까지 실행됩니다. 이렇게 하면 화면이 ``2 단위 너비로, 2 단위 높이로 표시됩니다. 단일 삼각형으로 전체 화면을 덮으려면 너비가 4 단위이고 높이가 4 단위인 삼각형을 사용하여 높이와 너비의 두 배를 사용합니다.

위에서 동일한 정점 셰이더를 할당하면 모든 것이 정확히 동일하게 보일 것입니다.

QuadMesh를 사용하는 것보다 ArrayMesh를 사용하는 것의 한 가지 단점은 씬이 실행될 때까지 삼각형이 구성되지 않기 때문에 ArrayMesh가 편집기에 표시되지 않는다는 것입니다. 이 문제를 해결하려면 모델링 프로그램에서 단일 삼각형 메시를 구성하고 대신 MeshInstance3D에서 사용하십시오.