ํ™ˆ three.js texture ์‚ดํŽด๋ณด๊ธฐ
ํฌ์ŠคํŠธ
์ทจ์†Œ

three.js texture ์‚ดํŽด๋ณด๊ธฐ

๐Ÿ’ป texture ๋ž€?

texture ๋Š” ๋ณดํ†ต geometry ์˜ ํ‘œ๋ฉด์„ ๊ฐ์Œ€ ์ˆ˜ ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์˜๋ฏธํ•˜๋ฉฐ, ๋ณดํ†ต ํฌํ† ์ƒต๊ณผ ๊ฐ™์€ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
์—ฌ๋Ÿฌ ์ข…๋ฅ˜๊ฐ€ ์กด์žฌํ•˜๋ฉฐ, ๊ฐ texture ๋งˆ๋‹ค geometry ์— ์ ์šฉ๋˜๋Š” ํšจ๊ณผ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

๐Ÿ’ป texture ์ข…๋ฅ˜

๐Ÿ‘จโ€๐Ÿ’ป Albedo (Diffuse / Color)

Albedo ๋Š” ๊ฐ€์žฅ ํ”ํ•œ texture ๋กœ, ์ƒ‰์ƒ๊ณผ ํŒจํ„ด์„ ์ด์šฉํ•˜์—ฌ ๋ฌผ์ฒด์˜ ์งˆ๊ฐ์ด๋‚˜ ์ƒ‰์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Opacity (Transparency)

Opacity ๋Š” ํŠน์ • ๋ถ€๋ถ„์„ ํˆฌ๋ช…ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” texture ์ž…๋‹ˆ๋‹ค.
ํšŒ์ƒ‰์กฐ(gray scale)๋ฅผ ๋„๋ฉฐ, ํฐ์ƒ‰๋ถ€๋ถ„์€ ๋ณด์ด๋Š” ๋ถ€๋ถ„์„, ๊ฒ€์€์ƒ‰ ๋ถ€๋ถ„์€ ๋ณด์ด์ง€ ์•Š๋Š” ๋ถ€๋ถ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ํšŒ์ƒ‰์„ ๋„๋Š” ๋ถ€๋ถ„์€ ํฐ์ƒ‰๊ณผ ๊ฒ€์€์ƒ‰ ์‚ฌ์ด์˜ ๋‹ค์–‘ํ•œ ๊ฐ’์˜ ํˆฌ๋ช…๋„๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Height (Displacement)

Height ๋Š” ๋†’๋‚ฎ์ด(๊ตด๊ณก)๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•˜๋Š” texture ์ž…๋‹ˆ๋‹ค.
ํšŒ์ƒ‰์กฐ(gray scale)์„ ๋„๋ฉฐ, ํ•ด๋‹น texture ๊ฐ€ ์ ์šฉ๋˜๋Š” geometry ์˜ ๋ฉด๋“ค์„ ์ž˜๊ฒŒ๋‚˜๋ˆ„์–ด ํ•ด๋‹น ์ •์ ๋“ค์„ ๋ณ€๊ฒฝํ•˜๋Š” ํŠน์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. texture ์˜ ๊ฒ€์€์ƒ‰ ๋ถ€๋ถ„์€ ์ •์ ์„ ์•„๋ž˜๋กœ ์ด๋™์‹œํ‚ค๋ฉฐ, ํฐ์ƒ‰ ๋ถ€๋ถ„์€ ์œ„๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค. ํšŒ์ƒ‰์„ ๋„๋Š” ๋ถ€๋ถ„์€ ๋‘์ƒ‰ ์‚ฌ์ด์˜ ์ง€์ ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
Height ์˜ ์žฅ์ ์€ ์„ธ๋ถ€์ ์ธ ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•œ ๊ฒƒ์ด์ง€๋งŒ, ์„ฑ๋Šฅ์ƒ์˜ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Normal

