TIL: Unreal C++ Landscape 57일차

박춘팔·2026년 6월 25일

언리얼 TIL

목록 보기
53/61

누적 학습 시간 : 622시간 34분

📅 2026-06-23

UE5 Landscape Material 구성 삽질 정리

이번 학습의 목표는 Fab/Quixel 텍스처를 이용해서 Landscape Material Function을 만들고, Layer Paint와 Nanite Displacement까지 적용하는 것이었다.

처음에는 강의와 노드 생김새가 달라서 단순 버전 차이 문제처럼 보였지만, 실제로는 다음 문제가 연쇄적으로 엮여 있었다.

1. Landscape 전용 UV Tiling 구성
2. 기본 더미 텍스처 생성 방식
3. Material Function의 출력 구조
4. Substrate와 Legacy Material Attributes 차이
5. Material Instance Parameter Group 정리
6. Landscape Layer Info Object 생성
7. Weight Blended / No Weight Blending 차이
8. Fab 텍스처의 ORM / ORDp 채널 차이
9. Nanite Tessellation과 Displacement 적용 조건
10. Height 텍스처 없이 ORM을 Displacement로 오용했을 때 발생하는 문제

결론부터 말하면, Landscape Material과 Layer Paint는 정상적으로 구성했고, Nanite Displacement 파이프라인도 동작 확인했다. 다만 내가 사용한 Fab 텍스처는 ORM이었기 때문에 Displacement 채널이 없었다. 따라서 실제 돌출 표현을 하려면 별도 Height/Displacement 텍스처가 필요하고, 없으면 Displacement는 끄는 쪽이 맞다.


Landscape Material Function 기본 구조

Landscape UV Tiling 노드 이해

강의에서 처음 나온 구조는 Landscape Coords에 Scalar 값을 곱해서 텍스처 타일링을 제어하는 방식이었다.

구조는 다음과 같다.

Landscape Layer Coords
→ Multiply with Scalar Parameter
→ Tiling

처음에 헷갈렸던 점은 Tiling이라는 노드가 특별한 노드처럼 보였다는 것이다. 실제로는 상황에 따라 Named Reroute Declaration이거나 Function Output일 수 있다.

중요한 점은 Tiling 값이 0이면 안 된다는 것이다.

Landscape UV × 0 = 모든 UV가 거의 0으로 고정

그러면 텍스처가 정상적으로 반복되지 않고 단색처럼 보일 수 있다. 강의에서는 0.2를 사용했고, 테스트할 때는 0.2, 1.0, 2.0 순서로 비교하는 게 좋다.

기본 더미 텍스처의 정체

강의에서는 다음 3개의 기본 텍스처를 사용했다.

각 텍스처의 역할은 다음과 같다.

텍스처용도Unreal 설정
T_FlatNormal기본 Normal MapsRGB Off, Compression: Normalmap
T_White_ColorBaseColor 기본값sRGB On, Compression: Default
T_White_LinearColorAO/Roughness/Mask 기본값sRGB Off, Compression: Masks 또는 Default

처음에는 Unreal 에디터 안에서 직접 Texture2D를 만들려고 했지만, 생성 메뉴에는 일반 Texture2D가 없었다.

따라서 일반 Texture2D는 외부에서 PNG를 만든 뒤 import하는 방식으로 처리했다.

Photoshop / Krita / 그림판 등에서 1x1 또는 4x4 PNG 생성
→ Content Browser로 드래그 앤 드롭
→ Texture Asset Editor에서 sRGB / Compression Settings 수정

여기서 White_ColorWhite_LinearColor는 이미지 자체가 다른 게 아니라 Unreal에서 색상으로 해석하느냐, 데이터로 해석하느냐가 다르다.


BaseColor / AO / Roughness / Normal 구성

BaseColor 구성

BaseColor는 텍스처 색과 Vector Parameter를 곱해서 색상 틴트를 줄 수 있게 만들었다.

