Three.js/Intro

[threejs-journey 1-10] Texture

SL123 2025. 6. 25. 13:00

개요

3D 모델을 리얼하게 만들고 싶다면, 단순히 메쉬만으로는 부족합니다. 바로 이때 필요한 것이 텍스처(Texture) 입니다. 텍스처는 이미지 파일을 3D 모델의 표면에 입혀 색상, 질감, 반사, 광택, 음영 등 현실적인 시각 효과를 만들어주는 역할을 합니다.

 

요즘에는 주로 PBR (Physically Based Rendering) 방식이 널리 사용되며, 아래는 대표적인 7가지 PBR 텍스처에 대한 정리입니다. 아래 링크는 유명한 Door Texture 예시를 기준으로 각 텍스처가 어떤 역할을 하는지 살펴봅니다.

https://3dtextures.me/2019/04/16/door-wood-001/

 

Door Wood 001

Door Wood 001 – Free seamless texture, 1024 x 1024, with the following maps: Diffuse Normal Displacement Roughness Ambient Occlusion Opacity Metallic

3dtextures.me

 

이후에는 uv, pbr 등등

 

 

- 알베도(Albedo / Base Color)

알베도(Albedo)는 모델의 기본 색상을 담당하며, 조명, 반사, 그림자 등의 조명 효과는 포함되지 않으며 순수 색 정보만 포함합니다. 대표적으로 현재 문은 나무 문, 나무 질감이며, 갈색 알베도로 구현되어 있습니다.

 

 

- 알파(Alpha Map)

알파(Alpha)는 투명도를 표현하고, 흑백(0)으로 갈수록 투명해지고, 흰색(255)으로 갈수록 보이는 것이 특징입니다. 알베도(Albedo) + 알파(Alpha) 값을 같이 사용할 때도 많습니다. 이 기능은 주로 창문, 유리, 철망 같은 것들을 표현할 때 유용합니다.

 

 

- 하이트 맵(Height Map)

하이트 맵(Height Map)은 표면의 실제 높낮이를 표현하는 텍스처입니다. 밝은 부분은 높게, 어두운 부분은 낮게 해석되며, 지형(Terrain) 생성이나 디스플레이스먼트 효과에 사용됩니다. 단, 이 맵은 실제 정점(Vertex)을 움직이므로, Subdivision(분할)이 필요하고 GPU 리소스를 많이 소모합니다.

 

 

 

- 노말 맵(Normal Map)

표면의 미세한 굴곡이나 질감을 빛의 반사 방향만 조작해서 표현하는 텍스처입니다, 내부적으로는 RGB 값을 통해 법선 벡터(XYZ 방향) 정보를 압축한 형태이기 때문에, 보라색-파란색 계열로 나오는 것입니다. 정점을 실제로 이동시키진 않지만, 빛의 방향을 속여 입체감을 표현합니다. 성능 부담 없이 디테일을 표현할 수 있는 장점이 있습니다.

 

- 앰비언트 오클루전(Ambient Occlustion AO)

 

접히거나 틈이 발생하는 곳에 음영 표현을 해주는 텍스처이다. 흑백 이미지이며, 어두운 곳은 더 어둡게 표현해주며, AO는 실시간 그림자라기보다는, 사전 계산된 간접 음영에 가까우며, Real Time PBR에서 시각적 완성도를 높여주는 역할을합니다. 

- 메탈릭(Metalness Map)

어느 부분이 금속인지를 정하는 텍스처입니다 흑백 이미지에서 흰색(1)은 금속과 가깝고, 검정(0)은 비금속과 가깝습니다. 금속과 가까울수록 빛을 반사하며, 비금속이라면 색을 중심으로 표현합니다. 대표적으로 플라스틱/나무는 0, 철/구리는 1로 표현됩니다. 

- 러프니스(Roughness Map)

표면의 거칠기를 표현하는 텍스처입니다. 흑백 이미지에서 흰색(1)은 거침(빛 확산, 무광 느낌)에 가깝고, 검정(0)은 매끄러움(거울 같은 반사)와 가깝습니다. 대표적으로 카펫은 반사되지 않는 흰색, 유리나 물처럼 매끄러운 표면은 반사가 강해 Roughness값이 0(검정)에 가깝습니다.

 

 

- ORM 텍스처

최적화를 위해 아래 3가지를 하나의 텍스처에 합치기도 합니다:

R AO (Ambient Occlusion)
G Roughness
B Metallic

 

이걸 ORM 텍스처 또는 Mask Map이라고 부릅니다. 텍스처 수를 줄이고 성능을 개선할 수 있습니다.

 

 

- 요약

텍스처 타입 역할 색상 타입
Albedo 기본 색상 RGB
Alpha 투명도 흑백
Height 실제 높낮이 흑백
Normal 가짜 굴곡 보라색 RGB
AO 틈 음영 흑백
Roughness 거칠기 흑백
Metallic 금속 여부 흑백
ORM 압축형 R/AO, G/Roughness, B/Metallic

PBR은 Three.js에서도 사용되고 있습니다. PBR에서 텍스처는 단순히 색상을 입히는 걸 넘어, 현실적인 조명 반응과 질감 표현을 가능하게 합니다, 각 텍스처의 역할을 정확히 이해해면 3D 퀄리티가 눈에 띄게 향상됩니다.

 

- PBR 참고 자료

PBR의 기초와 이론을 더 깊이 이해하고 싶다면, Marmoset의 공식 블로그 시리즈를 참고해보세요. Marmoset은 PBR 렌더링 엔진인 Toolbag을 만든 회사로, 해당 시리즈에는 빛의 물리적 특성, 재질의 반사와 분산, 에너지 보존, 메탈/러프니스 워크플로우의 원리까지 상세하게 다루고 있습니다.

 

 

https://marmoset.co/posts/basic-theory-of-physically-based-rendering/

 

Basic Theory of Physically-Based Rendering

"PBR" is bandied about a lot, often generating confusion as to what exactly it means. The short answer is: "many things", and "it depends". Let me explain...

marmoset.co

 

https://marmoset.co/posts/physically-based-rendering-and-you-can-too/

 

Physically-Based Rendering, And You Can Too!

We cover the basics of art content creation, some of the reasoning behind various PBR standards (without getting too technical), and squash some common misconceptions.

marmoset.co

 

- Texture

Three.js에서 이미지(Image)를 이용한 텍스처(Texture)를 사용하면 단순한 메쉬에 현실적인 질감과 시각 효과를 입힐 수 있습니다. 텍스처는 색상뿐 아니라 반사, 거칠기, 투명도 등 다양한 정보를 포함할 수 있으며, 특히 PBR(Physically Based Rendering) 환경에서 중요하게 쓰입니다. 

const image = new Image();

const texture = new THREE.Texture(image);

image.onload = () => {
  texture.needsUpdate = true;
};

image.src = "/textures/door/color.jpg";

new THREE.Texture(image)로 직접 Image 객체를 텍스처로 생성해줍니다. image.onload는 이미지가 완전히 로드된 후에 texture,needsUpdate = true를 호출하여 GPU에 반영합니다. 마지막으로 image.src = 경로는 이미지의 실제 경로를 저장하고, 로컬 서버를 기주능로 /public 폴더 하위 경로로 매핑됩니다.

const material = new THREE.MeshBasicMaterial({ map: texture });

텍스처를 사용하려면 머터리얼에도 map으로 등록해야 합니다.

 

텍스처가 브라우저에서 너무 밝게 보일 때는 색상 인코딩 설정을 의심해볼 필요가 있습니다.

// map, matcap을 사용하는 경우 sRGB에서 인코딩되어야 합니다.
const texture = new THREE.Texture(image);
texture.colorSpace = THREE.SRGBColorSpace;

특히 map, matcap, envMap과 같은 색상 관련 텍스처는 sRGB로 지정해야 정확한 색이 나옵니다. 기본적으로 브라우저에서 불러온 이미지 데이터는 sRGB 공간을 따르기 때문입니다.

정상적으로 렌더링 되는 것을 확인할 수 있습니다.

 

 

텍스처를 타일처럼 반복하려면 repeat을 설정해야 합니다.repeat.x = 2는 가로로 2번, repeat.y = 3은 세로로 3번 반복됩니다.

 

colorTexture.repeat.x = 2;
colorTexture.repeat.y = 3;

텍스처가 늘어나는 것을 확인할 수 있습니다. 이후에 wrapS, wrapT를 세팅하면 가로 2번, 세로 3번인 텍스처가 완성됩니다:

colorTexture.repeat.x = 2;
colorTexture.repeat.y = 3;
colorTexture.wrapS = THREE.RepeatWrapping;
colorTexture.wrapT = THREE.RepeatWrapping;

 