Normal ์€ geometry ์˜ ์ •์ ๋“ค์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ , ๋ฒ•์„  ๋ฒกํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น›์ด ํ‘œ๋ฉด๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ์‹์„ ๊ฐ€์งœ๋กœ ๋งŒ๋“ค์–ด ํ‘œ๋ฉด์˜ ๋””ํ…Œ์ผ์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์—ฐํ•œ ๋ณด๋ผ์ƒ‰์„ ๋„๊ณ  ์žˆ์œผ๋ฉฐ, rgb ๊ฐ’๋“ค์€ ๋†’๋‚ฎ์ด๋‚˜, ํ‹ˆ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Ambient Occlusion

geometry ๊ฐ€ ๋น›์— ์–ผ๋งˆ๋‚˜ ๋…ธ์ถœ๋˜๋Š”์ง€๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” texture ์ž…๋‹ˆ๋‹ค.
ํšŒ์ƒ‰์กฐ๋ฅผ ๋„๋ฉฐ, ๋ฐ์€ ๋ถ€๋ถ„์€ ๋น›์„ ํก์ˆ˜ํ•˜๋Š” ๋ถ€๋ถ„์ด๊ณ  ์–ด๋‘์šด ๋ถ€๋ถ„์€ ๋น›์— ๋œ ๋ฐ˜์‘ํ•˜๋Š” ๋ถ€๋ถ„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Metalness

๊ธˆ์†์„ฑ๊ณผ, ๊ธˆ์†์˜ ๋ฐ˜์‚ฌ๋ฅผ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” texture ์ž…๋‹ˆ๋‹ค.
ํšŒ์ƒ‰์กฐ์˜ ์ด๋ฏธ์ง€์ด๋ฉฐ, ํฐ์ƒ‰ ๋ถ€๋ถ„์€ ๊ธˆ์†์„ฑ์„ ๋„๋Š” ๋ถ€๋ถ„์„, ๊ฒ€์€์ƒ‰ ๋ถ€๋ถ„์€ ๊ธˆ์†์„ฑ์„ ๋„์ง€ ์•Š๋Š” ๋ถ€๋ถ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Roughness

๊ฑฐ์น ๊ธฐ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” texture ์ž…๋‹ˆ๋‹ค.
ํšŒ์ƒ‰์กฐ์ด๋ฉฐ, ํฐ์ƒ‰์€ ์ตœ๋Œ€ ๊ฑฐ์น ๊ธฐ๋ฅผ, ๊ฒ€์€์ƒ‰์€ ์ตœ์†Œ ๊ฑฐ์น ๊ธฐ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ป texture ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

texture ๋ฅผ geometry ์— ์ ์šฉ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ๋Š” texture ๋ฅผ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. texture ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์–‘ํ•˜์ง€๋งŒ ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ• ๋‘๊ฐ€์ง€๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป image.onload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ...์ƒ๋žต...
THREE.ColorManagement.enabled = false;

const image = new Image();

const texture = new THREE.Texture(image);
image.src("image-path");

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

// ...์ƒ๋žต...

const mesh = new THREE.Mesh(
  geometry,
  new THREE.BasicMeshMaterial({ map: texture })
);

// ...์ƒ๋žต...

์œ„ ์˜ˆ์‹œ๋Š” Image ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ texture ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
onload ๋Š” Image ๊ฐ์ฒด์— ์ด๋ฏธ์ง€ ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋กœ, ํ•จ์ˆ˜ ๋‚ด๋ถ€์— texture.needsUpdated = true ๊ฐ€ ์„ ์–ธ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ด๋ฏธ์ง€ ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜์—ˆ์–ด๋„ texture ๋Š” ์™„๋ฃŒ์—ฌ๋ถ€๋ฅผ ์•Œ์ง€๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— needsUpdate ๋ฅผ true ๋กœ ์„ค์ •ํ•ด texture ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป TextureLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
// ...์ƒ๋žต...
THREE.ColorManagement.enabled = false;

const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("image-path");

// ...์ƒ๋žต...
const mesh = new THREE.Mesh(
  geometry,
  new THREE.BasicMeshMaterial({ map: texture })
);

// ...์ƒ๋žต...

์œ„ ์˜ˆ์‹œ์™€ ๊ฐ™์ด TextureLoader ๋ฅผ ์‚ฌ์šฉํ•ด์„œ texture ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, onload ๋ฐฉ์‹์œผ๋กœ ๋ถˆ๋Ÿฌ์™”์„๋•Œ๊ณผ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ–Š TextureLoader์˜ ์ฝœ๋ฐฑํ•จ์ˆ˜