구조는 다음과 같다.

BaseColorTex RGB
× LayerA_BaseColor
→ BaseColor

LayerA_BaseColor를 흰색으로 두면 원본 텍스처 색이 유지되고, 다른 색을 넣으면 전체 색이 틴트된다.

AO / Roughness 구성

AO와 Roughness는 packed texture의 채널을 사용해 Min/Max 범위를 보정하는 방식이었다.

수식은 다음과 같다.

AO = saturate(lerp(AO_Min, AO_Max, AO_Tex))
Roughness = saturate(lerp(Roughness_Min, Roughness_Max, Roughness_Tex))

여기서 Lerp는 Substrate 전용 노드로 대체하는 게 아니라 그대로 사용하는 수학 노드다. Substrate는 최종 셰이딩 구조가 바뀌는 것이고, Lerp, Multiply, Saturate 같은 계산 노드는 그대로 쓴다.


Material Function 출력 구조와 Substrate 차이

내 MF와 강의 MF의 출력 차이

처음 만든 MF는 MakeMaterialAttributes를 통해 Result 하나만 출력하고 있었다.

강의에서는 MF_LandscapeLayerA가 다음처럼 보였다.

강의의 MF 출력은 다음 2개였다.

MA
Displacement

내 MF에서 Displacement가 안 나오던 이유는 단순했다. Dp_Tex를 Named Reroute처럼 빼놓은 것과 Function Output으로 외부에 내보내는 것은 다르다.

외부 출력 핀을 만들려면 반드시 다음을 해야 한다.

우클릭 → Function Output 생성
Output Name = Displacement
Dp_Tex 또는 Height 계산 결과 연결

Substrate 때문에 Material Root가 다르게 보임

내 프로젝트에서는 Material Root에 Front Material 핀이 보였다.

강의는 Legacy Material Attributes 방식이고, 내 프로젝트는 Substrate가 활성화된 상태였기 때문에 루트 노드 생김새가 달랐다.

이때 선택지는 3개였다.

선택지판단
강의와 동일하게 Legacy + Use Material Attributes 방식으로 맞춤최선
Substrate를 유지하고 Substrate Slab BSDF → Front Material 구조로 다시 구성차선
MakeMaterialAttributes와 Substrate 루트를 섞어서 억지로 연결비추천

이번 학습에서는 강의 흐름을 따라가기 위해 Use Material Attributes 기반으로 정리했다.


Material Instance Parameter 정리

MI 파라미터가 Global 그룹으로 흩어진 이유

처음 MI를 열었을 때 파라미터들이 다음처럼 분산되어 있었다.

강의에서는 파라미터가 하나의 그룹으로 정리되어 있었다.

원인은 MF 내부 Parameter 노드의 Group 설정이었다.

Parameter Name: LayerA_UV_Tiling
Group: LandscapeLayerA
Sort Priority: 원하는 표시 순서

MI 화면에서 고치는 게 아니라, 부모 Material 또는 Material Function 내부의 Parameter 노드를 고쳐야 한다.

Group은 네임스페이스가 아니다

중간에는 LayerA 그룹 안에 LayerB 파라미터가 섞여 보였다.

여기서 핵심은 다음이다.

Group은 표시용 카테고리일 뿐이다.
Parameter Name이 같으면 같은 파라미터로 취급될 수 있다.

잘못된 예:

LayerA MF: Tiling
LayerB MF: Tiling

올바른 예:

LayerA_UV_Tiling
LayerB_UV_Tiling
LayerC_UV_Tiling

Layer를 복사해서 만들 때는 Group뿐 아니라 Parameter Name도 전부 Layer별로 고유하게 바꿔야 한다.


Landscape Paint와 Layer Info 문제

Fill Layer가 비활성화된 이유

Landscape Paint에서 Fill Layer가 비활성화되었다.

에러 메시지는 다음과 같았다.

