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.

뷰포트 2개

소개

이 튜토리얼에서는 :ref:`SubViewport <class_SubViewport>`를 3D 개체에 적용할 수 있는 텍스처로 사용하는 방법을 소개합니다. 그렇게 하기 위해 아래와 같은 절차적 행성을 만드는 과정을 안내합니다.

../../_images/planet_example.png

참고

이 튜토리얼에서는 이 행성과 같은 역동적인 대기를 코딩하는 방법을 다루지 않습니다.

이 튜토리얼에서는 사용자가 Camera3D, 광원, :ref:`MeshInstance3D <class_MeshInstance3D>`을 포함한 기본 씬을 설정하는 방법에 익숙하다고 가정합니다. Mesh§class_PrimitiveMesh@@, :ref:`StandardMaterial3D <class_StandardMaterial3D>`을 메시에 적용합니다. :ref:`SubViewport <class_SubViewport>`를 사용하여 메시에 적용할 수 있는 텍스처를 동적으로 생성하는 데 중점을 둘 것입니다.

적 씬은 다음 노드들을 사용할 것입니다:

  • :ref:`SubViewport <class_SubViewport>`를 렌더 텍스처로 사용하는 방법

  • 등장방형 매핑을 사용하여 텍스처를 구에 매핑

  • 절차적 행성을 위한 조각 셰이더 기술

  • :ref:`뷰포트 Texture <class_ViewportTexture>`에서 거칠기 맵 설정

씬 설정하기

만들기 새 씬을 만들고 아래 표시된 대로 정확하게 다음 노드를 추가합니다.

../../_images/viewport_texture_node_tree.webp

MeshInstance3D로 이동하여 메쉬를 SphereMesh로 만듭니다.

SubViewport 설정하기

SubViewport 노드를 클릭하고 크기를 ``(1024, 512)``로 설정합니다. :ref:`SubViewport <class_SubViewport>`는 너비가 높이의 두 배인 한 실제로 어떤 크기도 될 수 있습니다. 정방형 투영을 사용할 것이므로 이미지가 구에 정확하게 매핑되도록 너비는 높이의 두 배여야 하지만 이에 대해서는 나중에 자세히 설명합니다.

다음으로 3D를 비활성화합니다. :ref:`ColorRect <class_ColorRect>`를 사용하여 표면을 렌더링하므로 3D도 필요하지 않습니다.

../../_images/planet_new_viewport.webp

ColorRect 전체를 차지하게 됩니다.

../../_images/planet_new_colorrect.webp

다음으로 셰이더 Material <class_ShaderMaterial>`을 :ref:`ColorRect <class_ColorRect>`에 추가합니다(ColorRect > CanvasItem > Material > Material > ``New ShaderMaterial`).

참고

이 튜토리얼에서는 음영 처리에 대한 기본 지식을 익히는 것이 좋습니다. 하지만 셰이더를 처음 접하시는 분들이라도 모든 코드가 제공되므로 따라하는데 문제가 없을 것입니다.

셰이더 자료에 대한 드롭다운 메뉴 버튼을 클릭하고 / 편집을 클릭합니다. 여기에서 셰이더 > ``New Shader``로 이동합니다. 이름을 지정하고 "만들기"를 클릭하세요. 인스펙터에서 셰이더를 클릭하여 셰이더 편집기를 엽니다. 기본 코드를 삭제하고 다음을 추가합니다.

shader_type canvas_item;

void fragment() {
    COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}

셰이더 코드를 저장하면 위 코드가 아래와 같은 그래디언트를 렌더링하는 것을 검사기에서 볼 수 있습니다.

../../_images/planet_gradient.png

이제 렌더링할 :ref:`SubViewport <class_SubViewport>`의 기본 사항과 구에 적용할 수 있는 고유한 이미지가 생겼습니다.

텍스처 적용

이제 :ref:`MeshInstance3D <class_MeshInstance3D>`으로 이동하여 :ref:`StandardMaterial3D <class_StandardMaterial3D>`을 추가합니다. 특별한 :ref:`셰이더 Material <class_ShaderMaterial>`이 필요하지 않습니다(단, 위 예의 대기와 같은 고급 효과에는 좋은 아이디어임에도 불구하고).