TextureLoader ์˜ load ํ•จ์ˆ˜๋Š” ์ด๋ฏธ์ง€ url ์™ธ์—๋„ onLoad, onError, onProgress ์™€ ๊ฐ™์€ ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ์ธ์ž๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค.

  • onLoad
    ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜๋กœ, ๋กœ๋”ฉ๋œ texture ๋ฅผ ์ธ์ž๋กœ ๊ฐ–์Šต๋‹ˆ๋‹ค.

  • onError
    ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

  • onProgress
    ๋กœ๋”ฉ์ด ์ง„ํ–‰ ์ค‘์ผ ๊ฒฝ์šฐ ์‹คํ–‰๋˜๋Š” ์ฝœ๋ฐฑ์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” ์ง€์›๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๐Ÿ’ป LoadingManager

texture ๋“ค์˜ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•˜๊ณ , ์ƒํƒœ์— ๋”ฐ๋ฅธ ์ธํ„ฐ๋ ‰์…˜์„ ์ฃผ๊ธฐ์œ„ํ•ด LoadingManager ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ...์ƒ๋žต...
const loadingManager = new THREE.LoadingManager();

const textureLoader = new THREE.TextureLoader(loadingManager);

const materials = [
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-1"),
  }),
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-2"),
  }),
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-3"),
  }),
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-4"),
  }),
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-5"),
  }),
  new THREE.MeshBasicMaterial({
    map: textureLoader.load("image-path-6"),
  }),
];
// ...์ƒ๋žต...

์œ„ ์˜ˆ์‹œ๋Š” 6๊ฐœ์˜ ์ด๋ฏธ์ง€๋ฅผ texture ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ถˆ๋Ÿฌ์˜จ ํ›„, BoxGeometry ์˜ ํ•œ ๋ฉด์”ฉ ์ ์šฉ๋˜๋„๋ก ๋งŒ๋“œ๋Š” ์ฝ”๋“œ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค. LoadingManager ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด, TextureLoader ์ƒ์„ฑ์ž ์ธ์ž์— ์ „๋‹ฌํ•œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ LoadingManager ๊ฐ€ ์†์„ฑ์œผ๋กœ ๊ฐ–๋Š” ํ•จ์ˆ˜๋“ค์„ ์ด์šฉํ•ด ์ž์—ฐ์Šค๋Ÿฌ์šด UX ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป onLoad

texture ๋“ค์ด ๋ชจ๋‘ ๋กœ๋”ฉ ๋˜์—ˆ์„ ๋•Œ, ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
// ...์ƒ๋žต...
loadingManager.onLoad = () => {
  const mesh = new THREE.Mesh(geometry, materials);
  scene.add(mesh);
};
// ...์ƒ๋žต...

์œ„ ์ฝ”๋“œ๋Š” ๋ชจ๋“  texture ๋“ค์˜ ๋กœ๋”ฉ์ด ์™„๋ฃŒ ๋˜์—ˆ์„ ๋•Œ, mesh ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋กœ ์ด๋ฏธ์ง€๊ฐ„ ๋กœ๋”ฉ ์†๋„๊ฐ€ ๊ท ์ผํ•˜์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป onProgress

์—ฌ๋Ÿฌ๊ฐœ์˜ ์ž๋ฃŒ๋“ค์ค‘, ํ•œ๊ฐœ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋กœ url, itemsLoaded, itemsTotal ์„ธ๊ฐœ์˜ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
url ์€ ํ˜„์žฌ ๋กœ๋”ฉ์ค‘์ธ ์ž๋ฃŒ์˜ url ์„, itemsLoaded ๋Š” ํ˜„์žฌ๊นŒ์ง€ ๋กœ๋”ฉ์™„๋ฃŒ๋œ ์ž๋ฃŒ์˜ ๊ฐฏ์ˆ˜, itemsTotal ์€ ๋กœ๋”ฉํ•ด์•ผ ํ•  ์ „์ฒด ์ž๋ฃŒ์˜ ๊ฐฏ์ˆ˜๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
// ...์ƒ๋žต...
loadingManager.onProgress = (url, itemsLoaded, itemsTotal) => {
  console.log(
    `current url is ${url}, and this item is ${itemsLoaded}/${itemsTotal}`
  );
};