This layer has no layer info assigned yet.
You must create or assign a layer info before you can paint this layer.

즉 문제는 Material Function이 아니라 Layer Info Object 미할당이었다.

각 레이어의 + 버튼을 눌러 Layer Info Object를 생성해야 한다.

Weight Blended / No Weight Blending

처음 Layer Info를 만들었을 때 No Weight Blending으로 잡히기도 했다.

일반적인 지형 레이어 페인팅이라면 Weight Blending으로 통일하는 편이 낫다.

이번 구조에서는 LayerA, LayerB, LayerC가 서로 섞이는 지형 재질이므로 Weight Blending 계열로 정리했다.


Fab 텍스처 적용과 품질 차이

강의와 내 프로젝트의 Layer Preview 차이

강의에서는 Layer 생성 후 미리보기 구체에 바로 텍스처가 적용되어 보였다.

내 프로젝트에서는 페인트는 되었지만 결과가 밋밋했다.

Fab 프리뷰는 훨씬 입체적이었다.

차이의 원인은 다음으로 정리했다.

1. MI에서 실제 텍스처 Override 체크가 안 되어 있을 수 있음
2. Normal Strength가 0이면 입체감이 거의 사라짐
3. AO/Roughness 채널 연결이 틀리면 셰이딩이 이상해짐
4. Fab 프리뷰는 Height/Displacement까지 반영된 경우가 있음
5. 조명과 Auto Exposure 차이도 결과에 영향을 줌

즉 단순히 텍스처 해상도 문제가 아니라 셰이딩 정보가 제대로 연결되어 있는지가 더 중요했다.


MF / MM 최종 구조

Material Function 전체 구조

최종 MF는 Texture Set, BaseColor, Mask, Normal, Displacement 출력으로 구성했다.

핵심 출력은 다음이다.

MakeMaterialAttributes → Function Output: MA
Height/Displacement 계산값 → Function Output: Displacement

MM에서 MA와 Displacement 합치기

MM에서는 MADisplacement를 각각 Landscape Layer Blend로 섞은 뒤 SetMaterialAttributes로 합쳤다.

처음에는 Displacement용 Layer 이름을 LayerA_Displacement처럼 만들었고, 그 결과 Paint Target Layer가 6개로 늘어났다.

이건 잘못된 구조다.

올바른 구조는 다음이다.

MA Blend Layers:
- LayerA
- LayerB
- LayerC

Displacement Blend Layers:
- LayerA
- LayerB
- LayerC

Displacement도 기존 Paint Layer의 weight를 공유해야 하므로, 레이어 이름을 동일하게 맞춰야 한다.


Nanite Displacement 적용

ini 설정과 Material 설정

Nanite Tessellation을 쓰려면 프로젝트 ini에 다음 값을 추가했다.

[/Script/Engine.RendererSettings]
r.Nanite.AllowTessellation=1
r.Nanite.Tessellation=1

그 뒤 에디터를 재시작하고, Material Main Node에서 다음을 설정했다.

Enable Tessellation = On
Displacement Center = 0.5
Displacement Magnitude = 작은 값부터 테스트

Landscape Actor에서는 다음도 필요하다.

Landscape 선택
→ Enable Nanite
→ Build Data / Rebuild Data

초기 Displacement Strength 구조

처음에는 Dp_Tex * Strength 형태로 구성했다.

하지만 실제 변화가 거의 보이지 않았다.

그래서 Constant 10을 넣어 파이프라인 자체가 동작하는지 테스트했다.

결과적으로 브러시한 영역이 통째로 솟아올랐다. 즉 연결과 Nanite Tessellation 자체는 동작했다.

Constant 10으로 솟아오름
= Displacement 파이프라인은 정상
= 문제는 Height 입력값 또는 채널

Displacement 값 보정과 ORM 문제

Centered Displacement Formula

Displacement 값은 0~1 범위를 쓰고, Center = 0.5를 기준으로 움직이는 것이 안정적이다.