텍스처의 위치도 변경할 수 있습니다.

colorTexture.offset.x = 0.5;
colorTexture.offset.y = 0.5;

텍스처를 회전할 수도 있습니다.

colorTexture.rotation = Math.PI * 0.25;

현재 가운데 기준이 아닌 모서리 기준으로 회전하기 때문에 텍스처가 이상하게 회전했다 이 부분을 해결해주자:

colorTexture.rotation = Math.PI * 0.25;
colorTexture.center.x = 0.5;
colorTexture.center.y = 0.5;

이렇게 반복, 위치 이동, 회전을 구현해봤습니다.

 

 

- TextureLoader

Three.js는 TextureLoader를 통해 텍스처를 더 빠르고 쉽게 불러올 수 있습니다.

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("/textures/door/color.jpg");
const alphaTexture = textureLoader.load("/textures/door/alpha.jpg");
const heightTexture = textureLoader.load("/textures/door/height.jpg");
const normalTexture = textureLoader.load("/textures/door/normal.jpg");
const ambientOcclusionTexture = textureLoader.load("/textures/door/ambientOcclusion.jpg");
const metalnessTexture = textureLoader.load("/textures/door/metalness.jpg");
const roughnessTexture = textureLoader.load("/textures/door/roughness.jpg");

 

 

한 줄로 이미지 로딩 + 텍스처 생성 + needsUpdate 처리까지 자동화됩니다. 여러 텍스처도 쉽게 불러올 수 있어 실전에서 매우 유용합니다.

 

 

로딩 상태 추적(콜백 사용)

const texture = textureLoader.load(
  "/textures/door/color.jpg",
  () => {
    console.log("load");
  },
  () => {
    console.log("progress");
  },
  () => {
    console.log("error");
  }
);
첫 번째 콜백: 완료 시 실행
두 번째 콜백: 진행 상태(주로 생략)
세 번째 콜백: 에러 발생 시 실행

 

텍스처 로딩이 완료되었는 지 콘솔에서 확인해볼 수 있습니다.

 

- UV unwrapping

UV unwrapping은 3D 모델의 표면을 평면(2D)으로 펼쳐서 텍스처를 입히기 위한 과정입니다. 마치 입체 도형을 종이로 접기 전의 전개도처럼, 3D 표면을 2D로 펼치는 작업이라고 볼 수 있습니다.

출처: https://www.unwrap3d.com/u3d/index.aspx

 

 

 

모든 3D 모델은 단순히 표면만으로는 어디에 어떤 텍스처를 입힐 지 알 수 없습니다. 그래서 모델의 각 버텍스(Vertex)에서 UV 좌표를 추가로 부여해, 2D 텍스처 이미지 상의 어느 위치를 3D 모델의 어느 부분에 매핑할지 정의합니다. UV는 X, Y 값은 축 이름으로, 텍스처의 가로(U) / 세로(V) 좌표를 의미합니다. 이를 통해 모델에 그림을 그리듯 정확한 텍스처 위치를 지정할 수 있습니다.

 

 

예시

Three.js에서는 BufferGeometry를 통해 uv 속성을 직접 정의합니다.

geometry.setAttribute('uv', new THREE.BufferAttribute(uvArray, 2));
uvArray: [u0, v0, u1, v1, ...] 식의 1D 배열
itemSize = 2 -> 각 버텍스마다 (u, v) 2개 값 사용
예: 48개 값이라면, 총 24개 버텍스에 대한 UV 좌표 정의

 

UV는 Blender, Unity, Unreal Engine, Three.js 등 다양한 곳에서 사용됩니다, 3D 모델에 2D 텍스처를 정확하게 매핑하기 위한 좌표이며, UV unwrapping은 이를 위한 펼치기 작업입니다. 거의 모든 3D 그래픽 툴/엔진에서 기본적으로 사용됩니다. 텍스처 품질과 정확도는 UV unwraping의 퀄리티에 따라 달라집니다

 

- Texture Filtering & Mipmapping

3D 그래픽을 작업하다 보면, 텍스처가 가까이 있을 때는 뚜렷하지만, 멀리 가면 뿌옇거나 이상한 줄무늬가 생기는 걸 본 적 있을 겁니다. 이것은 단순히 해상도 문제나 이미지 품질 때문이 아닙니다. 바로 텍스처 필터링(Texture Filtering)과 밉매핑(Mipmapping) 설정 때문입니다.

 