onProgress ํ•จ์ˆ˜๋ฅผ ์ด์šฉํ•ด ํ”„๋กœ๊ทธ๋ž˜์Šค ๋ฐ”๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚จ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

loadingManager-onProgress-1

๐Ÿ‘จโ€๐Ÿ’ป onStart

์—ฌ๋Ÿฌ๊ฐœ์˜ ์ž๋ฃŒ๋“ค์ค‘, ํ•œ๊ฐœ๊ฐ€ ๋กœ๋”ฉ์„ ์‹œ์ž‘ํ•  ๋•Œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋กœ onProgress ์™€ ๊ฐ™์€ ์ธ์ž๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป onError

๋กœ๋”ฉ ๊ณผ์ •์ค‘์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค.
url ์„ ์ธ์ž๋กœ ๋ฐ›์œผ๋ฉฐ, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ์ž๋ฃŒ์˜ url ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์œ„ ์ฝ”๋“œ๋“ค์„ ์ถ”๊ฐ€ํ•œ ํ›„ ์‹คํ•ด์‹œํ‚ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ mesh ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค,

loadingManager-1

๐Ÿ’ป UV Mapping

UV mapping ์€ 2D ์ด๋ฏธ์ง€๋ฅผ 3D ๋ฌผ์ฒด์˜ ํ‘œ๋ฉด์œผ๋กœ ํˆฌ์˜ํ•˜๋Š” ๊ณผ์ •์œผ๋กœ, ํ‰๋ฉด์ธ texture ๋ฅผ 3D์ธ mesh ์— ์ ์šฉ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ์„ธ์Šค ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ UV mapping ์„ ์ง„ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” mesh ๋ฅผ ์ „๊ฐœ๋„์™€ ๋น„์Šทํ•œ ํ‰๋ฉด์œผ๋กœ ๋ณ€ํ™˜(UV Map)์‹œ์ผœ์ฃผ๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•œ๋ฐ ์ด ๊ณผ์ •์„ UV unwrapping ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป UV ์ขŒํ‘œ๊ณ„

UV mapping, UV unwrapping ์— ์–ธ๊ธ‰๋œ UV ๋Š” mesh ์˜ ์ •์ ๋“ค(vertices)์— ์ €์žฅ๋œ 2D texture ์˜ ์ขŒํ‘œ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
๋ณดํ†ต openGL ์—์„œ๋Š” U ๋Š” ๊ฐ€๋กœ์ถ•, V ๋Š” ์„ธ๋กœ์ถ•์„ ์˜๋ฏธํ•˜์ง€๋งŒ ์ด๋Š” ์‚ฌ์šฉํ•˜๋Š” ๋„๊ตฌ์— ๋”ฐ๋ผ ์ƒ์ดํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ๊ฐ์ถ•์˜ ์ตœ์†Ÿ๊ฐ’์€ 0, ์ตœ๋Œ“๊ฐ’์€ 1 ์ž…๋‹ˆ๋‹ค.

uv-coordinate

์œ„ ๊ทธ๋ฆผ์€ texture ์˜ UV ์ขŒํ‘œ๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ธ ๊ฒƒ์ด๋ฉฐ, ์•„๋ž˜๋Š” ํ•ด๋‹น texture ๋ฅผ BoxGeometry ์˜ ํ•œ ๋ฉด์— UV ์ขŒํ‘œ์— ๋”ฐ๋ผ ์ ์šฉ์‹œํ‚จ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

uv-mapping

BoxGeometry ์˜ ๊ฐ ์ •์ ๋“ค์— ํ• ๋‹น๋œ UV ์ขŒํ‘œ์— ํ•ด๋‹นํ•˜๋Š” texture ๊ฐ€ ์ ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ UV ์˜ ์ขŒํ‘œ์˜ ์ตœ๋Œ“๊ฐ’๋ณด๋‹ค ํฐ ์ขŒํ‘œ๊ฐ€ ํ• ๋‹น ๋˜์—ˆ์„ ๊ฒฝ์šฐ, ๋นˆ๊ณต๊ฐ„์„ ์ฑ„์šฐ๊ธฐ์œ„ํ•ด ๋ฐ˜๋ณต๋˜์—ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ์„ค์ •์—๋”ฐ๋ผ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