그래서 다음 수식으로 바꿨다.

(Dp_Tex - 0.5) * DisplacementStrength + 0.5

최종 노드 흐름은 다음과 같다.

Dp_Tex
→ Subtract 0.5
→ Multiply LayerX_Displacement
→ Add 0.5
→ Saturate
→ Output Displacement

Material Details에서는 Magnitude를 조절했다.

하지만 여전히 기대한 것처럼 돌이 튀어나오지는 않았다.

ORM에는 Displacement 채널이 없음

R/G 채널을 Displacement에 넣으면 실제 높이 변화는 생겼지만, 결과가 완전히 비정상적으로 변했다.

원인은 텍스처가 ORDp가 아니라 ORM이었다는 점이다.

ORM:
R = Ambient Occlusion
G = Roughness
B = Metallic

즉 ORM에는 Height/Displacement 채널이 없다.

따라서 R/G를 DP에 연결하면 다음과 같은 잘못된 결과가 나온다.

AO를 높이맵처럼 사용
Roughness를 높이맵처럼 사용
→ 지형이 찢어지고 비정상적으로 솟음

반대로 ORDp 계열이라면 보통 다음처럼 사용한다.

ORDp:
R = AO
G = Roughness
B = Displacement / Depth

최종 결론은 다음이다.

ORM은 AO/Roughness/Metallic 용도로만 사용한다.
Displacement를 쓰려면 별도 Height/Displacement 텍스처가 필요하다.
Height가 없다면 Displacement는 Constant 0.5로 끄는 편이 맞다.

최종 체크리스트

Material Function 체크리스트

- Texture Sample Parameter 이름이 LayerA/B/C별로 고유한가?
- Scalar Parameter 이름이 LayerA/B/C별로 고유한가?
- Group이 Layer별로 정리되어 있는가?
- MA는 Function Output인가?
- Displacement도 Function Output인가?
- Displacement 수식은 (Height - 0.5) * Strength + 0.5 구조인가?
- 결과에 Saturate를 걸었는가?

Master Material 체크리스트

- MA용 Landscape Layer Blend의 Layer 이름: LayerA/B/C
- Displacement용 Landscape Layer Blend의 Layer 이름도 동일하게 LayerA/B/C
- SetMaterialAttributes로 Material Attributes와 Displacement를 합쳤는가?
- Material Main Node에서 Enable Tessellation이 켜져 있는가?
- Magnitude와 Center를 조절했는가?

Landscape 체크리스트

- LayerA/B/C에 Layer Info Object가 할당되어 있는가?
- Weight Blending 방식이 의도와 맞는가?
- Landscape에 MI가 적용되어 있는가?
- Enable Nanite가 켜져 있는가?
- Build/Rebuild Nanite Data를 했는가?

Texture 체크리스트

- BaseColor는 sRGB On인가?
- Normal은 sRGB Off + Normalmap 압축인가?
- ORM/Mask는 sRGB Off인가?
- ORM을 Displacement로 오용하고 있지 않은가?
- Height/Displacement 텍스처가 실제로 존재하는가?

오늘의 결론

최선

ORM은 AO/Roughness/Metallic 용도로만 쓰고, Displacement는 별도 Height/Displacement 텍스처를 받아서 처리한다. 큰 돌이나 자갈은 Landscape Displacement에 기대기보다 Static Mesh, Foliage, PCG Scatter로 배치하는 것이 맞다.

차선

Height 텍스처가 없다면 Displacement를 Constant 0.5로 고정해 끄고, Normal/AO/Roughness만 제대로 연결해서 시각 품질을 확보한다.

비추천

ORM의 R/G/B 채널을 억지로 Displacement에 넣는 것. 높이 변화는 생기지만 AO/Roughness/Metallic 데이터를 높이로 쓰는 것이므로 지형이 비정상적으로 찢어진다.

profile
이것 저것 다해보는 삶

0개의 댓글