MeshInstance3D > GeometryInstance > 형상 > 재질 재정의 > New StandardMaterial3D

그런 다음 StandardMaterial3D의 드롭다운을 클릭하고 "편집"을 클릭합니다.

"리소스" 섹션으로 이동하여 Local to scene 상자를 확인하세요. 그런 다음 "알베도" 섹션으로 이동하여 "텍스처" 속성 옆을 클릭하여 알베도 텍스처를 추가합니다. 여기서는 우리가 만든 텍스처를 적용하겠습니다. "새 뷰포트 텍스처"를 선택합니다.

../../_images/planet_new_viewport_texture.webp

인스펙터에서 방금 생성한 ViewportTexture를 클릭한 다음 "Assign(할당)"을 클릭합니다. 그런 다음 팝업 메뉴에서 이전에 렌더링한 뷰포트를 선택합니다.

../../_images/planet_pick_viewport_texture.webp

이제 구가 뷰포트에 렌더링된 색상으로 색칠되어야 합니다.

../../_images/planet_seam.webp

텍스처가 둘러싸는 부분에 형성되는 보기 흉한 솔기가 보이시나요? 이는 UV 좌표를 기반으로 색상을 선택하고 UV 좌표가 텍스처를 감싸지 않기 때문입니다. 이는 2D 지도 투영의 전형적인 문제입니다. 게임 개발자는 종종 2차원 지도를 구에 투영하고 싶지만 둘러싸면 이음새가 커집니다. 다음 섹션에서 설명할 이 문제에 대한 우아한 해결 방법이 있습니다.

행성 텍스처 만들기

이제 SubViewport <class_SubViewport>`로 렌더링하면 구에 마법처럼 나타납니다. 그러나 텍스처 좌표에 의해 생성된 보기 흉한 이음새가 있습니다. 그렇다면 구를 멋지게 감싸는 다양한 좌표를 어떻게 얻을 수 있을까요? 한 가지 해결책은 텍스처 영역에서 반복되는 함수를 사용하는 것입니다. ``sin```cos``는 이러한 두 가지 기능입니다. 이를 텍스처에 적용하고 어떤 일이 일어나는지 살펴보겠습니다. 셰이더의 기존 색상 코드를 다음으로 바꾸십시오.

COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
../../_images/planet_sincos.webp

별로 나쁘지 않습니다. 주위를 둘러보면 이제 이음새가 사라진 것을 알 수 있지만 그 자리에 기둥이 꼬집어 있습니다. 이러한 핀칭은 Godot가 :ref:`StandardMaterial3D <class_StandardMaterial3D>`에서 텍스처를 구에 매핑하는 방식으로 인해 발생합니다. 이는 구형 지도를 2D 평면으로 변환하는 등장방형 투영이라는 투영 기술을 사용합니다.

참고

기술에 대한 약간의 추가 정보에 관심이 있으시면 구면 좌표를 데카르트 좌표로 변환하겠습니다. 구면 좌표는 구의 경도와 위도를 매핑하는 반면 데카르트 좌표는 모든 의도와 목적을 위해 구 중심에서 점까지의 벡터입니다.

각 픽셀에 대해 구에서의 3D 위치를 계산합니다. 그로부터 3D 노이즈를 사용하여 색상 값을 결정합니다. 3D로 노이즈를 계산함으로써 극점의 끼임 문제를 해결합니다. 이유를 이해하려면 2D 평면이 아닌 구 표면 전체에 걸쳐 노이즈가 계산되는 모습을 상상해 보십시오. 구 표면을 가로질러 계산할 때 가장자리에 닿지 않으므로 극에 이음새나 핀치 포인트가 생성되지 않습니다. 다음 코드는 ``UVs``를 데카르트 좌표로 변환합니다.

float theta = UV.y * 3.14159;
float phi = UV.x * 3.14159 * 2.0;
vec3 unit = vec3(0.0, 0.0, 0.0);

unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
unit = normalize(unit);

그리고 unit``를 출력 ``COLOR 값으로 사용하면 다음을 얻습니다.

../../_images/planet_normals.webp

이제 구 표면의 3D 위치를 계산할 수 있으므로 3D 노이즈를 사용하여 행성을 만들 수 있습니다. 우리는 `Shadertoy <https://www.shadertoy.com/view/Xsl3Dl>`_에서 직접 이 노이즈 기능을 사용할 것입니다:

vec3 hash(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));

    return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  vec3 u = f * f * (3.0 - 2.0 * f);

  return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
                     dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
                     dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
             mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
                     dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
                     dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
}

참고

모든 크레딧은 저자 Inigo Quilez에게 있습니다. 이 제품은 MIT 라이센스로 출판되었습니다.

이제 noise``를 사용하려면 ``fragment 함수에 다음을 추가하세요.

float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
../../_images/planet_noise.webp

참고

텍스처를 강조하기 위해 재질을 음영 처리되지 않음으로 설정했습니다.

이제 노이즈가 실제로 구 주위를 매끄럽게 감싸는 것을 볼 수 있습니다. 비록 이곳은 당신이 약속했던 행성과는 전혀 달라 보이지만요. 그럼 좀 더 다채로운 것으로 넘어가 보겠습니다.

행성 색칠하기

이제 행성의 색상을 만들어 보겠습니다. 이를 수행하는 방법에는 여러 가지가 있지만 지금은 물과 땅 사이의 그라데이션을 사용하겠습니다.

GLSL에서 그래디언트를 만들기 위해 mix 함수를 사용합니다. ``mix``는 두 값을 사용하여 사이를 보간하고 세 번째 인수를 사용하여 두 값 사이를 보간할 정도를 선택합니다. 본질적으로 두 값을 함께 *혼합*합니다. 다른 API에서는 이 함수를 ``lerp``라고도 합니다. 그러나 ``lerp``는 일반적으로 두 개의 플로트를 함께 혼합하기 위해 예약되어 있습니다. ``mix``는 부동 소수점 유형이든 벡터 유형이든 모든 값을 사용할 수 있습니다.

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);

첫 번째 색상은 바다를 의미하는 파란색입니다. 두 번째 색상은 일종의 붉은색입니다(모든 외계 행성에는 빨간색 지형이 필요하기 때문입니다). 그리고 마지막으로 n * 0.5 + 0.5``에 의해 혼합됩니다. ``n``는 ``-1``와 ``1 사이에서 부드럽게 변화합니다. 따라서 이를 mix``가 예상하는 ``0-1 범위에 매핑합니다. 이제 색상이 파란색과 빨간색 사이에서 변경되는 것을 볼 수 있습니다.

../../_images/planet_noise_color.webp

우리가 원하는 것보다 조금 더 흐릿합니다. 행성은 일반적으로 육지와 바다가 비교적 명확하게 구분되어 있습니다. 그러기 위해 마지막 항을 ``smoothstep(-0.1, 0.0, n)``로 변경하겠습니다. 따라서 전체 줄은 다음과 같습니다.

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));

smoothstep``가 수행하는 작업은 번째 인수가 번째 인수보다 낮으면 ``0``를 반환하고, 번째 인수가 번째 인수보다 크면 ``1``를 반환하며, 번째 숫자가 번째와 번째 사이에 있으면 ``0``와 ``1 사이를 원활하게 혼합합니다. 따라서 이 줄에서 ``smoothstep``는 ``n``가 ``-0.1``보다 작을 때마다 ``0``를 반환하고, ``n``가 ``0``보다 클 때마다 ``1``를 반환합니다.

../../_images/planet_noise_smooth.webp