๐Ÿ’ป texture ๋ณ€ํ˜•ํ•˜๊ธฐ

๐Ÿ‘จโ€๐Ÿ’ป repeat

UV ์ขŒํ‘œ๊ณ„์˜ ์ถ• ๋ฐฉํ–ฅ์œผ๋กœ texture ๋ฅผ ์–ผ๋งˆ๋‚˜ ๋ฐ˜๋ณต์‹œํ‚ฌ ๊ฒƒ์ธ์ง€ ์„ค์ •ํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค.
Vector2 ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, 1๋ณด๋‹ค ํฐ ๊ฐ’์„ ์„ค์ •ํ•˜๊ฒŒ๋˜๋ฉด wrap ์†์„ฑ(wrapS, wrapT) ์— THREE.RepeatWrapping ์ด๋‚˜ THREE.MirroredRepeatWrapping ์„ ์ถ”๊ฐ€๋กœ ์ง€์ •ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
// ...์ƒ๋žต...
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load("image-path");

texture.repeat.x = 3;

// ...์ƒ๋žต...

์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚จ ๊ฒฐ๊ณผ ์ž…๋‹ˆ๋‹ค.

texture-repeat-1

U (x) ์ถ• ๋ฐฉํ–ฅ์œผ๋กœ ๋Š˜์–ด๋‚˜๊ธฐ๋งŒ ํ•  ๋ฟ, ๋ฐ˜๋ณต๋˜์ง€ ์•Š์•˜์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ wrapS ์— THREE.RepeatWrapping ์†์„ฑ์„ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
// ...์ƒ๋žต...
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load("image-path");

texture.repeat.x = 3;
texture.wrapS = THREE.RepeatWRapping;

// ...์ƒ๋žต...

texture-repeat-2

๋งŒ์•ฝ wrap ํ”„๋กœํผํ‹ฐ์— THREE.MirroredRepeatWrapping ์„ ์„ค์ •ํ•ด์ฃผ๊ฒŒ ๋˜๋ฉด, ๋งค ๋ฐ˜๋ณต์‹œ texture ๊ฐ€ ๋ฐ˜์ „๋ฉ๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป offset

UV ์ขŒํ‘œ๊ณ„์˜ ์ถ• ๋ฐฉํ–ฅ์œผ๋กœ mesh ์— ์ ์šฉ๋  texture ์˜ offset ์„ ์„ค์ •ํ•ด ์ค๋‹ˆ๋‹ค.
Vector2 ํƒ€์ž…์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
// ...์ƒ๋žต...
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load("image-path");

texture.repeat.x = 3;
texture.wrapS = THREE.RepeatWRapping;

texture.offset.x = 0.5;

// ...์ƒ๋žต...

์œ„ ์ฝ”๋“œ๋Š” x ์ถ• ๋ฐฉํ–ฅ์œผ๋กœ texture ๋ฅผ 3๋ฒˆ ๋ฐ˜๋ณตํ•œ ํ›„, offset ์„ 0.5๋กœ ์„ค์ • ํ•œ ์ฝ”๋“œ ์ด๋ฉฐ ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

texture-offset-1

๐Ÿ‘จโ€๐Ÿ’ป rotation

texture ๊ฐ€ ์ค‘์ ์„ ์ค‘์‹ฌ์œผ๋กœ ์–ผ๋งˆ๋‚˜ ํšŒ์ „ํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ์†์„ฑ์ž…๋‹ˆ๋‹ค.
์ค‘์ ์€ center ์†์„ฑ์„ ํ†ตํ•ด ์„ค์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’์€ UV ์ขŒํ‘œ๋กœ (0, 0) ์ฆ‰ ์™ผ์ชฝ ์•„๋ž˜๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
// ...์ƒ๋žต...
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);
const texture = textureLoader.load("image-path");

texture.repeat.x = 3;
texture.wrapS = THREE.RepeatWrapping;