이 두 가지 개념을 정리하고, 왜 Fliter와 Mipmap을 함께 쓰는 지 설명합니다.

 

 

밉매핑이란?

밉매핑(Mipmapping)은 하나의 텍스처 이미지를 크기를 줄여가며 계층적으로 여러 개 만들어 멀리 있는 물체에 낮은 해상도 버전을 자동으로 사용하는 기술입니다. 예를 들어 1024x1024 텍스처를 밉매핑하면 이렇게 구성됩니다:

1024x1024 → 512x512 → 256x256 → ... → 1x1

이 모든 이미지는 GPU에 함께 업로드되고, 카메라 거리나 렌더링 상황에 따라 적절한 해상도를 자동으로 선택해 렌더링 성능과 품질을 동시에 향상시킵니다. 해당 과정은 Three.js GPU가 자동으로 처리합니다. 

 

 

 

Minification Filiter (축소 필터)

MinFilter는 텍스처가 화면보다 클 때, 즉 멀리 있을 때 적용되는 필터입니다. 예를 들면 아래와 같습니다:

colorTexture.minFilter = THREE.NearestFilter;

 

이때 발생할 수 있는 문제가 바로 모아레 패턴(Moiré Pattern) 입니다.

 

=

 

모아레 패턴이 생기는 이유

THREE.NearestFilter는 축소 시 가장 가까운 픽셀 한 개만 사용하는 필터입니다. 즉, 픽셀 간의 선형 보간 없이 뚝뚝 끊어진 해상도 축소가 일어나죠.그 결과 고주파 텍스처(세밀한 반복 무늬)가 멀리서 보여질 때 다음과 같은 이상한 격자무늬, 줄무늬 같은 현상이 발생할 수 있습니다.

 

https://en.wikipedia.org/wiki/Moir%C3%A9_pattern

 

Moiré pattern - Wikipedia

From Wikipedia, the free encyclopedia Interference pattern A moiré pattern formed by two units of parallel lines, one unit rotated 5° clockwise relative to the other The fine lines that make up the sky in this image create moiré patterns when shown at s

en.wikipedia.org

 

NearestFilter + generateMipmaps = false 

이 설정은 Minecraft, 체커보드, 도트 스타일 게임이나 픽셀이 보간 없이 그대로 유지되어야 할 때 사용됩니다. 또한, 밉매핑은 여러 해상도 계층을 GPU에 업로드하기 때문에 메모리 사용량이 늘어납니다. 따라서 성능이 중요하거나, 픽셀 스타일이 필요한 경우엔 밉맵 없이 NearestFilter만 사용하는 것이 적합합니다.

colorTexture.generateMipmaps = false;
colorTexture.minFilter = THREE.NearestFilter;
colorTexture.magFilter = THREE.NearestFilter;

이 설정은 일반적인 고품질 텍스처에는 적합하지 않지만, 밉맵을 사용하지 않는 특정 상황에 대한 설정입니다.

 

 

 

Mipmapping + 필터링

모아레 패턴을 해결하려면 보통 밉매핑을 활성화하고, LineaMipmapLinearFilter 같은 선형 보간 필터를 사용하는 것이 이상적입니다:

colorTexture.generateMipmaps = true;
colorTexture.minFilter = THREE.LinearMipmapLinearFilter;

이렇게 하면 GPU가 거리나 축소 비율에 따라 부드러운 버전을 자동 선택해 모아레를 줄여줍니다.

 

 

결론

Three.js에서 텍스처는 단순히 이미지를 입히는 것을 넘어, 현실감 있는 3D를 구성하는 핵심 요소입니다. 특히 필터링(Filter)밉매핑(Mipmapping) 은 렌더링 품질과 성능 모두에 영향을 주는 중요한 설정입니다.

 

멀리 있는 텍스처가 깨지거나 줄무늬가 생긴다면, 밉매핑과 필터링 설정을 꼭 확인하세요. 성능 위주의 도트 스타일 게임이라면 generateMipmaps = false + NearestFilter 조합이 유리하고, 고품질 PBR 텍스처에는 LinearMipmapLinearFilter 조합으로 부드럽고 자연스러운 디테일을 유지할 수 있습니다.

각 텍스처의 역할과 GPU가 텍스처를 어떻게 처리하는지 이해하고 나면, Three.js에서의 그래픽 퀄리티는 훨씬 더 강력해질 수 있습니다.