이것을 좀 더 행성처럼 만들기 위한 한 가지 더. 땅이 너무 지저분해서는 안 됩니다. 가장자리를 조금 더 거칠게 만들어 보겠습니다. 노이즈가 있는 거칠어 보이는 지형을 만들기 위해 셰이더에서 자주 사용되는 비결은 다양한 주파수에서 노이즈 레벨을 서로 겹쳐서 배치하는 것입니다. 우리는 대륙의 전체적인 얼룩진 구조를 만들기 위해 하나의 레이어를 사용합니다. 그런 다음 다른 레이어가 가장자리를 약간 분할하고, 또 다른 레이어가 계속됩니다. 우리가 할 일은 단 한 줄이 아닌 네 줄의 셰이더 코드를 사용하여 ``n``를 계산하는 것입니다. ``n``는 다음과 같습니다.

float n = noise(unit * 5.0) * 0.5;
n += noise(unit * 10.0) * 0.25;
n += noise(unit * 20.0) * 0.125;
n += noise(unit * 40.0) * 0.0625;

이제 행성은 다음과 같습니다.

../../_images/planet_noise_fbm.webp

바다 만들기

이것을 행성처럼 보이게 만드는 마지막 작업입니다. 바다와 땅은 빛을 다르게 반사합니다. 그래서 우리는 바다가 육지보다 조금 더 빛나기를 바랍니다. 출력 COLOR``의 ``alpha 채널에 네 번째 값을 전달하고 이를 거칠기 맵으로 사용하여 이를 수행할 수 있습니다.

COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);

이 줄은 물에 대해서는 ``0.3``를 반환하고 육지에 대해서는 ``1.0``를 반환합니다. 즉, 땅은 매우 거칠고 물은 매우 매끄러울 것입니다.

그런 다음 재질의 "Metallic" 섹션에서 ``Metallic``가 ``0``로 설정되고 ``Specular``가 ``1``로 설정되어 있는지 확인하세요. 그 이유는 물이 빛을 잘 반사하지만 금속성이 아니기 때문입니다. 이러한 값은 물리적으로 정확하지는 않지만 이 데모에는 충분합니다.

다음으로, "거칠기" 섹션에서 거칠기 텍스처를 행성 텍스처 SubViewport <class_SubViewport>`를 가리키는 :ref:`뷰포트 Texture <class_ViewportTexture>`로 설정합니다. 마지막으로 ``Texture Channel``를 ``Alpha``로 설정합니다. 이는 렌더러에게 출력 ``COLOR``의 ``alpha` 채널을 Roughness 값으로 사용하도록 지시합니다.

../../_images/planet_ocean.webp

행성이 더 이상 하늘을 반사하지 않는다는 점을 제외하면 거의 변화가 없음을 알 수 있습니다. 이는 기본적으로 무언가가 알파 값으로 렌더링될 때 배경 위에 투명한 개체로 그려지기 때문에 발생합니다. 그리고 SubViewport <class_SubViewport>`의 기본 배경이 불투명하므로 :ref:`뷰포트 Texture <class_ViewportTexture>`의 ``alpha` 채널은 1``이므로 행성 텍스처가 약간 희미한 색상과 ``Roughness 값으로 그려집니다. 어디에나 ``1``가 있습니다. 이 문제를 해결하려면 :ref:`SubViewport <class_SubViewport>`로 이동하여 "Transparent Bg" 속성을 활성화합니다. 이제 하나의 투명 객체를 다른 투명 객체 위에 렌더링하고 있으므로 ``blend_premul_alpha``를 활성화하려고 합니다.

render_mode blend_premul_alpha;

이렇게 하면 색상에 alpha 값을 미리 곱한 다음 올바르게 혼합합니다. 일반적으로 하나의 투명한 색상을 다른 투명 색상 위에 혼합할 때 배경에 ``alpha``가 ``0``인 경우에도(이 경우처럼) 이상한 색상 번짐 문제가 발생합니다. ``blend_premul_alpha``를 설정하면 문제가 해결됩니다.

이제 행성은 육지가 아닌 바다에 빛을 반사하는 것처럼 보일 것입니다. 씬에서 OmniLight3D 주위를 이동하면 바다에 반사되는 효과를 확인할 수 있습니다.

../../_images/planet_ocean_reflect.webp

그리고 거기에 있습니다. :ref:`SubViewport <class_SubViewport>`을 사용하여 생성된 절차적 행성입니다.