texture.rotation = Math.PI * 0.25;

์œ„ ์˜ˆ์‹œ๋Š” texture ๋ฅผ ๐›‘/4 ๋งŒํผ ํšŒ์ „์‹œํ‚ค๋Š” ์ฝ”๋“œ์ด๋ฉฐ, ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

texture-rotation-1

UV ์ขŒํ‘œ๊ณ„์—์„œ (0, 0) ์ฆ‰ ์™ผ์ชฝ ์•„๋ž˜๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํšŒ์ „์ด ๋˜์—ˆ๊ณ , ๊ทธ๋กœ์ธํ•ด ์ƒ๊ธด ๋นˆ๊ณต๊ฐ„์„ ์ฑ„์šฐ๊ธฐ์œ„ํ•ด texture ๊ฐ€ ๋Š˜์–ด๋‚œ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
// ...์ƒ๋žต...
texture.center.x = 0.5;
texture.center.y = 0.5;
// ...์ƒ๋žต...

์œ„์™€ ๊ฐ™์ด ์ค‘์‹ฌ์„ ๋ฉด์˜ ํ•œ ๊ฐ€์šด๋ฐ๋กœ ์˜ฎ๊ธฐ๊ฒŒ ๋˜๋ฉด, ํ•ด๋‹น ์ขŒํ‘œ๋ฅผ ์ค‘์‹ฌ์œผ๋กœ ํšŒ์ „ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ’ป Mips and Filtering

mip-map-1

์œ„ ์ด๋ฏธ์ง€๋Š” PlaneGeometry ์— 16x16 ํฌ๊ธฐ์˜ texture ๋ฅผ ์ ์šฉํ•œ ํ›„, camera ๋ฅผ mesh ์™€ ๊ฑฐ์˜ ํ‰ํ–‰ํ•˜๊ฒŒ ์œ„์น˜์‹œํ‚จ ๋ชจ์Šต์ž…๋‹ˆ๋‹ค. Camera ์—์„œ ๋ฉ€๋ฆฌ ๋–จ์–ด์ง„ texture ์ผ์ˆ˜๋ก ํ๋ ค์ง€๋Š” ํ˜„์ƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๋Š” filter ๊ณผ mip mapping ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Mip mapping

Mip mapping ์€ ๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ์ตœ์ ํ™” ํ•˜๊ธฐ ์œ„ํ•ด, ์›๋ณธ texture ์˜ ํฌ๊ธฐ๋ฅผ ์ถ•์†Œ์‹œํ‚จ texture ๋“ค์˜ ๋ณต์‚ฌ๋ณธ๋“ค(mips)์˜ ์ง‘ํ•ฉ(mip map)์„ ์‚ฌ์ „์— ๋งŒ๋“œ๋Š” ๊ณผ์ •์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

mipmap-low-res-enlarged

์œ„ ๊ทธ๋ฆผ์€ 16 x 16 ํฌ๊ธฐ์˜ ์›๋ณธ texture ๋ฅผ ์ถ•์†Œ์‹œํ‚จ mip map ์ž…๋‹ˆ๋‹ค.
16 x 16 โ‡’ 8 x 8 โ‡’ 4 x 4 โ‡’ 2 x 2 โ‡’ 1 x 1 ์™€ ๊ฐ™์ด ๋†’์ด์™€ ๋„“์ด๋ฅผ ์ ˆ๋ฐ˜์œผ๋กœ ์ค„์—ฌ๊ฐ€๋ฉฐ ์ถ•์†Œ์‹œํ‚จ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์œผ๋ฉฐ, 1 x 1 ํฌ๊ธฐ์˜ mip ์„ ์–ป์„ ๋•Œ ๊นŒ์ง€ ์ถ•์†Œ์‹œํ‚จ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป Filtering

๋ Œ๋”๋ง ํ•ด์•ผํ•˜๋Š” texture ๊ฐ€ ์›๋ณธ ํฌ๊ธฐ๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜, ์ž‘์„ ๊ฒฝ์šฐ ์›๋ณธ ๊ทธ๋Œ€๋กœ ๋ Œ๋”๋งํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ๋ Œ๋”๋งํ•ด์•ผํ•˜๋Š” ์ ์ ˆํ•œ ํ”ฝ์…€๊ฐ’์„ ๊ฒฐ์ •ํ•ด์•ผ ํ•˜๋Š”๋ฐ ์ด ๊ณผ์ •์„ filtering ์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ–Š Magnification Filter

texture ์˜ ํฌ๊ธฐ๊ฐ€ ์›๋ณธ ๋ณด๋‹ค ์ปค์ ธ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, ์ฆ‰ ์›๋ณธ texture ๊ฐ€ mesh ๋ณด๋‹ค ์ž‘์„ ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋Š” filter ์ž…๋‹ˆ๋‹ค.
์ด ์ƒํ™ฉ์—๋Š” texel (texture ์˜ 1px) ์ด ํ•˜๋‚˜ ์ด์ƒ์˜ ํ”ฝ์…€์„ ์ปค๋ฒ„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • THREE.LinearFilter
    ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด 4๊ฐœ์˜ ํ”ฝ์…€์„ ๊ณจ๋ผ, ๊ฐ ํ”ฝ์…€์˜ ์‹ค์ œ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋น„์œจ๋กœ ์„ž๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
    three.js ์—์„œ๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ ์šฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

    1
    2
    3
    4
    
    // ...์ƒ๋žต...
    const texture = textureLoader.load("small-image-path");
    texture.magFilter = THREE.LinearFilter;
    // ...์ƒ๋žต...
    

    ์œ„ ์ฝ”๋“œ๋Š” ์•„์ฃผ ์ž‘์€ ์ด๋ฏธ์ง€๋ฅผ texture ๋กœ ๋ถˆ๋Ÿฌ์˜จ ํ›„, magnification filter ๋กœ LinearFilter ๋ฅผ ์ ์šฉํ•ด์ค€ ์ฝ”๋“œ๋กœ, ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    mag-linear-filter

  • THREE.NearestFilter
    texture ์—์„œ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ํ”ฝ์…€์„ ๊ณจ๋ผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค. ํ•ด์ƒ๋„๊ฐ€ ๋‚ฎ์€ texture ์— ์ ์šฉํ•  ๊ฒฝ์šฐ ํ”ฝ์…€ํ™” ๋ฉ๋‹ˆ๋‹ค.

    1
    2
    3
    4
    
    // ...์ƒ๋žต...
    const texture = textureLoader.load("small-image-path");
    texture.magFilter = THREE.NearestFilter;
    // ...์ƒ๋žต...
    

    ์œ„ ์˜ˆ์‹œ๋Š” NearestFilter ๋ฅผ ์ ์šฉํ•œ ์ฝ”๋“œ์ด๊ณ  ๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    mag-nearest-filter-1

    texture ๊ฐ ํ”ฝ์…€ํ™” ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ–Š Minification Filter

texture ๊ฐ€ ์›๋ณธ๋ณด๋‹ค ์ž‘์•„์ ธ์•ผ ํ•  ๊ฒฝ์šฐ, ์ฆ‰ ์›๋ณธ texture ๊ฐ€ mesh ๋ณด๋‹ค ํด ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋Š” filter ์ž…๋‹ˆ๋‹ค.
์ด ๊ฒฝ์šฐ๋Š” texel ์ด ์ตœ๋Œ€ ํ•œ๊ฐœ์˜ ํ”ฝ์…€์„ ์ปค๋ฒ„ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

  • THREE.NearestFilter
    Magnification filter ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ํ”ฝ์…€์„ ๊ณจ๋ผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.

  • THREE.LinearFilter
    Magnification filter ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ฃผ๋ณ€์˜ ๊ฐ€๊นŒ์šด ํ”ฝ์…€ 4๊ฐœ์„ ๊ณจ๋ผ ์ ์ ˆํ•œ ๋น„์œจ๋กœ ์„ž์Šต๋‹ˆ๋‹ค.

  • THREE.NearestMipmapNearestFilter
    ์ ์ ˆํ•œ mip ์„ ๊ณ ๋ฅธ ํ›„, mip ์—์„œ ํ”ฝ์…€ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

  • THREE.NearestMipmapLinearFilter
    ๋‘ ๊ฐœ์˜ mip ์„ ๊ณจ๋ผ ํ”ฝ์…€์„ ํ•˜๋‚˜์”ฉ ์„ ํƒํ•œ ํ›„, ๋‘ ๊ฐœ์˜ ํ”ฝ์…€์„ ์ ์ ˆํ•œ ๋น„์œจ๋กœ ์„ž์Šต๋‹ˆ๋‹ค.

  • THREE.LinearMipmapNearestFilter
    ์ ์ ˆํ•œ ๋ฐ‰์„ ๊ณ ๋ฅธ ๋’ค ๋„ค ๊ฐœ์˜ ํ”ฝ์…€์„ ๊ณจ๋ผ ์„ž์Šต๋‹ˆ๋‹ค.

  • THREE.LinearMipmapLinearFilter
    ๋‘ ๊ฐœ์˜ mip ์„ ๊ณจ๋ผ ๊ฐ๊ฐ ํ”ฝ์…€์„ 4๊ฐœ์”ฉ ์„ ํƒํ•˜๊ณ , ์„ ํƒํ•œ 8๊ฐœ์˜ ํ”ฝ์…€์„ ์„ž์Šต๋‹ˆ๋‹ค.
    three.js ์˜ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

mini-filter-compare

์œ„ ์ด๋ฏธ์ง€๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด, mip ์˜ ๊ฐ ๋‹จ๊ณ„๊ฐ€ ๋‹ค๋ฅธ texture ์— ์—ฌ์„ฏ๊ฐœ์˜ minification filter ๋ฅผ ์ ์šฉํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

different-mip-maps

์œ„์˜ ์˜ˆ์‹œ์—์„œ NearestFilter ์™€ LinearFilter ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, ํ•ญ์ƒ ์›๋ณธ texture ๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์•„ mipmap ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GPU๊ฐ€ ์›๋ณธ texture ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ€๋ฆฌ ๋–จ์–ด์งˆ์ˆ˜๋ก ๊นœ๋นก๊ฑฐ๋ฆผ์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‚˜๋จธ์ง€ 4๊ฐœ์˜ filter ๋Š” ๋ชจ๋‘ mipmap ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์ ์— ๋”ฐ๋ผ ๋ Œ๋”๋ง ๋˜๋Š” texture ๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค. LinearMipmapLinearFilter ๊ฐ€ ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šฐ๋‚˜, 2๊ฐœ์˜ mip ์—์„œ ๊ฐ๊ฐ 4๊ฐœ์˜ ํ”ฝ์…€์„ ๊ฐ€์ ธ์˜ค๊ธฐ๋•Œ๋ฌธ์— ๊ณ„์‚ฐ๋Ÿ‰์ด ๋งŽ์•„์ง„๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

NearestFilter ์™€ LinearFilter ๋Š” mipmap ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด mipmap ์ƒ์„ฑ์„ ๋น„ํ™œ์„ฑํ™” ์‹œํ‚ค๋ฉด GPU์˜ ๋ถ€ํ•˜๋ฅผ ์กฐ๊ธˆ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1
2
3
// ...์ƒ๋žต...
texture.generateMipmaps = false;
// ...์ƒ๋žต...

๐Ÿ“” ์ฐธ๊ณ ์ž๋ฃŒ

How to make photorealistic 3D graphics with different texture maps?
A Brief Introduction to Texture mapping for 3D Artists
Texture Maps: The Ultimate Guide For 3D Artists
UV Unwrapping
What is UV Mapping & Unwrapping? (full beginners guide)
[ํฌํ”„์˜ ์‰์ด๋” ์ž…๋ฌธ๊ฐ•์ขŒ] 03. ํ…์Šค์ฒ˜๋งคํ•‘ Part 1
What is mipmap technique and whatโ€™s the benefit of using it in rendering?

์ด ๊ธฐ์‚ฌ๋Š” ์ €์ž‘๊ถŒ์ž์˜ CC BY 4.0 ๋ผ์ด์„ผ์Šค๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

three.js geometry ์‚ดํŽด๋ณด๊ธฐ

three.js material ์‚ดํŽด๋ณด๊ธฐ